Mattermost のカスタムPlugin を開発してみる

## 1.はじめに

自社製品をバックエンドとして、Mattermostでチャットボットを開発する必要があり調査したところ、以下の選択肢があることがわかりました。

1. [Outgoing Webhook](https://developers.mattermost.com/integrate/webhooks/)
特定のチャンネルで特定のトリガーワードが投稿された際に、外部のURLにHTTP POSTリクエストを送信できる
特徴:
   - トリガー条件: 指定したチャンネルでの投稿、または特定のトリガーワードに一致する投稿
   - 利用可能なチャンネル: パブリックチャンネルのみ
   - プライベートチャンネルやダイレクトメッセージでは使用不可

1. [スラッシュコマンド](https://developers.mattermost.com/integrate/slash-commands/)
ユーザーがチャット入力欄で「/」から始まるコマンドを入力することで、特定のアクションを実行できる
特徴:
   - トリガー方法: ユーザーが手動でコマンドを入力
   - 利用可能なチャンネル: パブリックチャンネル、プライベートチャンネル、ダイレクトメッセージ全てで使用可能

1. [プラグイン](https://developers.mattermost.com/integrate/plugins/)
Mattermostの機能を拡張するためのモジュールで、サーバーサイドやクライアントサイドで動作する
特徴:
   - カスタマイズ性: 独自の機能やUIコンポーネントを追加可能
   - 複雑なビジネスロジックの実装や、UIのカスタマイズが必要な場合に適している

当初はOutgoing Webhookを利用することを検討していましたが、プライベートチャンネルやダイレクトメッセージでの利用ができないため、スラッシュコマンドを検討しました。しかし、スラッシュコマンドは送信先にContent-Type: application/x-www-form-urlencoded でリクエストを送信するため、JSON形式のリクエストを送信することができないという制約がありました。そのため、プラグインを利用することにしました。

本記事では、Mattermostプラグインを開発する際の手順について紹介します。

## 2. 開発環境とツールの準備

まず、開発においてはMattermostが提供する[Plugin Starter Template](https://github.com/mattermost/mattermost-plugin-starter-template)を使用しました。このテンプレートはMattermost用プラグインの構造を備えており、必要な機能を追加するだけで迅速に開発を進めることができます。

開発環境：Go言語、Node.js v16 、npm v8

## 3. プラグイン開発

### 1. プラグインの構造

テンプレートの基本構造は以下の通りです。

```
├── Makefile
├── README.md
├── assets
├── bin
├── build
├── core
├── dist
├── plugin.json
├── public
├── server
└── webapp
```

今回は、サーバーサイドの開発となるため、`server`ディレクトリ内の`plugin.go` に機能を実装しました。

### 2. 機能の実装

今回実装したい機能は、`特定のワード(Botアカウントへのメンションを想定)` を含むメッセージが投稿された際に、自社製品のエンドポイントに投稿されたメッセージを送信するというものです。その際、メッセージにもともと含まれていないUser名等の情報をAPIで取得してメッセージに追加します。

#### 1. 設定の読み込みとバリデーション
プラグインの設定は、Mattermost管理画面で設定可能です。このプラグインでは、以下の設定を使用します。
- ExternalServerURL: 外部サーバーのURL
- TriggerKeyword: 検出するキーワード
`OnConfigurationChange`メソッドで設定変更を検知し、バリデーションも同時に行っています。

```go
func (p *Plugin) OnConfigurationChange() error {
	var configuration = new(configuration)

	err := p.API.LoadPluginConfiguration(configuration)
	if err != nil {
	return err
	}

	// バリデーション
	if configuration.ExternalServerURL == "" {
	return errors.New("ExternalServerURL is required")
	}
	if configuration.TriggerKeyword == "" {
	return errors.New("TriggerKeyword is required")
	}

	p.configurationLock.Lock()
	defer p.configurationLock.Unlock()

	p.configuration = configuration

	return nil
}
```

#### 2. キーワードの検出
  `MessageHasBeenPosted`メソッドで、Mattermostに投稿されたメッセージを監視し、特定のキーワードが含まれるかどうかをチェックします。キーワードが含まれる場合には、`sendDataToExternalServer`メソッドを非同期で実行し、外部サーバーへデータを送信します。

```go
func (p *Plugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) {
	message := post.Message
	config := p.getConfiguration()
	triggerKeyword := config.TriggerKeyword

	if triggerKeyword == "" {
	p.API.LogError("TriggerKeyword is not configured", "user_id", post.UserId, "channel_id", post.ChannelId)
	return
	}

	if strings.Contains(strings.ToLower(message), strings.ToLower(triggerKeyword)) {
	go p.sendDataToExternalServer(post)
	}
}
```

#### 3. メッセージの編集
  `sendDataToExternalServer`内で、外部サーバーに送信するデータを構築します。この際、MattermostのAPIを使用して、ユーザー名やチャンネル名などの情報を取得し、メッセージに追加します。

```go
	// ユーザー名を取得
	user, _ := p.API.GetUser(post.UserId)
	userName := user.Username

	// Channel を取得
	channel, _ := p.API.GetChannel(post.ChannelId)
	teamID := channel.TeamId

	// 送信するデータを構築
	data := Payload{
			UserID:    post.UserId,
			UserName:  userName,
			ChannelID: post.ChannelId,
			Text:      post.Message,
			Timestamp: post.CreateAt,
			PostID:    post.Id,
			RootID:    post.RootId,
			TeamID:    teamID,
	}
```

#### 4. plugin.jsonの設定
Mattermostのプラグインにはplugin.jsonという構成ファイルがあり、プラグインのメタデータや依存関係、設定を定義します。
##### 基本情報

```json
	{
	"id": "jp.vantiq.mattermost.plugin",
	"name": "Vantiq Plugin",
	"description": "Vantiq Plugin for Mattermost",
	"homepage_url": "https://github.com/mattermost/mattermost-plugin-starter-template",
	"support_url": "https://github.com/mattermost/mattermost-plugin-starter-template/issues",
	"icon_path": "assets/starter-template-icon.svg",
	"min_server_version": "6.2.1",
	...
	}
```

- id: プラグインの一意の識別子
- name: プラグインの名称。Mattermost管理画面で表示される
- description: プラグインの簡単な説明
- homepage_url: プラグインのホームページURL
- support_url: サポートURL
- icon_path: プラグインのアイコンファイルパス
- min_server_version: プラグインが動作する最低限のMattermostサーバーバージョン
  
##### 設定スキーマ

プラグインの設定項目は、Mattermost の管理画面から設定できるようになります。settings_schema セクションで定義された情報をもとに、設定画面が生成されます。

```json
"settings_schema": {
"header": "Plugin Configuration",
"footer": "",
"settings": [
	{
	"key": "ExternalServerURL",
	"display_name": "External Server URL",
	"type": "text",
	"help_text": "The URL of the external server to send data to.",
	"placeholder": "https://vantiq.com/endpoint",
	"default": ""
	},
	...
]
}
```

- settings_schema: プラグインの設定項目の定義
    - settings: 個々の設定項目の配列
        - key: 設定項目のキー (設定値を識別するための文字列)
        - display_name: 設定項目の表示名 (ユーザーインターフェースに表示される名前)
        - type: 設定項目のタイプ ("text" や "boolean" など)
        - help_text: 設定項目の説明テキスト (ユーザーが設定の意味を理解するためのヘルプ)
        - placeholder: 設定項目の入力欄のプレースホルダーテキスト (ユーザーに適切な入力値を促す)
        - default: 設定項目のデフォルト値

## 4. ビルドとデプロイ
`make` コマンドを実行するだけでプラグインがビルドされ、`dist` ディレクトリにプラグインのtar.gzファイルが生成されます。生成されたファイルをMattermostのプラグイン管理画面からアップロードすることで、プラグインをデプロイできます。
管理者設定でプラグインを有効化し、必要な設定項目（例えばエンドポイントやトリガーワード）を入力することで、プラグインが動作します。