Teams通知の実装を調査した話

## 背景

あるWebアプリで、Webアプリ上のイベントをビジネスSNSに通知する機能の要望があり、実現性を調査することになりました。

対象となったのはSlackとTeamsです。

Slackは実装例が豊富で、比較的容易に実現性の確認ができました。
一方Teamsはかなり情報が少なく、本家の重厚なドキュメントと向き合う必要がありました。

結局Teamsのみ見送りになったのですが、せっかくなので調べて分かったことをまとめます。

## Teams以外とTeamsの比較

### Teams以外の場合
Teams以外の一般的なSNSで今回の要件を実装する場合、このような流れになります。

（※ざっくり処理の流れを伝える図であり、あまり厳密な図ではありません）
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/ba889775-918f-fbaa-e08f-3c1fb37ecae4.png">

### Teamsの場合
Teamsが他のSNSと大きく異なるのが、「Bot」の概念です。
**Teamsには「Bot Framework」という概念が存在しており、Webアプリ開発者がBotの振る舞いをNode.jsでコーディングするようになっています。**
**また、実装したBot用のAPIサーバを建てる必要があります。**
**（本家ドキュメントではAzureAD上に建てるよう紹介されていましたが、どうやら別プラットフォーム上に建てても良いらしい？）**

（※ざっくり処理の流れを伝える図であり、あまり厳密な図ではありません）
<img width="600" alt="名称未設定ファイル-ページ1のコピー.drawio (1).png (68.7 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/b59e1997-81f5-2c73-0d3c-aa4a08f328b3.png">


## 構築手順（ローカルまで）

以下が今回やりたいことに近い印象だったのでこれを真似しました。
https://docs.microsoft.com/en-us/microsoftteams/platform/sbs-gs-notificationbot?tabs=vscode

### 1. 開発環境の用意：Teams Toolkitのインストール
> Prepare development environment > Install the Teams Toolkit

VSCodeに拡張をインストールします。
インストール終えるとチュートリアルが立ち上がりますが、先述の記事のほうに従いますのでスルーします。

<img width="600" alt="cb3fde5b-36f8-63ac-da83-26862d5d4070.png (316.8 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/389f3718-6b91-72b1-1bfd-d2b8923c717b.png">


### 2. 無料のTeams sandbox環境を作成する

> Set up your Teams development tenant > Create a free Teams developer tenant (optional)

Slackでは実際のworkspace上で開発を行うのですが、TeamsおよびOffice365にはsandbox環境の仕組みが存在しています。

