Mattermost 記事まとめ: https://blog.kaakaa.dev/tags/mattermost/
本記事について
Mattermost の統合機能アドベントカレンダーの第 19 日目の記事です。
本記事では、Mattermost Server Plugin の開発に利用できる API について紹介します。
Mattermost Plugin についての公式ドキュメントは下記になります。 https://developers.mattermost.com/extend/plugins/overview/
Mattermost Plugin API
Mattermost Server Plugin の開発に利用できる API の一覧は下記にあります。
https://developers.mattermost.com/extend/plugins/server/reference/
ユーザーやチャンネル、投稿などの操作については REST API とほぼ同様の機能を有しています。数が多いため全ては紹介しませんが、Server Plugin 特有の処理に関する API を紹介していこうと思います。
GetConfig
GetConfig
は、Mattermost Server のシステムコンソールの設定情報を取得します。
siteURL := p.API.GetConfig().ServiceSettings.SiteURL
p.API.GetConfig()
下記のOnConfigurationChange
内で実行し、Plugin 構造体のフィールドとして保持しておくのが良いそうです。
https://github.com/mattermost/mattermost-plugin-starter-template/blob/master/server/configuration.go
OpenInteractiveDialog
OpenInteractiveDialog
は、第 16 日目の記事でも紹介した Interactive Dialog を Plugin から開くための API です。Interactive Dialog を開くにはTriggerId
というパラメータが必須であり、TriggerId
は Slash Command 実行時、もしくは Interactive Message Button/Menu 実行時に送信されるリクエストからしか取得できません。
以下の例は Slash Command 実行時に Interactive Dialog を開き、入力された情報を整形して投稿を作成するコードです。
func (p *SamplePlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
appErr := p.API.OpenInteractiveDialog(model.OpenDialogRequest{
TriggerId: args.TriggerId,
URL: fmt.Sprintf("%s/plugins/com.github.kaakaa.mattermost-plugin-starter-template/callback", *p.API.GetConfig().ServiceSettings.SiteURL),
Dialog: model.Dialog{
Title: "Sample Plugin Dialog",
SubmitLabel: "Submit",
Elements: []model.DialogElement{
{
DisplayName: "タイトル",
Name: "title",
Placeholder: "Title",
Type: "text",
},
{
DisplayName: "Snippet",
Name: "snippet",
Type: "textarea",
},
},
},
})
if appErr != nil {
return nil, appErr
}
return &model.CommandResponse{}, nil
}
Interactive Dialog のリクエスト送信先を
URL: fmt.Sprintf("%s/plugins/com.github.kaakaa.mattermost-plugin-starter-template/callback", *p.API.GetConfig().ServiceSettings.SiteURL),
のようにすることで、Plugin から開いた Interactive Dialog のリクエストを Plugin で処理することもできます。このリクエストの受信先として、Mattermost Plugin Server Hook であるServeHTTP
を使って/callback
のエンドポイントを受け取る処理を追加します。
func (p *SamplePlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/callback" {
defer r.Body.Close()
request := model.SubmitDialogRequestFromJson(r.Body)
t := request.Submission["title"]
s := request.Submission["snippet"]
p.API.CreatePost(&model.Post{
UserId: request.UserId,
ChannelId: request.ChannelId,
Message: fmt.Sprintf("## %s\n\n```\n%s\n```", t, s),
})
return
}
fmt.Fprint(w, "Hello, world!")
}
このようにすることで、Interactive Dialog の起動からリクエストの処理までを Plugin だけで完結させることができます。
PublishWebSocketEvent
PublishWebSocketEvent
は、プラグイン独自の WebSoket イベントを送信する API です。
PublishWebSocketEvent
は、3 つの引数を取ります。
event string
: WebSocket イベント名を指定します。実際に送信される WebSocket イベントには接頭辞としてcustom_<PluginID>_
が付与されますpayload map[string]interface{}
: 送信されるデータの内容を指定しますbroadcast *model.WebsocketBroadcast
: WebSocket を送信する対象を指定しますmodel.WebsocketBroadcast
で、ユーザー、チャンネル、チームを指定したり、受信対象から外すユーザーを指定したりできます
Webapp Plugin API のregisterWebSocketEventHandler
と組み合わせることで、プラグイン内で WebSocket イベントに関する処理を完結させることができます。
この辺りの使用例は第 22 日目以降の記事で紹介する予定です。
SendMail
SendMail
は、Mattermost Plugin から HTML メールを送信する API です。システムコンソールで SMTP の設定が完了している必要があります。
Slash Command を実行した際にメールを送信する例は以下のようになります。
func (p *SamplePlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
appErr := p.API.SendMail(
"test@example.com",
"Sample Title",
"<h1>Mail from plugin</h1><div><p>Sample Mail</p></div>")
if appErr != nil {
return nil, appErr
}
return &model.CommandResponse{}, nil
}
KVGet / KVSet
KVGet
, KVSet
は、プラグインごとに割り当てられた Key Value ストアから値を取得、または値を設定する API です。Key Value ストアには[]byte
型のデータを格納できるため、格納用データの構造体を作成し、json.Marshal
で[]byte
型のデータ化したものを出し入れするのが主な使い方になるのではないかと思います。
Key Value ストアを使用してカウンターを実装した例が以下になります。
type counter struct {
Count int `json:"count"`
CreatedAt string `json:"created_at"`
}
func (p *SamplePlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
// read data
b, appErr := p.API.KVGet("counter")
if appErr != nil {
return nil, appErr
}
var ct counter
if b == nil {
// init if data is not found
ct = counter{
Count: 0,
CreatedAt: time.Now().Format(time.RFC3339),
}
} else {
if err := json.Unmarshal(b, &ct); err != nil {
return nil, model.NewAppError("where", "id", nil, err.Error(), http.StatusInternalServerError)
}
}
// count up
ct.Count = ct.Count + 1
b, err := json.Marshal(ct)
if err != nil {
return nil, model.NewAppError("where", "id", nil, err.Error(), http.StatusInternalServerError)
}
// set data
if appErr := p.API.KVSet("counter", b); appErr != nil {
return nil, appErr
}
return &model.CommandResponse{Text: fmt.Sprintf("counter: %d", ct.Count)}, nil
}
Key Value ストアを操作する API はKVGet
, KVSet
以外にも数多くあります。データを削除するKVDelete
や、プラグイン内で保存済みのデータのkey
を取得するKVList
、期間を指定して値を格納できるKVSetWithExpiry
などがあります。また、同時にKVSet
が実行された場合に不整合が起きないようにするため、Atomic な Key Value ストアの操作を強制するためのKVCompareAndSet
などもあります。
API の一覧については公式ドキュメントを参照してください。 https://developers.mattermost.com/extend/plugins/server/reference/
Helpers
Mattermost Plugin API は数多くあるため、Mattermost に対するほとんどの操作を実行することはできますが、より簡単に Plugin API を実行するための Helper 関数がいくつか存在します。
ここでは、Helper 関数のうち一部を紹介します。
Helper 関数の一覧は下記の公式ドキュメントにあります。 https://developers.mattermost.com/extend/plugins/server/reference/#Helpers
KVGetJSON / KVSetJSON
先ほどのKVGet
、KVSet
のコードでは、構造体を[]byte
に変換するためにKVSet
を呼ぶ前にjson.Marshal
を、KVGet
を呼んだ後にjson.Unmarshal
を呼んでいましたが、KVGetJSON
、KVSetJSON
を使用することで、その必要がなくなります。
そのため、先ほどの処理を多少すっきりと書くことができます。
type counter struct {
Count int `json:"count"`
CreatedAt string `json:"created_at"`
}
func (p *SamplePlugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
// read data
var ct counter
exists, err := p.Helpers.KVGetJSON("counter", &ct)
if err != nil {
return nil, model.NewAppError("where", "id", nil, err.Error(), http.StatusInternalServerError)
}
if !exists {
ct = counter{
Count: 0,
CreatedAt: time.Now().Format(time.RFC3339),
}
}
// count up
ct.Count = ct.Count + 1
// set data
if appErr := p.Helpers.KVSetJSON("counter", ct); appErr != nil {
return nil, model.NewAppError("where", "id", nil, err.Error(), http.StatusInternalServerError)
}
return &model.CommandResponse{Text: fmt.Sprintf("counter: %d", ct.Count)}, nil
}
CheckRequiredServerConfiguration
CheckRequiredServerConfiguration
は、システムコンソールの設定をチェックするための Helper 関数です。
引数に指定したmodel.Config
と同じ設定になっているかをチェックし、異なる設定だった場合はfalse
を返します。
システムコンソール > Bot アカウント > Bot アカウントの作成を有効にするの設定が有効になっていない場合にプラグインの起動を停止するような例は以下のようになります。
func toPtr(v bool) *bool {
return &v
}
func (p *SamplePlugin) OnActivate() error {
b, err := p.Helpers.CheckRequiredServerConfiguration(&model.Config{
ServiceSettings: model.ServiceSettings{
EnableBotAccountCreation: toPtr(true),
},
})
if err != nil {
return err
}
if !b {
return errors.New("EnableBotAccountCreation must be true.")
}
...
プラグイン起動時、サーバーのログには以下のようなログが出力されます。
2020-12-13T00:16:01.409+0900 error mlog/log.go:229 Unable to restart plugin on upgrade. {"path": "/api/v4/plugins", "request_id": "u31bzwuyhbyibratzznjxgxzrr", "ip_addr": "::1", "user_id": "87x93uo8pfnzdro9ktcmobpa1r", "method": "POST", "err_where": "installExtractedPlugin", "http_code": 500, "err_details": "EnableBotAccountCreation must be true."}
さいごに
本日は、Mattermost Plugin の Server API について紹介しました。 明日からは、Mattermost Plugin のWebappサイドの実装について紹介します。