izumotto blog

たまには真面目に

ChatWorkのWebhookとMicrosoft Flowで何かやる

心機一転して1つ目の記事です。若干真面目な記事を気まぐれで書きます。

 ----------

私の職場ではChatWorkを使っているのですが、2017年11月1日からWebhookに対応したという発表がされていました。そこで、最近友人が積極利用を呼びかけているMicrosoft Flowを使って何かしてみたという内容です。

 

目次

 

今回やること

今回Flowで実装するのは以下のフローです。

  1. Chatwork上で特定のルームに書き込みがされる
  2. 書き込み内容がファイルの添付だった場合、そのファイルをDropboxの特定フォルダにコピー(ダウンロード)する

Flowの起動トリガーにWebhookを用いることで、リアルタイム性の高いファイルのコピーができます。

 

1)Flowを準備する

まずはFlowの準備です。Microsoft Flowにアクセスして、サインインします。

f:id:izumotto:20171230143612p:plain

メニュー左上の「マイフロー」を選択すると、今まで作成したフロー一覧が表示されます。今回はテンプレートを使わないので、「一から作成」を選択します。

f:id:izumotto:20171230144521p:plain

最初にトリガーを選択しますが、「要求 - HTTP要求の受信時」を選択します。

f:id:izumotto:20171230145055p:plain

ここでは、JSONスキーマを入力する必要があります。Flowにはサンプルから自動でスキーマを作成してくれる機能があるため、これを活用しました。ChatWork APIドキュメントのWebhookページに掲載されているリクエストボディのサンプルをコピーし、それを上図の「サンプルのペイロードを使用してスキーマを生成する」の先に貼り付けることでトリガーが完成します。

HTTP POSTのURLを取得したいので、ここで一度「フローの保存」をし、「完了」を選択します。フローの詳細画面に戻るので、「フローの編集」を選択するとURLが確定しているはずです。このURLをコピーして、次はChatWork側の設定を行います。

 

2)ChatWorkを準備する

WebhookのURLが確定したので、次はChatWork側の準備です。ChatWorkにログインしたあと、右上のメニューを開いて「API設定」を選択します。

f:id:izumotto:20171230150837p:plain

 左のメニューから「Webhook」を選択すると、作成したWebhook一覧が表示されます。今回はFlow用の新しいWebhookを作成するため、「新規作成」を選択します。

f:id:izumotto:20171230153710p:plain

Webhookの新規作成画面が表示されるため、必要事項を入力します。今回は「ルームイベント」を選択します。

f:id:izumotto:20171230153849p:plain

  • Webhook名
    ChatWork上でWebhookを区別するための名称です。好きな名前をつけます。
  • Webhook URL
    先ほどFlowで作成した「HTTP POSTのURL」を入力します。
  • イベント
    ルームイベントを選択し、メッセージ作成メッセージ更新の両方にチェックを入れます。ルームIDには、Webhookによる通知を受けたいルームのIDを入力します。

必要事項の入力が完了したら、「作成」ボタンを押します。これらの入力内容は後から編集して修正することが可能です。

Webhookの一覧画面に戻り、今作成したWebhookのステータスが「有効」になっていれば完了です。先ほど指定したルームにメッセージが投稿されると、Flowでその情報を受け取ることができるようになりました。

 

3)添付ファイルをDropboxにコピーする

ルームに書き込みがされたことを検知できるようになったため、あとは目的を果たすためにFlow側で試行錯誤します。

ファイルのダウンロード等にはChatWorkのAPIを使用するため、まずはそちらの準備が必要です。先ほどのAPI設定画面で「API Token」を選択してアカウントのパスワードを入力すると、APIトークンを取得できます。あとで使用するので一旦コピーしておきます。

f:id:izumotto:20171230155327p:plain

ここからはFlowに戻り、ステップを組み合わせていきます。ステップの概要は以下の通りです。

  1. 添付ファイルのファイルIDを取得する
  2. ファイルのダウンロード用URLを取得する
  3. ファイルをダウンロードする
  4. Dropboxにファイルをアップロードする

