Home »

Cómo crear un plugin de Mattermost

En este tutorial vamos a crear un sencillo plugin para Mattermost con Go que nos servirá de base de conocimiento para desarrollar plugins más complejos. Por si no lo conocéis, Mattermost es una aplicación de mensajería para equipos similar a Slack, pero open-source.

En el ejemplo que vamos a desarrollar el plugin responderá a un comando con un usuario random conectado al canal, como podéis ver en el gif.

Preparación

Para probar el plugin necesitamos tener Mattermost en nuestra máquina, si no es así Mattermost tiene una imagen de Docker muy sencilla de instalar. Esta imagen está pensada para poder probar Mattermost, no para ser usada en producción.

Para instalarla hay que tener Docker instalado y ejecutar este comando:

1
docker run --name mattermost-preview -d --publish 8065:8065 --add-host dockerhost:127.0.0.1 mattermost/mattermost-preview

En este repositorio tenéis más detalles de la instalación.

Si ha ido bien podemos acceder a la nueva instancia de Mattermost en http://localhost:8065/, donde podemos crear nuestro usuario.

Para este plugin, necesitamos tener algunos usuarios extra. Para crear nuevos usuarios vamos al menú principal y seleccionamos “Get Team Invite Link”, copiamos la url y la abrimos en un nuevo navegador. Para crear el usuario hacemos esto tantas veces como usuarios nuevos queramos crear.

Ya tenemos Mattermost instalado con varios usuarios de prueba, vamos a empezar el plugin.

El Plugin

La forma más sencilla de empezar un plugin desde cero es partir de este template que nos da Mattermost.

Primero nos clonamos el template:

1
git clone --depth 1 https://github.com/mattermost/mattermost-plugin-starter-template com.example.my-plugin

Ahora en el repo que hemos clonado editamos plugin.json donde rellenamos “name”, “id” y “description”. También podemos quitar webapp del json porque nuestro plugin no va a tener front.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"id": "random-user-plugin",
"name": "Random user plugin",
"description": "This plugin return a random user in the channel",
"version": "0.1.0",
"min_server_version": "5.12.0",
"server": {
"executables": {
"linux-amd64": "server/dist/plugin-linux-amd64",
"darwin-amd64": "server/dist/plugin-darwin-amd64",
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
}
},
"settings_schema": {
"header": "",
"footer": "",
"settings": []
}
}

Después de editar el json vamos a la consola y en la raíz del proyecto ejecutamos make.

Si todo va bien vemos este mensaje: plugin built at: dist/random-user-plugin-0.1.0.tar.gz.

Para subir el plugin vamos a “System Console > Plugins > Plugin Management > Upload Plugin” y cuando esté subido lo activamos.

Tenemos que seguir estos pasos cada vez que queramos probar nuestro plugin. El que acabamos de subir no tiene nada, así que volvamos al código para empezar a programar.

Abrimos server/plugin.go, vamos a empezar añadiendo un hook cuando el plugin se active. En este hook vamos a registrar un bot que devolverá el usuario elegido y el comando a utilizar para que el bot responda.

Primero creamos el hook:

1
2
3
func (p *Plugin) OnActivate() error {

}

Ahora registramos el bot dentro de la función OnActivate:

1
2
3
4
5
6
7
8
9
10
11
12
bot := &model.Bot{
Username: "random-user",
DisplayName: "RandomUser",
}

botUserID, ensureBotErr := p.Helpers.EnsureBot(bot)

if ensureBotErr != nil {
return ensureBotErr
}

p.botUserID = botUserID

En las primeras líneas estamos dando un username y un display name al bot. Para ello, usamos el modelo de Mattermost que podemos importar desde github.com/mattermost/mattermost-server/v5/model.

En la siguientes líneas creamos el bot con p.Helpers.EnsureBot y gestionamos el error si hubiese alguno.

Por último, guardamos el id del bot recién creado en el plugin, para ello, tenemos que extender el modelo de plugin que tenemos al inicio del fichero. Lo dejamos así:

1
2
3
4
5
6
7
8
9
10
type Plugin struct {
plugin.MattermostPlugin

configurationLock sync.RWMutex

configuration *configuration

// Nuestro bot id
botUserID string
}

Continuamos registrando el comando que tiene que escribir el usuario para que el bot reaccione. Añadimos las siguientes líneas al final de OnActivate:

1
2
3
4
5
return p.API.RegisterCommand(&model.Command{
// Comando
Trigger: "random-user",
AutoComplete: true,
})

RegisterCommand tiene muchas más opciones que podemos consultar aquí.

Ahora que hemos registrado nuestro bot y el comando, vamos a escribir el código que se va ejecutar cuando se invoque a /random-user.

Primero creamos un nuevo hook que se lanzará cuando se ejecute un comando:

1
2
3
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {

}

También necesitaremos todos los usuarios del canal actual. Para ello, usaremos GetUsersInChannel de la api de Mattermost:

GetUsersInChannel(channelId, sortBy string, page, perPage int) ([]*model.User, *model.AppError)

