Skeet フレームワークを使用して Firebase Functions と Discord Webhooks の統合を探究します。このチャプターでは、Skeet のエコシステム内で Firebase Functions を活用して Discord イベントを処理する方法の詳細に迫ります。リアルタイムで反応するボット機能を作成し、ユーザーインタラクションを高めるための洞察を得ます。
Skeet に Discord Interaction Webhook のエンドポイントを追加する
Discord Interaction Webhook のエンドポイントを追加することで、
Discord ボットの Interaction を受け取ることができます。
skeet add webhook
コマンドを使って、
Discord Interaction Webhook のエンドポイントを追加しましょう。
skeet add webhook
? Select Webhook Type (Use arrow keys)
❯ discord
stripe
✅ discordRouter added 🎉
✅ helloAction added 🎉
✅ helloCommand added 🎉
✅ helloIndex added 🎉
✅ deployCommands added 🎉
✅ helloMessage added 🎉
🔗 ./functions/skeet/src/routings/http/discordRouter.ts
🔗 ./functions/skeet/src/lib/discord/actions/helloAction.ts
🔗 ./functions/skeet/src/lib/discord/commands/hello.ts
🔗 ./functions/skeet/src/lib/discord/commands/index.ts
🔗 ./functions/skeet/src/lib/discord/deploy-commands.ts
🔗 ./functions/skeet/src/lib/discord/messages/helloMessage.ts
✔ Successfully exported to ./functions/skeet/src/index.ts 🎉
Discord Interaction Webhook のエンドポイント及び、
以下のツリーのように discord
ディレクトリが作成され、
必要なファイルが追加されました。
tree -L 2 functions/skeet/src/lib
functions/skeet/src/lib
├── config.ts
├── discord
│ ├── actions
│ ├── commands
│ ├── deploy-commands.ts
│ └── messages
...
- actions - Discord インタラクションアクションの関数を定義するディレクトリ
- commands - Discord スラッシュコマンドの関数を定義するディレクトリ
- messages - Discord ボタンメッセージの関数を定義するディレクトリ
- deploy-commands.ts - Discord スラッシュコマンドをデプロイする関数ファイル
アプリのデプロイ
変更したエンドポイントのみをデプロイするには、
--function
オプションを使います。
skeet deploy --function skeet:discordRouter
カンマ区切りで複数のエンドポイントを指定することもできます。
skeet deploy --function skeet:discordRouter,skeet:root
Discord インタラクション Webhook の登録
ここで、Discord Developer Portal に移動し、
Discord インタラクション Webhook を登録します。
discordRouter
のエンドポイントを表示させるには、
以下のコマンドを実行します。
skeet get https
表示されたエンドポイントをコピーし、
Discord Developer Portal の Webhook URL に設定し保存します。
エンドポイントが無事に登録されると、
Discord Developer Portal の Webhook URL に設定したエンドポイントに
Discord ボットからの Interaction が送信されます。
エンドポイントに問題がある場合は、ここでエラーが起きます。
問題が起きた場合には以下のコマンドでエラーを確認し、
エンドポイントを修正してください。
skeet logs
Discord ボタンメッセージの登録・実行
Discord ボタンメッセージのテンプレートは、
以下のディレクトリにあります。
functions/skeet/src/lib/discord/messages/
デフォルトでは helloMessage.ts
が登録されています。
import {
ButtonStyleTypes,
MessageComponentTypes,
} from '@skeet-framework/discord-utils'
export const helloMessage = () => {
const body = {
content: 'hello button',
components: [
{
type: MessageComponentTypes.ACTION_ROW,
components: [
{
type: MessageComponentTypes.BUTTON,
style: ButtonStyleTypes.PRIMARY,
label: 'Hello Button',
custom_id: 'hello-button',
},
],
},
],
}
return body
}
この関数を root
エンドポイントから呼び出して、
Discord ボタンメッセージを送信します。
functions/skeet/src/routings/http/root.ts
import { onRequest } from 'firebase-functions/v2/https'
import { publicHttpOption } from '@/routings/options'
import { TypedRequestBody } from '@/types/http'
import { RootParams } from '@/types/http/rootParams'
import { helloMessage } from '@/lib/discord/messages/helloMessage'
import { messageChannel } from '@skeet-framework/discord-utils'
import { defineSecret } from 'firebase-functions/params'
const DISCORD_TOKEN = defineSecret('DISCORD_TOKEN')
export const root = onRequest(
{ ...publicHttpOption, secrets: [DISCORD_TOKEN] },
async (req: TypedRequestBody<RootParams>, res) => {
try {
const message = helloMessage()
const channelId = 'your-channel-id'
await messageChannel(DISCORD_TOKEN.value(), channelId, message)
res.json({ status: 'success' })
} catch (error) {
res.status(500).json({ status: 'error', message: String(error) })
}
}
)
DISCORD_TOKEN
は、Secret Manager に登録した Discord のトークンが defineSecret で定義されています。
channelId
は、お好きなチャンネルの ID を設定してください。
それでは skeet s
コマンドでローカルサーバーを起動し、
ログに表示される root
エンドポイントにアクセスしてみましょう。
skeet s
✔ functions: Loaded functions definitions from source: discordRouter, root.
✔ functions[asia-northeast1-discordRouter]: http function initialized (http://127.0.0.1:5001/your-project-id/asia-northeast1/discordRouter).
✔ functions[asia-northeast1-root]: http function initialized (http://127.0.0.1:5001/your-project-id/asia-northeast1/root).
http://127.0.0.1:5001/your-project-id/asia-northeast1/root
にアクセスすると、
Discord ボタンメッセージが送信されます。
ボタンを押してみると
無事にレスポンスが返ってきました 🎉
Discord スラッシュコマンドの登録・実行
Discord スラッシュコマンドのテンプレートは、
以下のディレクトリにあります。
functions/skeet/src/lib/discord/commands/
デフォルトでは hello.ts
が登録されています。
import { SlashCommandBuilder } from '@skeet-framework/discord-utils'
export const data = new SlashCommandBuilder()
.setName('hello')
.setDescription('Hello Slash Command')
.addStringOption((option) =>
option.setName('hey').setDescription('Say something').setRequired(true)
)
この関数を deploy-commands
エンドポイントから呼び出して、
Discord スラッシュコマンドをデプロイします。
skeet deploy --discord
を実行すると、
デプロイ必要な DISCODE_TOKEN
を Secret Manager から呼び出して実行することができます。
skeet deploy --discord
Started refreshing application (/) commands.
Successfully reloaded application (/) commands.
✨ Done in 4.01s.
無事にスラッシュコマンドがデプロイされました🎊
スラッシュコマンドを実行してみると、
無事にレスポンスが返ってきました 🎉
Discord インタラクションアクションの登録
Discord インタラクションアクションのテンプレートは、
以下のディレクトリにあります。
functions/skeet/src/lib/discord/actions/
デフォルトでは helloAction.ts
が登録されています。
import {
DiscordRouterParams,
deferResponse,
updateResponse,
} from '@skeet-framework/discord-utils'
import { Response } from 'firebase-functions/v1'
import { inspect } from 'util'
export const helloAction = async (
res: Response,
db: FirebaseFirestore.Firestore,
discordToken: string,
body: DiscordRouterParams
) => {
console.log('helloAction')
console.log(inspect(body))
await deferResponse(discordToken, body.id, body.token)
// define your logic here
await updateResponse(discordToken, body.application_id, body.token, {
content: 'hello response!',
flags: 64,
})
return true
}
ボタンアクションやスラッシュコマンドが実行されると、
Discord から discordRouter
エンドポイントにリクエストが送信されます。
discordRouter
エンドポイントは、
functions/skeet/src/routings/http/discordRouter.ts
に定義されています。
関数の中の if 文で、
リクエストの種類に応じて、
helloAction
や helloCommand
などの関数を呼び出しています。
デフォルトでは、helloAction
が hello
コマンド、 hello-button
トリガー発動時に呼び出されます。
...
if (req.body.type === InteractionType.PING) {
// To verify the request
res.send({
type: InteractionType.PING,
})
} else if (req.body.type === InteractionType.APPLICATION_COMMAND) {
// Slash command
const options = req.body.data as CommandData
if (options.name.match(/^hello$/)) {
await helloAction(res, db, DISCORD_TOKEN.value(), req.body)
}
} else {
// Button action
const { custom_id } = req.body.data as ActionData
if (custom_id.match(/^hello-button$/)) {
await helloAction(res, db, DISCORD_TOKEN.value(), req.body)
}
}
...
それでは次の章で、これらの関数と Firestore を組み合わせて、
Discord アプリを作成していきましょう。