実際には適宜バリデーションをかけてあげる必要がありますが、本記事では割愛します。(考慮すべき点などは最後にまとめて記載します)

3-1)添付ファイルのファイルIDを取得する

ChatWorkには、ファイルをダウンロード可能なURLを生成するAPI用意されており、これを使ってルームに投稿されたファイルをダウンロードします。その際、各ファイルに割り当てられているファイルIDを指定する必要があるため、まずはファイルIDを取得します。

簡単と思っていましたが、個人的にはここが一番つまづきました。以下はボツ案なので読み飛ばしても問題ありません。

  • ボツ1)ChatWorkにはファイル一覧を取得するAPIが用意されており、最初はこれを利用しようと考えていましたが、なぜか投稿したファイル情報が取得できないので諦めました。ルームへのファイル投稿数が100件を超えているからなのか、APIが返すファイル一覧に反映されるのに時間がかかるのか、詳細までは調べていません。
  • ボツ2)Webhookから取得できるデータには、各メッセージに割り当てられているメッセージIDが含まれています。そこで、メッセージの詳細を取得するAPIを用いてファイルIDを取得しようと思いましたが、このAPIはファイルIDを返してくれないようだったので諦めました。

そもそもファイルIDを取得できるAPIは、ボツ1)のファイル一覧を取得するAPIしか用意されていないようでした。ということで、どこかからファイルIDを抜き出す必要があります。

どこからファイルIDが得られるか調べてみたところ、メッセージ本文にファイルIDが含まれていることが分かりました。ルーム画面からファイルを確認した際、下図オレンジ枠のファイルダウンロードリンクに、「file_id={ファイルID}」としてファイルIDが含まれています。

f:id:izumotto:20171230195009p:plain

また、同様にWebhookから得られるbody要素にもファイルIDが含まれていました。

  • Body要素
    [info][title][dtext:file_uploaded][/title][preview id={ファイルID} ht=150][download:{ファイルID}]{ファイル名} ({ファイルサイズ} KB)[/download][/info]

body内の2箇所にファイルIDが含まれているため、ここから抜き出すことでファイルIDを取得するようにします。

前置きが長くなりましたが、Flowで新しいステップ「データ操作 - 作成」を選択します。

f:id:izumotto:20171230203053p:plain

この際、「動的なコンテンツの追加」から「文字列関数の式」を使って、body要素からファイルIDだけを抜き出します。

今回はbody要素の「[download:{ファイルID}]」部分からファイルIDを抜き出します。やり方は色々あると思いますが、やりたいことは以下の通り。

 substring(
  string(triggerBody()?['webhook_event']?['body']),
  add(
   indexOf(
    string(triggerBody()?['webhook_event']?['body']),
    string("[download:")
   ),
   10
  ),
  9
 )

ただし、これらを1つの「作成」ステップに入れようとするとエラーになってしまったので、以下の4ステップに分けました。

  1. "[download:"という定型ワードを出力
  2. indexOfを用いてbody要素内に定型ワード(1.)が何文字目に含まれるかを出力
  3. addを用いてbody要素内にファイルIDが何文字目に含まれるかを出力
    ※2.の出力結果+10(定型ワードの文字数)
  4. substringを用いてbody要素からファイルIDを出力

 

スマートさはありませんが、一旦力ずくで実装です。

f:id:izumotto:20171230204949p:plain

なんとかファイルIDを取得することができました...!

3-2)ファイルのダウンロード用URLを取得する

ファイル情報を取得するAPIを用いて、30秒間だけダウンロード可能なURLを生成します。新しいステップ「HTTP」を追加します。

f:id:izumotto:20171231140511p:plain

  • 方法
    GET
  • URI
    「動的なコンテンツの追加」を用いて、下記を入力します。最後の?以降を入力することでダウンロード用URLを取得できます。
    https://api.chatwork.com/v2/rooms/{ルームID}/files/{ファイルID}?create_download_url=1
  • ヘッダー
    Enterキー:X-ChatWorkToken
    値の入力:ChatWorkのAPIトーク

