PowershellでMattermostAPIを叩いて複数チャンネルに定型文を送るタスクを簡略化

現在参画中の案件でやたらと同じ定型文を複数チャンネルに送る業務があるので、APIを使って投稿することにして手作業を減らすことにしました。
この手のちょっとしたツールをPythonを使って作るという情報はネットを探せばその辺に落ちている印象ですが、**Pythonはインストールしないと使えない**という点がセキュリティ()の厳しい現場とはミスマッチです。なので今回はPowershellでMattermostAPIを使ってメッセージを複数チャンネルに送信する方法についてまとめました。

結論から言うと、
1. `/users/login`エンドポイントでアクセストークンを取得する
2. `/users/me/teams/チームID/channels`エンドポイントを検索して投稿したいチャンネルのIDを特定する
3. `/posts`エンドポイントにPOSTリクエストを送ってメッセージを投稿する

という段階を踏むことでPowershellを使ってMattermostAPIを叩いてメッセージを投稿するということが実現できます。

# 処理の流れ
今回は`messages`というフォルダの中に送信したい定型文(`post.txt`)を配置することにします。
やりたいこととしては、`autopost.ps1`を実行すると、`post.txt`の内容でMattermostに投稿されるというものです。
`getAccessToken.ps1`や`
```
> tree /f > .\tree.txt
C:.
│  accesstoken.txt
│  autopost.ps1
│  getAccessToken.ps1
│  getAccountInfo.ps1
│  getChannelInfo.ps1
│  
└─messages
        post.txt
```

環境構築に使ったスクリプトです。
```powershell:powershell
mkdir messages
new-item ./messages/post.txt
new-item getAccessToken.ps1
new-item getChannelInfo.ps1
new-item getAccountInfo.ps1
new-item autopost.ps1
new-item accesstoken.txt
```

送信したい定型文は以下の内容とします。
```txt:post.txt
# 5/19(月) サーバー利用禁止時間のお知らせ
@channel
本日5/19(月)は、13:00-16:00の間でサーバーの利用を禁止します。
ご利用予定の方は11:00までに申請をお願いします。
```


# API経由でログインするには
API経由でログインするにはMattermostの統合機能からパーソナルアクセストークンを予め発行して利用する場合(①)と、`/users/login`エンドポイントにリクエストを送り、受け取ったセッション用のトークンを利用する方法(②)があります。
アクセストークンを発行した後は、パーソナルアクセストークンを利用する場合と`/users/login`エンドポイントで認証した場合とで使い方に変わりはなく、発行されたトークンをヘッダーに付与してリクエストを作成するという点は同じです。

## ①パーソナルアクセストークンによる認証
アクセストークンを発行する方法については、[こちら](https://zenn.dev/kaakaa/articles/qiita-20201210-9931449346fca68940ab)の記事で詳しく解説している方がいらっしゃいました。基本的にはこちらで記載されている手順で自分のIDに対してパーソナルアクセストークンを発行して、`accesstoken.txt`に保存しておけば良いです。
（`accesstoken.txt`というファイルに保存しなければならないというわけではなく、単に使い勝手の問題なので、Powershellの内部にべた書きで書いてしまっても良いと思います。べた書きするとGitHubなどでソース管理する時に良くないよねとかは除いて･･････）

①の方法は、管理者アカウントでMattermostにログインし、自身のアカウントにアクセストークンの発行許可を出すということを意図的にやらなければなりません。場合によっては、あなたは管理者アカウントのログイン情報を知らなかったり、現場の上長が管理者アカウントの情報を管理している場合もあるかと思いますが、冒頭でも説明した通り、セキュリティ()が厳格な現場では①の手順で進めようと思っても許可が出ないかもしれません。そんな時は次に紹介する②の方法で認証します。


## ②/users/loginエンドポイントでの認証
https://developers.mattermost.com/api-documentation/#/operations/Login
`/user/login`エンドポイントで認証をするためには、`http://your-mattermost-server/api/v4/users/login`宛てのリクエストのBodyにログインIDとパスワードを指定して、Postメソッドでリクエストを送信します。
この時のログインIDとパスワードとは、Webブラウザやデスクトップアプリの画面からログインする時のIDやパスワードと同じです。


