前置き
チームの連絡手段をLINEのグループメッセージからSlackに移行するにあたって,スタンプがないのが寂しいという意見が寄せられたため,無理やりSlackに引き込むためにスタンプ機能を実装したよ,という話です.
やりたいこととしては,Slack AppでLINEスタンプに相当する機能を実装するというシンプルなものです.
関連事例
ネタとしてはn番煎じです. 同じことを考える方はいるようで,Web Hookを活用してGASで対応する画像URLを取得して貼り付けたり[1],CSSをいじったり[2][3],それをChromeの拡張機能として実装したり[4]と,絵文字を大きく表示させる方法がすでにいろいろ考えられています. しかし,[1]の場合は,画像のURLがメッセージ上に表示されてしまう上に,スタンプの情報を予めスプレッドシートに登録しておく必要があります. また,[2][3][4]の場合は,他人からの見え方をいじることができないことや,モバイルアプリでの見え方をコントロールできないことなどが問題となります.
目標と使うもの
そこで,この記事では以下のことを目標にして実装を行います.
- URL等の無駄なものを表示させない
- カスタム絵文字をアップロードするだけでスタンプを増やせるようにする
- 通知にそれっぽい文字列を出す(「〜〜がスタンプを送信しました」みたいな)
SlackにはAPIが用意されていて,おまけにAPIを叩くためのライブラリも各言語向けに充実しています. 今回はFlaskを使ってPythonでプログラムを書き,zappaを使ってAWS Lambda上にデプロイしてサービスを走らせることにします.
- Flask (A Python Microframework)
- slackapi/python-slackclient: Slack Developer Kit for Python
- Miserlou/Zappa: Serverless Python Web Services
- AWS Lambda - Serverless Compute
今回AWSを初めて使ってみたので,変な表現や使い方の間違いがあるかもしれません.
準備
AWSを利用するために以下の準備を済ませておきます.
- AWSのアカウント作成
- IAMコンソールで認証情報の作成
- AWS CLI等で認証情報ファイルの作成: 参考
これらについてはネットや書籍に腐るほど資料があるので,そちらを参照してください. 無料のKindle本もあるようですが,どうもAWSの英語版ヘルプを単に電子書籍化しただけのもののようです.
今回はArch Linux上で開発を行いました.使用したソフトウェアとライブラリのバージョンは以下のとおりです.
- virtualenv 15.1.0
- Python 3.6.1
- zappa 0.41.2
- Flask 0.12.1
- slackclient 1.0.5
プロジェクト用にディレクトリを作って,その中にふつうにvirtualenvをつくります.
必要なパッケージをインストールします.
zappaを試してみる
zappaって何よ
そもそもzappaとはなんぞやというのを簡単に解説します.
Zappa makes it super easy to build and deploy all Python WSGI applications on AWS Lambda + API Gateway. Think of it as “serverless” web hosting for your Python apps. That means infinite scaling, zero downtime, zero maintenance - and at a fraction of the cost of your current deployments!
zappaを使うと超簡単にWSGIアプリケーションを AWS Lambda + API Gateway にビルド・デプロイできます. サーバレスになるので,メンテナンスもダウンタイムもありませんし,スケーリングも自由自在,しかも費用は何分の1にもなります.
というようなことが書かれています. Pythonでウェブサービスを書こうとすると,だいたいFlaskやDjangoのようなWSGIフレームワークで作成するわけですが,通常のやり方でこいつらを動かすためにはPythonが常時起動できるサーバを用意する必要があります. しかしzappaを使えばその必要はありません. 既存のWSGIアプリケーションを,コードはそのままに,AWS Lambda + API Gateway に展開できるわけですからこれはすごいです. AWSはかなりのリソースを無料で開放してくれていますから,こんなのを見つけちゃうとVPSとか契約するのが正直ばからしくなっちゃいます.
というわけで,これを使ってSlackのカスタム絵文字をLINEスタンプ風にするアプリを走らせます.
zappaを使う(Hello World)
FlaskでHello Worldを表示するだけのアプリケーションを,zappaを使ってデプロイしてみようと思います.
使用するコードはこちら(コード引用元):
app.py
試しに手元で実行してみましょう.
これをAWSに乗っけるために,zappaのセットアップをします.プロジェクトのディレクトリでつぎのコマンドを実行します.
ひとまず上記の画像のように何も入力せずに,デフォルトの設定を適用します.
zappa init
によって,zappaの設定ファイルであるzappa_settings.jsonが作成されます.
あとは zappa deploy dev
するだけで勝手にデプロイしてくれます.恐ろしい手軽さ…!
何かエラーが起きる場合は,aws configure
やIAMコンソールの認証情報などを確認してみてください.
先ほどと同様に curl でURLを叩いてみると,Hello World! が表示されます.
備考
- アプリケーションを編集して反映させたい場合は,
deploy
ではなくzappa update dev
を実行してアップデートします. zappa tail dev
を実行するとログが流れてきます.かなりカラフル
Slack Appを作ってデプロイする
皆さんご存知のとおり,SlackはBotやAppによって機能の拡張が簡単にできます. 今回の絵文字をスタンプ化する機能は,Events APIでメッセージのイベントを取得し,絵文字を判定して,絵文字画像のURLをattachmentに入れて投稿する,という処理で実現できます.
コードはGitHubで公開しています. セットアップの手順はREADMEをご覧ください.
基本的にはSlack Events API Pythonライブラリのサンプルコードを参考にして実装しました.
今回はこのライブラリは使用せずに,slackclientとFlaskだけで実装を行い,zappaを使ってAWS Lambdaにデプロイします. ここから先は,今回実装したコードの一部分を示しながら,Events APIを使用したSlack Appの実装と,zappaでのデプロイ手順を説明していきます.
Step 1. Slack Appの登録
Slack APIのApp管理ページへ行き,Appを作成します. App Nameはこの記事ではとりあえず Gigamoji とし,アプリを利用するチームを選択して完了です.
Step 2. イベント受取の設定・Slack API認証鍵のチェック
"Event Subscriptions"を開き,メッセージ関係のイベントを受け取るように設定します.
"Enable Events"をONにして,“Add Team Event” から取得するイベントを選択します.
今回は4つ設定していますが,プライベートチャンネルやDMでスタンプ化しない場合は message.channels
以外追加する必要はありません.
変更したら,画面下の “Save Changes” を押して変更を保存します.
今回のアプリケーションは投稿されたメッセージを読むほかに,絵文字の情報にアクセスしたりユーザとして投稿を行ったりするため,"OAuth & Permissions"で emoji.read
と chat.write.user
の権限を追加する必要があります.
最後に,"Basic Information"を開いてClient ID, Client Secret, Verification Tokenの3つを手元にコピーしておきます.
これらの秘匿すべき情報は,アプリケーションを実行するサーバの環境変数に格納するのが定石になっているようです. この記事でもその方法に則って,環境変数から各種認証情報を取得します.
zappaを使ったAWS Lambdaのリモート環境変数の設定方法は Step 7 で解説します.
Step 3. チャレンジに対するレスポンスの実装
Slack Events APIを使用するためには,url_verification
というイベントに対して指定された値で返答するようにしなければなりません[5].
これを実現するのがつぎのコードです(残りの部分はGitHubのコードを参照).
(2018.Mar.23 修正.force=True
しないとパースできなくなっていた.原因は不明.)
これを実装した状態でデプロイしたら,"Event Subscriptions"の"Request URL"にアプリケーションのURLを貼り付けて,Appを登録します. ここで入力したURLにイベントが飛んできます.
Step 4. OAuth認証・DynamoDBへのユーザトークンの保存
"OAuth & Permissions"を開いて,"Install App to Team"ボタンをクリックします. 承認画面が出てくるので,"Authorize"ボタンをクリックして,Appをチームにインストールします.
つぎに, “Redirect URLs” の項目を設定します.
ここでは,デプロイ先の https://xxxxxxxxx.execute-api.xxxxxxx.amazonaws.com/dev/auth_callback
を指定します.
ユーザの承認後,Redirect URLに対してOAuthアクセスのためのコードが渡されるので,Slack APIの oauth.access
メソッドを呼んでユーザに紐付いたトークンを取得し,それをDynamoDBに保管することにします.
まず保管先のDynamoDBのテーブルをAWSのコンソールで作成します.
テーブル名を適当に設定します.
今回は,Primary KeyとしてチームIDとユーザIDを組み合わせた文字列を使用し,値はOAuthトークンを UserToken
に格納します.
Primary Keyの名前はここでは TeamUserId
としておきます.
キャパシティは適当に設定します.
テーブルの作成が完了したので,つぎにプログラムからDynamoDBを使用するコードを書きます. テーブル名とAWSリージョンは環境変数に格納しておきます(リージョンはboto3を使って取得できそうな気もしますが,今回はひとまず). DynamoDBはPython向けライブラリから非常に簡単に操作できるようになっています.
oauth.access
でユーザのトークンを取得し,auth.test
でチームIDとユーザIDをあらためて取得,DynamoDBに保存という流れです.
今回は平文でてきとーに保存していますが,必要に応じて暗号化するようにしたほうがよさそうです.
Step 5. Appのインストール画面の作成
今回のアプリケーションは,Appがユーザのメッセージを消したりユーザの代わりに投稿したりするものです. したがって,チームにAppをインストールするだけでなく,ユーザごとにインストール画面を開いてAppの認証を行う必要があります. ここではその画面を作成します.めんどくさいので単純に “Add to Slack” ボタンだけです.
このボタンを押してユーザが承認すると,Step 4で設定したURLに認証に必要な情報が飛んでいきます.
Step 6. イベントを受け取って処理する
これで準備ができたので,イベントを受け取って処理するコードを書きます.
イベントのデータは type
属性に "event_callback"
が指定されていて,JSON形式で “/” 宛にPOSTで届きます.
- message イベントであることを確認
- メッセージが絵文字のみかどうかをチェック
- DBからユーザのトークンを取得
- 絵文字の画像URLを取得
- もとのメッセージを消してスタンプ化した絵文字をユーザとして投稿する
の手順で処理を行います.
普通にURLを投稿するだけではスタンプ化には程遠いので,Attachment機能[6]を利用して画像だけをうまく表示させるようにします.
画像のみを表示するためには text
属性を空白にしてAttachmentの image_url
にURLを指定します.
また,fallback
属性に指定した文字列は,text
が利用できないときの代替文字列として通知に出してくれるみたいです.
ということで,今回は以下のようなAttachmentを使用します.
Message Builderを使うと,どのように表示されるかインタラクティブに確かめることができます: Message Builderの使用例
これで材料は整ったので,あとは手順通りにプログラムを組むだけです.ライブラリがしっかりしているのでらくちん.
ここで,200番台以外のステータスコードを返すとSlack側がリトライを投げてくるので,それを防ぐために X-Slack-No-Retry
をヘッダに設定して無効化するようにします.
全部200で返してもいいんですけど,まあ…
Step 7. zappaでのリモート環境変数の設定,デプロイ
そんなこんなでコードが準備できたので,zappaを使ってデプロイします.
zappaでは,S3 Bucketに環境変数を記述したjsonファイルを置くことでリモート環境変数を設定できるようになっています. まずはファイルを書きます.
env.json
S3 Bucketを新しく作成し,このファイルをアップロードします.
zappa_settings.json の remote_env
属性にファイルの場所を指定すれば設定完了です.
最終的にこんな感じになります.
zappa_settings.json
満を持してデプロイです.すでに完了している場合は update
で既存のものをアップデートしてください.
ここで初めてデプロイした場合は,Step 3, 4のURLの設定を忘れないようにしてください.
使ってみる
認証
デプロイ先のURLにアクセスするとボタンが出てくるので承認します.
Authorizeボタンを押すと,“Gigamojiを[チーム名]にインストールしました!!” と表示されて登録が完了します.
Slackでの動作確認
このアプリケーションはカスタム絵文字のみに対応しています(標準の絵文字はURLが取得できないので). てきとーなチャンネルで絵文字だけを投稿して動作確認をしてみると,つぎのようになります.
Androidアプリではファイルサイズも出ないので,スタンプっぽさが増します. 絵文字のほかに文字列がある場合や,1つの投稿に絵文字が2つ以上含まれている場合はスタンプ化されません.
また,通知の文字列もきちんと設定できています(別ユーザから観測).
まとめ
- Slack Appで絵文字をスタンプ化してみた
- zappaを使えばWSGIアプリケーションをサーバレス化できる
→ VPSやSaaSホストの準備が要らなくなる - AWSの利用方法の勉強になった