SiriっぽいTeamsアプリを作ってみる

# はじめに

仕事にしても何にしても使えるものは使っておくに限る。
最近はリモートワークの影響でteamsを使い始めたので、これも使っておきたい。
どうせなら勤怠とかの自動化に使おうとも思ったが、敷居が高そうなので途中から計画変更してSiriにした。
だって、`Siriっぽいもの作った事あります`って言っておけば何かしら仕事もらえるんじゃなかろうか。

# 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/2339e452-8381-aaf7-dc6f-4bfec2eda6b1.png)
<font color="dark_pink">何も分からない俺のSiri</font>

# Teams API

TeamsにはTeamsに蓄えられた情報を扱うためのRest APIが用意されている。

 + [Microsoft Teams API](https://docs.microsoft.com/ja-jp/graph/teams-concept-overview)
 + [Microsoft Graph API を使用して Microsoft Teams で作業する](https://docs.microsoft.com/ja-jp/graph/api/resources/teams-api-overview?view=graph-rest-1.0)

これらのRestAPIを使ってタブやボットなどのアプリを作成する。
尚、APIで使用可能なリソースは下記種類がある。

| リソース | メソッド |
| :----- | :----- |
| team | 各自のチームの一覧表示、すべてのチームの一覧表示、作成、読み取り、更新、削除、複製、アーカイブ、アーカイブ解除 |
| group | メンバーの追加、 メンバーの削除、所有者の追加、 所有者の削除、ファイルの取得、ノートブックの取得、プランの取得、予定表の取得 |
| channel | 一覧表示、作成、読み取り、更新、削除 |
| teamsTab | 一覧表示、作成、読み取り、更新、削除 |
| teamsApp | 一覧表示、公開、更新、削除 |
| teamsAppInstallation | 一覧表示、インストール、アップグレード、削除 |
| chatMessage | 送信 |
| call | 応答、拒否、リダイレクト、ミュート、ミュート解除、画面共有ロールの変更、参加者の一覧表示、参加者の招待 |
| schedule | 作成または置換、取得、共有 |
| schedulingGroup | 作成、一覧表示、取得、置換、削除 |
| shift | 作成、一覧表示、取得、置換、削除 |
| timeOff | 作成、一覧表示、取得、置換、削除 |
| timeOffReason | 作成、一覧表示、取得、置換、削除 |

2020-06-27現在では.NET, JS, Pythonでサンプルが用意されている。

 + [Microsoft Teams 開発者向けプラットフォームのチュートリアルとコードサンプル](https://docs.microsoft.com/ja-jp/microsoftteams/platform/tutorials/code-samples)


# 参考になるサンプルを探す

サンプルを眺めていると、nodejs配下に50.teams-messaging-extensions-searchなるジャストなものがある。
このサンプルはなにをするものかと言うと、`アプリケーションコマンドにnodeのパッケージ名を入力すると詳細を教えてくれる。`と言うもの。
アプリケーションコマンドが何かと言うと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/c694c882-ad89-d9ae-7903-069b52e75650.png)
<font color="dark_pink">ココ</font>

これを元にsiriを作っていきたい。まずはREADMEに沿って環境を整える。

## 必要なパッケージを入れる。

nodejsのサンプルなので、必要なのはnodejs。特にバージョンの指定がないのでstableで良いと思う。
自分の端末にはもともと入っていたので特に変えてない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/4c28e06a-65e6-5684-5be1-5a26e36a4fb1.png)

nodejs入れたらinstall

```bash
npm install
```

## ngrokの導入

READMEを読むと[ngrok](https://ngrok.com/)を利用するらしいので、早速本家から落としてインストールする。尚ngrokに関しては有志の強い方が記事を書いてくださっているので、そちらを参照してほしい。
この記事でngrokに関する有用な情報はない。だって今回初めて触った。

 + [ngrokが便利すぎる](https://qiita.com/mininobu/items/b45dbc70faedf30f484e)
 + [ngrokの利用方法](https://qiita.com/hirokisoccer/items/7033c1bb9c85bf6789bd)

インストール方法は[ngrok](https://ngrok.com/)に登録してソフトをダウンロードして展開するだけ。
展開したらexeが出てくるのでパス通す。特に困る事はないハズ。

公式サイトの指示通りngrokをアカウントに接続させたら完了。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/1b9c3efd-6d27-9478-92aa-bf79e1fad9a2.png)
<font color="dark_pink">とりあえずヘルプ出してみた</font>

あとは実行するだけ

```bash
ngrok http -host-header=rewrite 3978
```

## Azureの準備

teamsを使う以上、Azureを使う。で、Azureの準備。

[Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration)の手順通りに進める。
[Azure Portal](https://ms.portal.azure.com/)を開き、リソースを作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/2081ef60-56b0-d4b8-08f1-153c7325a3e9.png)

`bot`と打つと`Bot Channel Registration`の候補が現れるのでこれを選ぶ。(公式手順ではWeb App Botでもできるらしい)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/71ead8ba-8984-3e70-e4a8-fb3eaf3c4777.png)

一礼して作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/df4aa2a7-074d-8564-189c-fe41c1399356.png)

下記設定で作成ボタンをクリック。するとデプロイまで進んでくれる。Azure賢い。

| 項目 | 設定値 | 
| :----- | :----- |
| ボットハンドル | miyatama-worksheet-hater |
| サブスクリプション | 従量課金(デフォルト) | 
| リソース | miyatama-teams-learning(新規作成) |
| 場所 | 米国西部(かっこいいから) |
| 価格レベル | S1 |
| メッセージングエンドポイント | |
| ApplicationInsight | オン |
| ApplicationInsightの場所 | East US |
| MicrosoftアプリIDとパスワード | 自動生成 |

で、しばらく放っておくと完了の通知が来る。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/30eb89dc-ce7c-74b4-9eaa-b62212dd3bee.png)
<font color="dark_pink">や っ た ぜ</font>

すべてのリソースを開くと2つリソースが追加されている(botとApplication Insight)。Botの方をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/1d3b05f9-6d66-baf3-a732-4947d961e15b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/cde11406-c0fc-e34a-2afc-249c8e2b2f07.png)

設定をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/4f3596a7-c9dc-6029-6bd9-13c038017d04.png)