Como veis necesitamos el id del canal que lo tenemos entre los argumentos que recibe ExecuteCommand en args.ChannelId. También tenemos que paginar, en este caso le pediremos la página 0 con 1000 usuarios para simplificar. El código quedaría así:

1
users, _ := p.API.GetUsersInChannel(args.ChannelId, "username", 0, 1000)

Ya tenemos todos los usuarios en la variable users pero entre ellos se pueden encontar bots; vamos a filtrarlos creando una función que se encargue de ellos:

1
2
3
4
5
6
7
8
9
10
11
func (p *Plugin) filterBots(users []*model.User) []*model.User {
var noBots []*model.User

for _, user := range users {
if !user.IsBot {
noBots = append(noBots, user)
}
}

return noBots
}

y la usamos:

1
2
users, _ := p.API.GetUsersInChannel(args.ChannelId, "username", 0, 1000)
users = p.filterBots(users)

En users ya tenemos un listado de usuarios libre de bots, ahora solo tenemos que elegir uno aleatorio y creamos un mensaje para mencionarle:

1
2
3
4
5
usersLen := len(users)
// Int, aleatorio entre 0 y el número de usuarios.
userIndex := rand.Intn(usersLen)
// Accedemos al usuario usando el indice aleatorio `users[userIndex]` y nos quedamos con su username
msg := "@" + users[userIndex].Username

A continuación, vamos a hacer que el bot escriba el mensaje de respuesta mencionando al usuario seleccionado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Rellenamos la información del post, usando los datos del canal actual, el bot id que guardamos anteriormente y el mensaje que acabamos de rellenar con el nombre de usuario.
post := &model.Post{
UserId: p.botUserID,
ChannelId: args.ChannelId,
RootId: args.RootId,
Message: msg,
}

// Creamos el post
_, createPostError := p.API.CreatePost(post)

if createPostError != nil {
return nil, model.NewAppError("ExecuteCommand", "error random-user", nil, createPostError.Error(), http.StatusInternalServerError)
}

// Respuesta al comando, en nuestro caso no necesitamos ninguna
return &model.CommandResponse{}, nil

También podemos añadir una pantalla de opciones a nuestro plugin.

Abrimos plugin.json y añadimos un radiobutton para elegir si queremos añadir @ en las menciones o no. Podemos ver opciones para los settings aquí

1
2
3
4
5
6
7
8
9
10
11
12
"settings_schema": {
"header": "",
"footer": "",
"settings": [
{
"key": "At",
"display_name": "Mención con @",
"type": "bool",
"default": true
}
]
}

Modificamos plugin.go para leer la configuración:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
config := p.getConfiguration()
at := config.At

usersLen := len(users)
userIndex := rand.Intn(usersLen)
username := users[userIndex].Username

msg := ""

if at {
msg = "@" + username
} else {
msg = username
}

Y añadimos el nuevo campo al modelo de la configuración en server/configuration.go:

1
2
3
type configuration struct {
At bool
}

Cuando subamos el plugin veremos algo así:

Ya tenemos el plugin listo, ahora si hacemos make y repetimos los pasos anteriores podremos ver el plugin en acción como hemos visto en el gif al inicio del tutorial.

Este es el código completo en plugin.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package main

import (
"math/rand"
"sync"

"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
)

type Plugin struct {
plugin.MattermostPlugin

configurationLock sync.RWMutex

configuration *configuration

botUserID string
}

func (p *Plugin) OnActivate() error {
bot := &model.Bot{
Username: "random-user",
DisplayName: "RandomUser",
}
botUserID, ensureBotErr := p.Helpers.EnsureBot(bot)

if ensureBotErr != nil {
return ensureBotErr
}

p.botUserID = botUserID

return p.API.RegisterCommand(&model.Command{
Trigger: "random-user",
AutoComplete: true,
})
}

func (p *Plugin) filterBots(users []*model.User) []*model.User {
var noBots []*model.User

for _, user := range users {
if !user.IsBot {
noBots = append(noBots, user)
}
}

return noBots
}

func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
users, _ := p.API.GetUsersInChannel(args.ChannelId, "username", 0, 1000)

users = p.filterBots(users)

config := p.getConfiguration()
at := config.At

usersLen := len(users)
userIndex := rand.Intn(usersLen)
username := users[userIndex].Username

msg := ""

if at {
msg = "@" + username
} else {
msg = username
}

post := &model.Post{
UserId: p.botUserID,
ChannelId: args.ChannelId,
RootId: args.RootId,
Message: msg,
}

p.API.CreatePost(post)

return &model.CommandResponse{}, nil
}

Para conocer qué más podéis hacer con los plugin de Mattermost os recomiendo que echéis un vistazo a la referencia de la API.

Y aquí tenéis un overview general de cómo hacer plugins con Mattermost.

Por último, un ejemplo más completo con distintas configuraciones. Por ejemplo, solo elegir un usuario que esté online, o un listado de usuarios.