Mattermost記事まとめ: https://blog.kaakaa.dev/tags/mattermost/

本記事について

Mattermostの統合機能アドベントカレンダーの第15日目の記事です。

本記事では、Mattermostの投稿にユーザーが操作できるセレクトボックスを追加するInteractive Message Menuの機能について紹介します。

Interactive Message Menu概要

Interactive Message Menuは、昨日紹介したInteractive Message Buttontと同様の機能です。

Interactive Message Buttonは、Mattermostの投稿にボタンを表示する機能でしたが、Interactive Message Menuでは、Mattermostの投稿にセレクトボックスを表示することができます。

Interactive Messageに関する公式ドキュメントは下記になります。

作成

Interactive Message Menuを含む投稿を作成する方法はInteractive Message Buttonの時とほぼ同じです。

Incoming Webhook(内向きのウェブフック)を使って作成する例は下記のようになります。

BODY='{
  "attachments": [{
    "title": "Echo text",
    "actions": [{
	  "id": "echo",
      "name": "Would you like to echo?",
      "integration": {
        "url": "http://localhost:8080/actions/echo",
        "context": {
          "text": "sample text"
		}
	  },
	  "type": "select",
	  "options": [{
		  "text": "Echo",
		  "value": "echo"
	  }, {
		  "text": "Reject",
		  "value": "reject"
	  }]
	}]
  }]
}'

curl \
  -H "Content-Type: application/json" \
  -d "$BODY"  \
  http://localhost:8065/hooks/ucw5qjw86jgeum77o1uw8197jr

上記のリクエストを実行すると、下記のようなセレクトボックスを含む投稿が作成されます。

first exmple

optionsに指定したオプションがセレクトボックスから選べるようになっています。

first example2

セレクトボックスから要素を選択すると、対応するオプションのvalueに指定した値を含むリクエストがIntegrationに指定したURLへ送信されることになります。

パラメータ

actionsフィールドに指定できるパラメータは、Interactive Message Buttonと同じPostAction構造体として宣言されています。 https://github.com/mattermost/mattermost-server/blob/master/model/integration_action.go#L37

昨日の記事にも載せましたが、再掲します。

この中で、Interactive Message Menuのみで使用できるパラメータはdata_source, options, default_optionになります。

optionsは、先ほどの例にも示したようにセレクトボックスで選択する要素を定義するフィールドです。 default_optionは、デフォルトで選択されているオプションを指定することができます。options内のいずれかのオプションのvalueの値を指定します。

data_sourceは特殊なオプションです。userschannelsを指定でき、これを指定するとセレクトボックスから選択できるオプションがそれぞれMattermost上のユーザーかMattermost上の公開チャンネルになります。data_sourceを指定した場合、optionsに指定されたオプションは無視されます。

data source

BODY='{
  "attachments": [{
    "title": "Echo text",
    "actions": [{
	  "id": "echo",
      "name": "Would you like to echo?",
      "integration": {
        "url": "http://localhost:8080/actions/echo",
        "context": {
          "text": "sample text"
		}
	  },
	  "type": "select",
	  "data_source": "channels"
	}]
  }]
}'

curl \
  -H "Content-Type: application/json" \
  -d "$BODY"  \
  http://localhost:8065/hooks/ucw5qjw86jgeum77o1uw8197jr

実行

今回は、slash commandの引数に指定したテキストをセレクトボックスで選択されたチャンネルへEchoするようなInteractive Message Menuを紹介します。

video

slash command、Interactive Message Menuからのリクエストを受け取るサーバーアプリのサンプルコードは下記のようになります。

(今回の例では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() {
	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 server",
				Actions: []*model.PostAction{{
					// (1) Echoメニュー
					Name: "Echo",
					Integration: &model.PostActionIntegration{
						URL: "http://localhost:8080/actions/echo",
						Context: map[string]interface{}{
							"text": r.Form.Get("text"),
						},
					},
					Type: model.POST_ACTION_TYPE_SELECT,
					Options: []*model.PostActionOptions{{
						Text:  "Echo",
						Value: "echo",
					}, {
						Text:  "Reject",
						Value: "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())
	})

	// (2) Echo Buttonが押されたときの処理
	http.HandleFunc("/actions/echo", func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		// (3) リクエストデータの読み出し
		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
		}

		selected, ok := payload.Context["selected_option"].(string)
		if !ok {
			resp := &model.PostActionIntegrationResponse{EphemeralText: "invalid request. Context['selected_option'] is not found."}
			fmt.Fprint(w, resp.ToJson())
			return
		}

		// (4) レスポンスの構築
		response := &model.PostActionIntegrationResponse{}
		switch selected {
		case "echo":
			response.Update = &model.Post{
				Message: text,
				Props:   model.StringInterface{},
			}
		case "reject":
			response.Update = &model.Post{Props: model.StringInterface{}}
			response.EphemeralText = "Echoing was rejected"
		default:
			response.EphemeralText = "invalid operation"
		}

		w.Header().Add("Content-Type", "application/json")
		io.WriteString(w, string(response.ToJson()))
	})

	http.ListenAndServe(":8080", nil)
}

(1)ではInteractive Message Menuの内容を定義しています。Integration.URLにリクエスト送信先としてhttp://localhost:8080/actions/echoを、Integration.Contextに、slash commandの引数として指定された文字列を保持しています。また、EchoRejectのオプションをoptionsに定義しています。

(2)で、セレクトボックスからオプションが選択された時に送信されるリクエストを処理しています。(3)で送信されたリクエストを読み出しています。送信されるリクエストの形式は、Interactive Message Menuから送信されるリクエストもInteractive Message Buttonと同じく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実行時の引数と、selected_optionをキーとして格納されている選択されたオプションを読み出しています。 そして、(4)で読み出したselected_optionの値に応じてレスポンスを構築しています。レスポンスの形式もInteractie Message Buttonと同じく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.
}

さいごに

本日は、Interactive Message Menuの使い方について紹介しました。 明日は、Interactive Dialogについて紹介します。

comments powered by Disqus