APIを叩くとJSONが返ってくるので、次のステップ「データ操作 - JSONの解析」を使ってファイルダウンロード用URLだけを取得します。

f:id:izumotto:20171231141658p:plain

  • コンテンツ
    「動的なコンテンツの追加」を用いて、先のHTTPで取得した本文を指定します。
  • スキーマ
    API仕様書に記載のサンプルをコピーして「サンプルのペイロードを使用してスキーマを生成する」に貼り付ければ良いのですが、サンプルにはダウンロード用URLの記載がないため、スキーマの"filesizse"や"upload_time"の並びに以下を追記します。
    "download_url" : { "type" : "string" }

これで、ファイルダウンロード用URLの取得ができました!

3-3)ファイルをダウンロードする

ファイルのダウンロードは、先ほど使ったステップ「HTTP」を使えば実現可能です。

f:id:izumotto:20171231142620p:plain

  • 方法
    GET
  • URI
    「動的なコンテンツの追加」を用いて、JSONの解析から得られた"download_url"を指定します。

ファイルのダウンロードはこれで完了です。

3-4)Dropboxにファイルをアップロードする

先ほどダウンロードしたファイルをDropboxにアップロードします。Flowが標準で提供しているDropbox関連のアクションの中から、「Dropbox - ファイルの作成」を次のステップに指定します。

※FlowとDropboxを初めて連携させる際には、Dropbox側での承認が必要です。画面に指示が出てくるので、指示に従ってFlowがDropboxを操作することへの許可を与えてください。

f:id:izumotto:20171231143412p:plain

  • フォルダーのパス
    ファイルを作成するDropbox内のパスを指定します。入力欄右側のフォルダアイコンを選択すると、GUIでパスを指定できます。
  • ファイル名
    作成するファイル名を拡張子付きで指定します。ダウンロード用URLを取得した際にファイル名も取得できているので、「動的なコンテンツの追加」を用いて、JSONの解析から得られた"filename"を指定します。
  • ファイルコンテンツ
    ファイルの中身は、先にHTTPでダウンロードしてきたファイルそのものです。「動的なコンテンツの追加」を用いて、ファイルをダウンロードしたHTTPの"本文"を指定します。

ここまでできれば完成です!

 

 まとめ

おさらいすると、今回実現したかったことは以下の内容です。

  1. Chatwork上で特定のルームに書き込みがされる
  2. 書き込み内容がファイルの添付だった場合、そのファイルをDropboxの特定フォルダにコピー(ダウンロード)する

最終的なFlowは以下のようになりました。

  1. 要求:Webhookを受け取る
  2. 作成:ファイルID取得の準備1
  3. 作成:ファイルID取得の準備2
  4. 作成:ファイルID取得の準備3
  5. 作成:ファイルID取得
  6. HTTP:ファイル情報取得
  7. JSON解析:ファイルダウンロード用URL取得
  8. HTTP:ファイルのダウンロード
  9. Dropbox:ファイルのアップロード

作成のところが力ずくで美しくないですが、WebhookやAPIの知識がゼロの筆者からすると自力でゴリゴリ勉強して書くよりはるかに楽に実現できたと思います。実際に動作を確認したところ、ChatWorkへのファイル投稿からDropboxへのファイル保存までは2,3秒で完了しました。テストした時間帯やファイルサイズに依存するかもしれませんが、レスポンスの速さは概ね良好です。

最後に、実用化のためにもう少し考慮すべき点です。

  • 添付ファイルありきの実装のため、ただのメッセージ投稿の場合は5.か6.あたりで処理がこける。投稿内容がファイル添付かどうかを判断する必要がある。
  • 全てのファイルをDropboxに保存する実装のため、自分が投稿したものも保存されてしまう。自分のユーザID等、保存しなくてよい投稿者のユーザIDの場合は処理を途中で終了する必要がある。

あとはファイルIDの取得をもう少しスマートにしたい…ですね。