右側ペインを下にスクロースるとMicrosoft App IDの項目があるので、管理のリンクをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/ab9a0a8c-7be3-ac93-4512-9d213833bdba.png)

新しいクライアントシークレットをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/ad329ec5-4ed6-0669-aa04-ae1bc4502ac7.png)

説明を入力して追加をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/b733ac4c-897b-2d26-42f7-d1498b9f3aa9.png)

ちゃんと追加される。この時にキーをコピーしてファイルに保存しておく。大事な事なのでもう一回書いておく。
<strong>シークレットをコピーしてファイルに保存しておく</strong>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/970c2b0c-5006-be2e-697a-e9f977682851.png)
<font color="dark_pink">ちゃんと追加出来ててえらい。</font>

## Azureでteams使える様にする。

Azure Portalで発行したAppIDとパスワードを.envファイルに追記する。

| 項目 | 設定値 |
| :----- | :----- |
| MicrosoftAppId | 後述 |
| MicrosoftAppPassword | 前述のシークレット発行時にコピーした文字列　|

AppIDはbotの設定に記載されている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/840c423a-fbc6-38fb-ee9a-0684b03f7d72.png)

これで準備ができたので早速動かす。

```bash
npm start
```

## teams用の設定を行う

teamsAppManifest/manifest.jsonを編集する。

 + idへMicrosoft App IDを設定
 + composeExtensions.botIdへMicrosoft App IDを設定

その後、teamsAppManifest内のファイルをZipに固める(ファイル名はmanifest.zip)。
大事な事なのでもう一度書こう。
<strong>teamsAppManifest内のファイルをZipに固める</stroing>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/20a51608-db66-b8f2-1d53-1591d402ffca.png)

