はじめまして、TechAcademy開発チームのmです。最近、Slack上で動く簡単なアプリを作ったのでそのお話をします。
Slackについて
KiRAMEXではコミュニケーションツールとして、Slackを活用しています。
- エンジニア <=> 非エンジニア
- 社員 <=> 業務委託
などなど、全てのやりとりがSlack上でシームレスに行われております。 他のチャットツールは用いておらず、業務に関することは全てSlackに集約されるのが良いですね。 また、TechAcademyのサービス内でもチャットサポート等にSlackが活用されております。
さて、Slackの良いところは、個人的には、下記3点挙げられるかと思っているのですが
- UI/操作性が良い
- 検索機能が充実している
- 「あの時あんなこと言ってた気がする...」という曖昧な記憶でも辿り着ける
- カスタマイズ性が高い
- 外部ツールとの連携、自作アプリなど、アイデアがあれば自由自在
その中でも、カスタマイズ性が高いというのは特に嬉しいポイント。APIが豊富、開発ツールも豊富で、エンジニアにとっては遊びがいあるツールですね。
実際にアプリを作ってみた
完成したもの
今回は以下のような、毎日業務終了時に送信している日報を簡単に書けるアプリを作りました。
バックエンド
APIリクエストをフックにLambdaを発動させます。この組み合わせは素早く作れるので個人的に好きです。 APIGatewayとLambdaそれぞれの設定に関しては今回は割愛します。
ライブラリ
公式に提供されているライブラリがたくさんあります。
今回は python-slack-sdk
を使いました。*1 *2
メッセージの見た目
Block Kit BuilderでSlackに送信するメッセージのUIを決めます。 自分でjsonを組み立てずとも、ブロックを動かすだけでメッセージがいい感じにできます。 ここで作ったjsonは後ほど使います。
実装
以下コードは一部改変&省略しています。
Slackからのリクエスト受け取り(handler部分)
import json import base64 from slack_sdk import WebClient from slack_sdk.errors import SlackApiError from urllib.parse import parse_qs def lambda_handler(event, context): body = base64.b64decode(event['body']) param = parse_qs(body.decode('utf-8')) payload = json.loads(param['payload'][0]) user_id = payload['user']['id'] if payload['type'] == 'shortcut': values = search_messages(user_id) open_modal(payload['trigger_id']) elif payload['type'] == 'view_submission': post_report(payload['view']['state']['values']) return { 'statusCode': 200 }
Slackでショートカットをクリックした時に最初に受け取る部分です。 event['body']
のなかにSlackから送られてくるpayloadが入っています。*3
payloadの中身はこんな感じ。
{ "type": "shortcut", "token": "XXXXXXX", "action_ts": "1620296309.086441", "team": { "id": "XXXXXXX", "domain": "XXXXXXX" }, "user": { "id": "XXXXXXX", "username": "XXXXXXX", "team_id": "XXXXXXX" }, "is_enterprise_install": false, "enterprise": null, "callback_id": "daily-report", "trigger_id": "XXXXXXX" }
以下APIを利用するには、ここに入っている trigger_id
が必要です。
メッセージを検索して前日分を取得する
def search_messages(user_id): client = WebClient(token=os.environ['SLACK_USER_TOKEN']) query = f'日報 {user_id} in:#channel' response = client.search_messages(query=query, sort='timestamp')
APIの search.messages
メソッドを使って、前日分のメッセージを取得します。
ここのクエリは、Slack上で command + F した時と同じ検索方法が使えるのでとても便利です。 返ってくるpayloadの中身はこんな感じ。
{ "ok": "True", "query": "の日報 {user_id} in:#channel", "messages": { "total": 18, "pagination": { "total_count": 18, "page": 1, "per_page": 20, "page_count": 1, "first": 1, "last": 18 }, "paging": { "count": 20, "total": 18, "page": 1, "pages": 1 }, "matches": [ 〜〜取得したメッセージの中身〜〜 ] } }
モーダルを開く
# trigger_idはSlack側からのリクエストで送られてきます def open_modal(trigger_id): client = WebClient(token=os.environ['SLACK_BOT_TOKEN']) data = { "Block Kit Builderで作ったjson" } client.views_open(view=data, trigger_id=trigger_id)
APIの views.open
メソッドを使って、日報を入力するモーダルを開きます。
ユーザ情報を取得する
def get_user_info(user_id): client = WebClient(token=os.environ['SLACK_BOT_TOKEN']) response = client.users_info(user=user_id) return response['user']
APIの users.info
メソッドを使って、日報送信したユーザ情報を取得します。
返ってくるpayloadの中身はこんな感じ。
{ "ok": "True", "user": { "id": "XXXXXX", "team_id": "XXXXXX", "name": "XXXXXX", "deleted": "False", "color": "bc3663", "real_name": "XXXXXX", "tz": "Asia/Tokyo", "tz_label": "Japan Standard Time", "tz_offset": 32400, "profile": { "title": "", "phone": "", "skype": "", "real_name": "XXXXXX", "real_name_normalized": "XXXXXX", "display_name": "", "display_name_normalized": "", "fields": "", "status_text": "", "status_emoji": "", "status_expiration": 0, "avatar_hash": "XXXXXX", "image_original": "https://XXXXXX", "is_custom_image": "True", "first_name": "XXXXXX", "last_name": "XXXXXX", "image_24": "https://XXXXXX", "image_32": "https://XXXXXX", "image_48": "https://XXXXXX", "image_72": "https://XXXXXX", "image_192": "https://XXXXXX", "image_512": "https://XXXXXX", "image_1024": "https://XXXXXX", "status_text_canonical": "", "team": "XXXXXX" }, "is_admin": "False", "is_owner": "False", "is_primary_owner": "False", "is_restricted": "False", "is_ultra_restricted": "False", "is_bot": "False", "is_app_user": "False", "updated": 1620288636, "is_email_confirmed": "True" } }
フォーム内容を投稿する
def post_report(data): ## 中略 blocks = { "Block Kit Builderで作ったjson" } client = WebClient(token=os.environ['SLACK_BOT_TOKEN']) response = client.chat_postMessage( channel="#daily-report", blocks=blocks, icon_url="投稿者のアイコンurl", username="username" )
APIの chat.postMessage
メソッドを使って、日報を送信します。
ここでは、投稿者が誰かわかるように、user_name
や icon_url
を指定しました。
Slack側での設定
アプリの作成
使用するAPIに必要なScopeをつける
ちなみに各APIに必要なScopeは以下の通りでした。
APIメソッド名 | TOKENタイプ | 必要なScope |
---|---|---|
search.messages | user | search:read |
views.open | bot | - |
users.info | bot | users:read |
chat.postMessage | bot | chat:write chat:write.customize |
Slackからリクエストを送るエンドポイントを設定する
アプリをインストール *4
おわりに
今回は、フォームの入力値をオートフィルしたかった&メッセージの見た目をいい感じにしたかったためアプリを作成しましたが、そこまでの機能が必要でなければ、ワークフロー *5を用いても作成することができます。Slackの活用方法は、無限大ですね。
それでは皆様もよきSlackライフを!