```powershell:getAccessToken.ps1
$userID = "xxxxxxx@example.com"
$password = "password"

$body = @{
    "login_id" = $userID
    "password" = $password
}

$response = Invoke-RestMethod -Uri "http://your-mattermost-server/api/v4/users/login" -Method Post -Body $body
$response | Out-File -FilePath ".\response.log" -Encoding utf8 -Append    # レスポンスをresponse.logに保存
$response.token | Out-File -FilePath ".\accesstoken.txt" -Encoding utf8   # アクセストークンをaccesstoken.txtに保存
```

実際に利用する場合は`$userID`, `$password`にはご自身のアカウント情報を指定してください。
認証が成功すると`Invoke-RestMethod`のレスポンスには`token`が含まれていて、以降はこのトークンを使いまわすことになるので、`accesstoken.txt`にその値を保存しています。

①または②の方法でトークンを取得できればAPI経由でのログインに成功したことになるので、これ以降はアクセストークンが`accesstoken.txt`に保存されている前提での説明に移ります。

```txt:(例)accesstoken.txt
XXXXXXXXXXXXXXXXXXXXX
```


# 投稿したいチャンネルのIDを特定する
アクセストークンを取得したら、いよいよAPI経由でメッセージを投稿するための材料を揃えていく作業になります。
投稿したいチャンネルが管理されているチームのチームIDを特定したら、
`users/me/teams/チームID/channels`というエンドポイントにリクエストしてください。

```powershell:getChannelInfo.ps1
# チームID
$teamId = "your_team_id"

# エンドポイントURL
$endpoint = "http://your-mattermost-server/api/v4/users/me/teams/$teamId/channels"

# アクセストークンを取得
$accessToken = Get-Content -Path ".\accesstoken.txt"

# リクエストヘッダーの定義
$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type" = "application/json"
}

# APIリクエスト
$response = Invoke-RestMethod -Uri $endpoint -Method Get -Headers $headers

# 結果を表示
$response | ConvertTo-Json | Out-File -FilePath ".\channels.json" -Encoding utf8 -Append
```

`channels.json`で受け取った中身を見ると、
自分が招待されているチャンネルのIDの他にダイレクトメッセージのIDも取得できていると思いますが、その中から自分が投稿したいチャンネルの`id`を探してください。
`display_name`というプロパティにMattermostのUI上で表示されている名前が記録されているので、それを手がかりに特定できると思います。

### ※チームIDの確認の仕方

チームIDは、WebブラウザでMattermostのシステムコンソールを開いたときのURLに含まれています。
`http://your-mattermost-server/admin_console/user_management/teams/`に移動すると、
サーバーで管理されているMattermostチームの一覧が確認できると思いますが、その中の一つを選択して画面遷移すると、以下のようなURLに移動したとします。
```
http://your-mattermost-server/admin_console/user_management/teams/ABCABCABCABCABCABC
```

上記の例の場合、`ABCABCABCABCABCABC` がチームIDです。


# APIでメッセージを送信する
https://developers.mattermost.com/api-documentation/#/operations/CreatePost

いよいよAPIで宛先のチャンネルにメッセージを投稿します。メッセージを投稿するには`/posts`エンドポイント宛てにPostメソッドでリクエストを送信します。

```powershell:autopost.ps1
# MattermostサーバーのURL
$serverUrl = "http://your-mattermost-server/api/v4"

# アクセストークン
$accessToken = Get-Content -Path ".\accesstoken.txt"

# 投稿先のチャンネルID
$channelId = "投稿したいチャンネルのID"

$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type" = "application/json"
}

$message = Get-Content -Path ".\messages\post.txt" -Raw

$body = @{
    channel_id = $channelId
    message    = $message
} | ConvertTo-Json

$response = Invoke-RestMethod -Uri "$serverUrl/posts" -Method Post -Headers $headers -Body $body
$response | Out-File -FilePath ".\response.log" -Encoding utf8 -Append
```

これで、MattermostAPIを叩いてメッセージを投稿するという当初の目的は達成できるのですが、せっかくなのでもう一工夫したいですよね。毎日投稿するようなメッセージであれば、日付が自動的に更新されてほしいものです。
そこで、`post.txt`を次のように修正します。
```diff:post.txt
- # 5/19(月) サーバー利用禁止時間のお知らせ
+ # {{day}}({{date}}) サーバー利用禁止時間のお知らせ
@channel
- 本日5/19(月)は、13:00-16:00の間でサーバーの利用を禁止します。
+ {{day}}({{date}})は、13:00-16:00の間でサーバーの利用を禁止します。
ご利用予定の方は11:00までに申請をお願いします。
```