teamsからカスタムアプリのアップロードを行う。アプリのアイコンをクリックし、ペインをスクロールするとカスタムアプリをアップロードの項目が出てくるのでクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/283a53e2-b5fe-9249-c508-a60221160993.png)

アップロードで先ほど作成したzipファイルを選択する。成功した場合はアプリが画面上に表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/151d9511-b91d-de03-b138-add76e34b32b.png)
<font color="dark_pink">アイコン変だけど気にしない</font>

アプリをクリックして追加を行う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/02698220-0ee0-00b1-90c7-cd861a760be0.png)


## 早速よびだす

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/6765a34b-62d9-8531-89e7-826bd6ffe2c3.png)
<font color="dark_pink">だめでした。</font>

これはなぜかと言うと下記図で言う所のAzureとngrokが連携できていないから。

![flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/81a02491-ad02-3ea3-eb93-8ba1231b2ff1.png)

と言うわけでngrokで用意したendpointをAzureに教えてあげないといけない。
まずは[ココ](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0)を参考にchannelを作成する。
次に、ngrokeのendpointをAzureに教える。ngrokのendpointは`ngrok http -host-header-rewrite 3978`で実行した時の結果を参照してほしい。
万が一gitbashで実行してしまった場合はwinptyを使うなりコマンドプロンプトで実行するなりなんなりで対処してほしい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/c6af44c6-bb04-64aa-386a-0df1eafdf431.png)
<font color="dark_pink">上記例だと`https://0a3496160a1a.ngrok.io/api/messages`</font>

Azure PortalからBotのリソースを開き、設定を表示する。すると、メッセージングのエンドポイントなる項目があるので、ここにngrokのendpointを張り付ける。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/ddcca735-1a66-8c26-d08f-46e92a3efeff.png)

## もう一回試す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/6a3bd4d2-2868-39b5-9447-5ddba5739f25.png)
<font color="dark_pink">や　っ　た　ぜ</font>

# Siriっぽくする

サンプル実行できただけでもかなりお腹いっぱいなのだが、元来の目的であるSiriの作成に入る。
Siriと言えば、`すみません。よくわかりません。`だ。異論もあるだろうか、私のイメージではそうだ。

botのコードを見てもらうと分かる通り、検索した結果を返すだけだ。これをSiri化するのは簡単だ。
固定で`すみません。よくわかりません。`を返せばよいだけだ。

中の仕様がよく分からない方は[ここ](https://docs.microsoft.com/ja-jp/microsoftteams/platform/messaging-extensions/how-to/search-commands/respond-to-search?tabs=dotnet)を見てほしい。めちゃくちゃ親切に書いてある。
書き変えた(?)後のコードは以下の通り。

```js
const axios = require('axios');
const { TeamsActivityHandler, CardFactory } = require('botbuilder');

class TeamsMessagingExtensionsSearchBot extends TeamsActivityHandler {
    async handleTeamsMessagingExtensionQuery(context, query) {
        const attachments = [];
        const heroCard = CardFactory.heroCard("result");
        const preview = CardFactory.heroCard("すみません。よくわかりませんでした。");
        const attachment = { ...heroCard, preview };
        attachments.push(attachment);

        return {
            composeExtension: {
                type: 'result',
                attachmentLayout: 'list',
                attachments: attachments
            }
        };
    }
}

module.exports.TeamsMessagingExtensionsSearchBot = TeamsMessagingExtensionsSearchBot;
```

早速試す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/c2410445-774d-fb03-9b2d-8a4a8f7779fe.png)
<font color="dark_pink">や っ た ぜ</font>

# ふりかえり

冒頭にも記載した通り、最初は勤怠の自動化をやろうかと思って色んなサンプルを眺めたりしていた。
が、途中でめんどくさくなって、何でもいいからアウトプットしておく運びとなった。
まぁ、ここまでやっておけば後は自然言語処理でも賢さ演出するぐらい誰でもできるし、何とかなるだろう。
teamsの記事を何個か書く予定なので無駄にはならないハズだ。

何よりも、勉強の方法を議論する位なら勉強した方が早い。