[Create a M365 testing tenant](https://developer.microsoft.com/en-us/microsoft-365/dev-program) の「Join now」をクリックします。

<img width="600" alt="5fa99734-f129-7d98-9b9d-ce0868fe6bfc.png (219.7 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/6c41eb63-06bc-8baa-a419-59a4351fa58f.png">


必要事項を入力し「Next」をクリックします。

<img width="600" alt="cd66a641-c113-0b47-10a0-dc461090f0f5.png (96.2 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/bc3e5b87-a7c6-5ec6-61cf-4276f3eae42c.png">

使うものをチェックします。
本記事では「Microsoft Teams」のみチェックしました。

<img width="600" alt="e161e0d3-81bd-2a47-8767-36b2da8142bd.png (446.9 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/7c129b5c-0166-a15d-2175-57bfc5d4f6ae.png">

sandbox環境の初期データ＆管理者ユーザ作成のウィザードが表示されるので従います。
「Instant sandbox」を選択して「Next」

<img width="597" alt="fdb7d79a-e3be-cd83-4e09-ef65bfa691ab.png (60.4 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/25630420-3c64-573d-005f-c20f653cc40b.png">

管理者ユーザの名前とPWを入力して「Continue」

<img width="600" alt="ae7e60a3-60a5-836e-10ca-f6ccd87fb464.png (61.1 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/a7bef8ad-0005-0d95-459c-c2418e33b595.png">

電話番号を登録して「Set up」

<img width="600" alt="0be3b6e4-df4b-3df7-f0df-a797fda5d1b0.png (104.4 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/8a91a5d5-889f-2939-12c4-38fb805f09d0.png">

以上の手順でsandbox環境が立ち上がります。
「Go to subscription」をクリックすると、該当sandboxのOffice365ホーム画面へ移動できます。

<img width="600" alt="5d78c6e1-1b8f-f00d-63d7-afed75f839d5.png (109.5 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/6230e25d-2d2a-d4c2-7098-48ec7c357ece.png">

「Teams」をクリックします。
（うまく遷移できないかも[^1]）
[^1]: ログインアカウントの切り替えがうまく働かないのか、自分が操作した際はもともとログイン中だった非sandboxなアカウントで入ってしまいました。一旦ログインし直すなど必要かもしれません。

<img width="600" alt="d5b3e3e4-62f8-a8bc-11d4-e63ef2917669.png (147.7 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/22f74c08-9cf4-c8ba-2528-b35d62c00096.png">


Teamsに入れました。テストデータが入っているのがわかります。


### 3. BotのNodeプロジェクトを新規作成する
> Build notification bot

Teams Toolkit 拡張を開き、「Create a new Teams app」を選択します。

<img width="300" alt="e2293dd5-9bcc-4a12-2149-bf327c13e307.png (77.7 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/826f59a8-770a-1890-3a56-08651a0da162.png">

以下の通り選択します。

<img width="600" alt="387d7f81-eb65-ae31-6ca0-b20e3cdd6b17.png (217.2 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/4ced80ac-744a-3b13-e9d4-e0572eede3c8.png">

・Javascript/TypeScript
・保存場所
をそれぞれ選択します。

<img width="600" alt="b771ed01-aad8-f9ca-7593-2d00f1bda76b.png (55.0 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/3b705f81-0590-54c4-6ff5-742cc85b7122.png">

アプリ名は適当な名称を入力します。

<img width="300" alt="5d2dd10e-1718-05ab-c288-8fbf8666f0e5.png (12.7 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/6ab50600-99db-6d35-ab04-85af1e3d057d.png">

プロジェクトが作成されるので開きます。
Teams　Toolkitを開きます。
「Teams Toolkit > ACCOUNTS > M365にサインインする」を実施し、以下の状態になるようにします。

※「サイドローディングが有効」になっていない場合は[こちら](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/prepare-your-o365-tenant#enable-custom-teams-apps-and-turn-on-custom-app-uploading)の手順を実施します

<img width="300" alt="d10eb32f-df7a-c328-bb80-c73a28fed288.png (156.4 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/85533010-68b8-68c1-3d16-ef279662161b.png">


サインインできたら、デバッガを起動します。

<img width="300" alt="c1e68c46-3748-b286-58e5-f5c341228275.png (43.9 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/41190c54-af90-0886-ccbd-e2bb4fd80d35.png">


「Debug(Chrome)」を実行してしばらく待つと、Chromeのウィンドウが立ち上がります。
途中でログインを求められた場合は入力します。
以下画面になりましたら、「追加」でアプリを追加します。

<img width="600" alt="6bf14b94-6110-28ae-d276-ab0fc61eb3db.png (202.8 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/7a6fafda-164d-64d9-9a7b-0e02ef5fe1dc.png">

ホストマシンで

```shell
curl -X POST http://localhost:3978/api/notification
```

を実行し、以下のようにTeams上にメッセージが送信されたら成功です。

<img width="600" alt="ded78997-c230-5e1e-0925-f269cf752a58.png (144.1 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/992d6d7c-a4e3-b0e0-0f85-1481356899a8.png">


続いて、特定ユーザにメッセージを送るようにします。
botのソースを以下のように変更します。
（ユーザ特定がかなり無理やりなため不安あり）
```js:src/bot/index.js
server.post(
  "/api/notification",
  ...,
  async (req, res) => {
    for (const target of await bot.notification.installations()) {
      if (target.type !== 'Person') {
        console.log('target.type mismatch')
        continue
      }
      if (target.conversationReference.user.id !== req.body.user_id) {
        console.log('user_id mismatch')
        continue
      }
      await target.sendAdaptiveCard(
        AdaptiveCards.declare<CardData>(notificationTemplate).render({
          title: req.body.title,
          appName: '', // TODO: 隠し方調査（カードの種類変える？）
          description: req.body.description,
          notificationUrl: '', // TODO: 隠し方調査（カードの種類変える？）
        })
      );
```

ホストマシンで

```shell
curl -X POST \
  -d user_id=xxx \
  -d title="Sample Service" \
  -d description="豊後 国博 様より [自宅インテリアを３Ｄ画像でコーディネートするサービス「インテリアート」](https://example.com/hoge)にコメント" \
  http://localhost:3978/api/notification
```

を実行すると、以下のようにTeams上にメッセージが送信されます。
※`user_id` は [graph explorer](https://developer.microsoft.com/ja-jp/graph/graph-explorer)から取れます

<img width="600" alt="スクリーンショット 2022-09-14 19.12.10.png (37.5 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/c4b18b2c-3d15-1b5f-add7-dcb2e3c0e834.png">

ちなみに、ここまで完了すると[Teams developer portal のApps画面](https://dev.teams.microsoft.com/apps)に「mynotificationbot-local-debug」が追加されているかと思います。
ローカルで実施する分には意識することのない画面ですが、ステージ本番反映する上では操作が必要そうな画面です。

<img width="600" alt="スクリーンショット 2022-09-15 17.28.21.png (37.3 kB)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728467/002e6ec8-e436-d89b-0159-299c6dde9d01.png">

## まとめ
今回はローカル環境上でTeamsBotを起動し、curlで任意の通知メッセージを送信するところまで実装しましたが、以下のような未解明事項を残しています。
今後これらの点まで調査できればと思います。

・ユーザを特定する処理
かなり無理矢理なので、そもそもこの記事でご紹介した方針で妥当かは未だ疑問符です。

・アプリ審査
他のOAuth認可と同様、本番反映の際にはMicrosoftの審査を受ける必要があると思われます。

・リモート環境でも正常に動作するか？
本記事でご紹介したローカル環境では、TeamsBotAPIサーバはローカル上に起動しており、ngrokによって上手くTeamsと連携できています。
実際はサーバを建てる必要があり、ドキュメント中ではAzureAD上に建てる方法で紹介されていましたが、AWS上に建ててもよい性質のものかは確認が必要です。