txtファイルの中で日付のように動的に内容が変わってほしい箇所を意図的に`{{}}`で囲ってしまいます。
次に投稿用のPowershellを次のように修正します。
```diff_powershell:autopost.ps1
# MattermostサーバーのURL
$serverUrl = "http://your-mattermost-server/api/v4"

# アクセストークン
$accessToken = Get-Content -Path ".\accesstoken.txt"

# 投稿先のチャンネルID
$channelId = "投稿したいチャンネルのID"

$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type" = "application/json"
}

+ $weekdayMap = @{
+     Sunday = "日"
+     Monday = "月"
+     Tuesday = "火"
+     Wednesday = "水"
+     Thursday = "木"
+     Friday = "金"
+     Saturday = "土"
+ }
+ $date = $((Get-Date).ToString('M/dd'))
+ $day = $weekdayMap[(Get-Date).DayOfWeek.ToString()]


- $message = Get-Content -Path ".\messages\post.txt" -Raw
+ $message = Get-Content -Path ".\messages\post.txt" -Raw
+ $message = $message -replace "{{date}}", $date
+ $message = $message -replace "{{day}}", $day

$body = @{
    channel_id = $channelId
    message    = $message
} | ConvertTo-Json

$response = Invoke-RestMethod -Uri "$serverUrl/posts" -Method Post -Headers $headers -Body $body
$response | Out-File -FilePath ".\response.log" -Encoding utf8 -Append
```

修正後のpost.txtで`{{date}}`や`{{day}}`のように動的に変わってほしい箇所を書き換えていたので、Powershell側では動的に変わって欲しい箇所を置換する処理を加えています。これで毎日内容が変わる日付や時間なども、いちいち手修正せず自動的に変わってくれるようになります。

## 自分のダイレクトメッセージ向けにメッセージを投稿するには

自分宛てのDMを送るには、まずMattermost上での自分のIDを特定する必要があります。
自分のIDを特定するためには、`/users/me`エンドポイントにリクエストを送って、自分のアカウントの情報を取得します。
```powershell:getMyId.ps1
$serverUrl = "http://your-mattermost-server/api/v4"

# アクセストークン
$accessToken = Get-Content -Path ".\accesstoken.txt"

$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Content-Type" = "application/json"
}

$response = Invoke-RestMethod -Uri "$serverUrl/users/me" -Method Get -Headers $headers
$response | ConvertTo-Json | Out-File -FilePath ".\myInfo.json" -Encoding UTF8 -Append
```

`myInfo.json`の中身を見てみると、様々な自分のアカウントの情報が取れていることがわかると思いますが、`id`プロパティの値があなたのアカウントのidになるので、これを控えておいてください。
以降は、`myId.txt`というファイルから取得することにします。

```txt:myId.txt
abcabcabcabcabcabcabcabcabcabcabcab
```

次に、既に取得した`channels.json`を見てください。
`channels.json`には特定のチャンネルの情報の他に、あらゆるダイレクトメッセージのIDも含まれていて、自分宛てのダイレクトメッセージのIDも含まれています。

ダイレクトメッセージの`name`属性は、`[自分のID]__[相手のID]`または`[相手のID]__[自分のID]`で表現されています。したがって、`name`属性が`[自分のID]__[自分のID]`となっているデータがあれば、それが自分宛てのダイレクトメッセージの情報なので、そのデータのid属性をテキストエディタ上でgrepするなどして探してください。
実行例として挙げた`myId.txt`のようなidが自分のアカウントのIDだとすると、`name`属性が`abcabcabcabcabcabcabcabcabcabcabcab__abcabcabcabcabcabcabcabcabcabcabcab`となっているデータのidが自分宛てのダイレクトメッセージのidです。

そのidを、既に紹介した投稿用のスクリプトのBodyの`channel_id`に指定すれば、自分宛てのDMにメッセージを送信することができるので、一斉送信する前のテストなどに活用することができます。


# さいごに
PowershellでMattermostに投稿する方法について解説しました。
今回紹介したコードが最適解ではないかもしれませんが、現場の環境やポリシーに合わせて調整しやすいのがスクリプトの良いところだと思います。

投稿作業をPowershellに落とし込むことができたので、あとはタスクスケジューラに登録すれば、定期的に決まったメッセージを送る業務を完全に自動化することもできます。
同じ業務を繰り返す作業に疲弊している方の参考になれば幸いです。