[Mattermost Integrations] Interactive Message Button
Mattermost 記事まとめ: https://blog.kaakaa.dev/tags/mattermost/
本記事について
Mattermost の統合機能アドベントカレンダーの第 14 日目の記事です。
本記事では、Mattermost の投稿にユーザーが操作できるボタンを追加する Interactive Message Button の機能について紹介します。
Interactive Message Button 概要
Interactive Message Button は、Mattermost の投稿にボタンを表示し、ボタンがクリックされるとどのボタンが押されたのかという情報がサーバーへ送信される機能です。
(画像は公式ドキュメントから)
Interactive Message に関する公式ドキュメントは下記になります。
作成
Interactive Message Button を含む投稿を作成するには、Message Attachments 機能と同様attachments
に Button に関する情報を追加します。
Incoming Webhook(内向きのウェブフック)を使って作成する例は下記のようになります。
BODY='{
"attachments": [{
"title": "Echo text",
"actions": [{
"id": "echo",
"name": "Echo",
"integration": {
"url": "http://localhost:8080/actions/echo",
"context": {
"text": "sample text"
}
}
}, {
"name": "Reject",
"style": "danger",
"integration": {
"url": "http://localhost:8080/actions/reject"
}
}]
}]
}'
curl \
-H "Content-Type: application/json" \
-d "$BODY" \
http://localhost:8065/hooks/ucw5qjw86jgeum77o1uw8197jr
上記のリクエストを実行すると、下記のようなボタンを含む投稿が作成されます。
リクエスト内容と見ると、actions
フィールドに含まれる下記の一つのオブジェクトが一つのボタンに関連していることが分かります。
{
"id": "echo",
"name": "Echo",
"integration": {
"url": "http://localhost:8080/actions/echo",
"context": {
"text": "sample text"
}
}
}
id
はボタンの ID、name
は投稿上に表示されるボタンのラベルを指定します。integration
内には、ボタンを押した時に送信されるリクエストの情報を記述しており、url
にはリクエスト送信先の URL を、context
にはリクエストに含まれる任意の情報を指定することができます。
ボタンが押された時に何か処理を行うには、リクエストを受け取るサーバーを用意する必要がありますが、それについては後述します。
パラメータ
actions
フィールドに指定できるパラメータは、Mattermost のサーバー側のコードにPostAction
構造体として宣言されています。
-
id
: 1 投稿内でユニークとなるボタンの ID を指定します。指定しない場合、Mattermost 側で一意の ID が付与されます。 -
type
: Interactive Message の種別を指定します。select
かbutton
を指定でき、何も指定しないとbutton
が指定されます。 -
name
: Mattermost 内でボタンのラベルとして表示される文字列を指定します。 -
disabled
: ボタンを無効化するオプションです。true
を指定するとボタンが押せなくなります。 -
style
: ボタンの色を指定できます。default
,primary
,success
,good
,warning
,danger
, もしくは任意の Hex color("#FF9900"
など)を指定できます。
Outgoing WebHook を利用するには、システムコンソール > 統合機能管理 > 外向きのウェブフックを有効にする の設定が有効
になっている必要があります。 -
data_source
: Interactive Message Buttonでは使用しません。Interactie Message Menuでのみ使用します。 -
options
: Interactive Message Buttonでは使用しません。Interactie Message Menuでのみ使用します。 -
default_option
: Interactive Message Buttonでは使用しません。Interactie Message Menuでのみ使用します。 -
integration
: アクションを実行した際に送信されるリクエストに関する情報を指定します。-
url
: リクエスト送信先の URL を指定します。 -
context
: 送信されるリクエストに含まれる追加情報を指定します -
integration
に指定した情報はリクエスト送信時にしか読み出されないため、(https などで通信経路のセキュリティが担保されていれば)アクセストークンのような秘密情報なども含めることができます。
-
実行
Interactive Message Button を機能させるには、ボタンが押された時に送信されるリクエストを処理するサーバーを立てておく必要があります。今回は、slash command の引数に指定したテキストを Echo するような Interactive Message Button について考えます。
slash command、Interactive Message Button からのリクエストを受け取るサーバーアプリのサンプルコードは下記のようになります。
(今回の例では slash command、Interactive Message Button 共に、Mattermost からlocalhost
に対してリクエストが送信されるため、システムコンソール > 開発者 > 信頼されていない内部接続を許可するの設定に、リクエスト送信先のサーバーを記述しておく必要があります。詳しくは公式ドキュメントを参照ください。)
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/mattermost/mattermost-server/v5/model"
)
func main() {
// (1) Interactive Message Button作成用slash command
http.HandleFunc("/command", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
response := model.CommandResponse{
ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
Attachments: []*model.SlackAttachment{{
Title: "Echo text",
Actions: []*model.PostAction{{
// (2) Echoボタン
Id: "echo",
Name: "Echo",
Integration: &model.PostActionIntegration{
URL: "http://localhost:8080/actions/echo",
Context: map[string]interface{}{
"text": r.Form.Get("text"),
},
},
}, {
// (3) Rejectボタン
Name: "Reject",
Style: "danger",
Integration: &model.PostActionIntegration{
URL: "http://localhost:8080/actions/reject",
},
}},
}},
}
// Need to set header. if not, just json string will be posted.
w.Header().Add("Content-Type", "application/json")
io.WriteString(w, response.ToJson())
})
// (4) Echo Buttonが押されたときの処理
http.HandleFunc("/actions/echo", func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// (5) リクエストデータの読み出し
b, _ := ioutil.ReadAll(r.Body)
var payload model.PostActionIntegrationRequest
json.Unmarshal(b, &payload)
text, ok := payload.Context["text"].(string)
if !ok {
resp := &model.PostActionIntegrationResponse{EphemeralText: "invalid request. Context['text'] is not found."}
fmt.Fprint(w, resp.ToJson())
return
}
// (6) レスポンスの構築
response := &model.PostActionIntegrationResponse{
Update: &model.Post{
Message: text,
Props: model.StringInterface{},
},
}
w.Header().Add("Content-Type", "application/json")
io.WriteString(w, string(response.ToJson()))
})
// (7) Rejectボタンが押されたときの処理
http.HandleFunc("/actions/reject", func(w http.ResponseWriter, r *http.Request) {
// (8) レスポンスの構築
response := &model.PostActionIntegrationResponse{
Update: &model.Post{
Props: model.StringInterface{},
},
EphemeralText: "Echoing was rejected.",
}
w.Header().Add("Content-Type", "application/json")
io.WriteString(w, string(response.ToJson()))
})
http.ListenAndServe(":8080", nil)
}
(1)
は slash command のリクエストを処理するロジックです。http://localhost:8080/command
へリクエストを送信する slash command を事前に登録しておく必要があります。slash command の登録方法については 6 日目の記事を参照ください。
(2)
では、slash command 実行時の引数情報を Echo するEchoボタンを定義しています。Integration.URL
にリクエスト送信先としてhttp://localhost:8080/actions/echo
を、Integration.Context
に、slash command の引数として指定された文字列を保持しています。(3)
では、Echo 処理を注意するためのRejectボタンを定義しています。リクエスト送信先はhttp://localhost:8080/actions/reject
にしてあります。また、Rejectボタンには"style": "danger"
をしているため、ボタンが赤く表示されます。
slash command を実行すると、下記のような投稿が作成されます。
(4)
で、Echoボタンが押された時に送信されるリクエストを処理しています。まず、(5)
で送信されたリクエストを読み出しています。Interactive Message Button から送信されるリクエストは Mattermost のコードではPostActionIntegrationRequest
として定義されています。
type PostActionIntegrationRequest struct {
UserId string `json:"user_id"`
UserName string `json:"user_name"`
ChannelId string `json:"channel_id"`
ChannelName string `json:"channel_name"`
TeamId string `json:"team_id"`
TeamName string `json:"team_domain"`
PostId string `json:"post_id"`
TriggerId string `json:"trigger_id"`
Type string `json:"type"`
DataSource string `json:"data_source"`
Context map[string]interface{} `json:"context,omitempty"`
}
今回はContext
内にtext
をキーとして slash command 実行時の引数が格納されているため、その文字列を読み出しています。
そして、(6)
で読み出した文字列を使ってレスポンスを構築しています。レスポンスの形式は Mattermost のコードでPostActionIntegrationResponse
として定義されています。
type PostActionIntegrationResponse struct {
Update *Post `json:"update"`
EphemeralText string `json:"ephemeral_text"`
SkipSlackParsing bool `json:"skip_slack_parsing"` // Set to `true` to skip the Slack-compatibility handling of Text.
}
PostActionIntegrationResponse
のUpdate
フィールドに指定したPost
のインスタンスで、Interactive Message Button を置き換えることができます。今回は slash command の引数として与えられた文字列をメッセージに持つ投稿に置き換えています。Props: model.StringInterface{},
でPost
のProps
を空にしていますが、これをしないと Interactive Message Button がずっと残り続けてしまうため、ボタンを削除したい場合は必ず空に設定する必要があります。Mattermost v5.10 以前では、明示的にProps
を空にしなくても Interactive Message Button が削除されていましたが、Mattermost v5.11 以上では、明示的に空に設定しないと削除されないようになったようです。(https://docs.mattermost.com/developer/interactive-messages.html#how-do-i-manage-properties-of-an-interactive-message)
(7)
は、Rejectボタンが押されたときの処理です。(8)
でレスポンスを構築していますが、ボタンが押された時に Interactive Message Button を含む投稿を削除したい場合は、このようにProps
を空に設定しただけのPost
インスタンスを指定します。EphemeralText
に指定した文字列は、ボタンを押した人だけに見えるメッセージになります。
さいごに
本日は、Interactive Message Button の使い方について紹介しました。
明日は、Interactive Message Menu について紹介します。
Discussion