App Home のタブを Cloud Functions で
構築した話
自己紹介
- @hey_cube
- 株式会社オプト
- React / Rails / GCP
今日話すこと
- App Home とは
- どんなものを作ったか
- 実装の紹介
- 開発を振り返って
- まとめ
App Home とは
- ユーザーと Slack を 1 対 1 で繋ぐスペース
- ユーザーに直感的にアプリを使ってもらうために追加された
- About・Messages・Home タブが存在する
どんなものを作ったか
- 社内限定で配信している YouTube Live のプッシュ通知を Messages タブで送る
- アーカイブの再生リストを Home タブに表示する
実装の紹介
- TypeScript
- Google Cloud Functions
- Google Apps Script
package.json
"devDependencies": {
"@google-cloud/functions-emulator": "^1.0.0-beta.6",
"@types/follow-redirects": "^1.8.0",
"@types/node": "^13.7.1",
"ts-node-dev": "^1.0.0-pre.44",
"typescript": "^3.7.5"
},
"dependencies": {
"follow-redirects": "^1.10.0"
}
コードが長いので
適当に分割します
Home タブ
実行の流れ
- ユーザーが Home タブを開く or ボタンを押す
- Slack API Server が Cloud Functions に POST リクエストを投げる
- Cloud Functions が Slack API Server (/api/views.publish) に POST リクエストを投げる
Slack API Server からの POST を受ける
export async function post(req: any, res: any) {
const body = req.body;
const payload = JSON.parse(body.payload || "{}");
// Home タブの構築
if (
body.type === "event_callback" &&
body.event.type === "app_home_opened" &&
body.event.tab === "home"
) {
const {
body: { playlists, userIds }
} = JSON.parse(await getMetadata());
await buildHome(body.event.user, playlists, userIds);
res.status(200).send(body.challenge);
}
}
export async function post(req: any, res: any) {
const body = req.body;
const payload = JSON.parse(body.payload || "{}");
// チャンネル登録・登録解除
else if (
payload.type === "block_actions" &&
(payload.actions?.[0]?.text?.text === "チャンネル登録" ||
payload.actions?.[0]?.text?.text === "登録済み")
) {
await toggleChannelSubscription(payload.user.id);
res.status(200).send("");
const {
body: { playlists, userIds }
} = JSON.parse(await getMetadata());
await buildHome(payload.user.id, playlists, userIds);
console.log("built.");
}
}
Slack API Server からの POST を受ける
async function buildHome(userId: string, playlists: any[], userIds: string[]) {
const host = "slack.com";
const path = "/api/views.publish";
const headers = {
"Content-type": "application/json",
Authorization:
"Bearer xoxb-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};
const playlistsBlock = playlists.map((e: any) => {/* GAS のデータを加工 */});
const json = {/* block を構築 */};
const options = { host, path, method: "POST", headers };
return new Promise((resolve, reject) => {
const req = request(options, (res) => resolve(res.statusCode));
req.on("error", (e) => reject(e.message));
req.end(JSON.stringify(json));
});
}
Slack API Server に POST を投げる
Messages タブ
実行の流れ
- 僕が Cloud Functions に POST リクエストを投げる
- Cloud Functions が Slack API Server (/api/chat.postMessage) に POST リクエストを投げる
export async function post(req: any, res: any) {
const body = req.body;
const payload = JSON.parse(body.payload || "{}");
// プッシュ通知の送信
else if (body.liveUrl) {
const {
body: { userIds }
} = JSON.parse(await getMetadata());
await pushNotification(body.liveUrl, userIds);
res.status(200).send("");
}
}
僕からの POST を受ける
async function pushNotification(liveUrl: string, userIds: string[]) {
const host = "slack.com";
const path = "/api/chat.postMessage";
const headers = {
"Content-type": "application/json",
Authorization:
"Bearer xoxb-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
};
const options = { host, path, method: "POST", headers };
const promises = userIds.map((u) => { /* GAS のデータを加工 */ };
return new Promise((resolve, reject) => {
const req = request(options, (res) => resolve(res.statusCode));
req.on("error", (e) => reject(e.message));
req.end(JSON.stringify(json));
});
});
return Promise.all(promises);
}
Slack API Server に POST を投げる
その他
export async function post(req: any, res: any) {
const body = req.body;
const payload = JSON.parse(body.payload || "{}");
// それ以外の場合は適当にレスポンスを返す
else {
res.status(200).send(body.challenge);
}
}
Slack API Server に認めてもらう
懺悔
- 本当は Slack からのリクエストが妥当か検証しないといけない
- Bolt を使えば簡単に実現できる
- が、Bolt は FaaS に対応していない
- 自力で実現できなかったのでサボりました()
Block Kit Builder
- Slack 用の UI を構築するためのツール
- ブラウザ上で Messages・Modals・App Home Beta の UI が試せる
- いくつかテンプレートも存在する
Block Kit Builder の App Home Preview のテンプレートで
要素同士の隙間を開けるために画像を使ってるらしい
{
"type": "context",
"elements": [
{
"type": "image",
"image_url": "https://api.slack.com/img/blocks/bkb_template_images/placeholder.png",
"alt_text": "placeholder"
}
]
}
Block Kit Builder での空白の表現方法
開発を振り返って
- 丸2日くらいかかった
- 全部 GAS で良かったかも知れない
- というか Glitch に Express + Bolt なサーバーを立てる方が多分楽だった
- 作りたいものは一応できたので満足
まとめ
- Slack 上でアプリが作れるので便利
- コンテンツを表示する Home
- ユーザーとやり取りする Messages
- UI は Block Kit Builder で作ろう
- 変に自前で構築せずに SDK を使おう
App Home のタブを Cloud Functions で構築した話
By hey_cube
App Home のタブを Cloud Functions で構築した話
Slack App の Home・Messages タブを Cloud Functions + GAS で構築した様子を解説 / https://opt.connpass.com/event/172661/
- 1,595