构建 Discord 机器人
在 Discord 开发者门户创建应用
- 访问 https://discord.com/developers/applications(如需登录请使用您的 Discord 账号)。
- 点击个人资料图片左侧的 New Application(新建应用)按钮。
- 为应用命名后点击 Create(创建)。
- 进入 Bot(机器人)版块,点击 Add Bot(添加机器人),最后点击 Yes, do it!(确认)完成创建。
现在已创建一个新应用来承载我们的斜杠命令。请不要关闭此页面,因为在后续开发中我们需要从这里获取信息。
在编写代码之前,我们需要通过 curl 调用 Discord 接口来注册一个斜杠命令到应用中。
将 DISCORD_BOT_TOKEN
替换为 Bot 版块中的令牌,CLIENT_ID
替换为页面 General Information(基本信息)版块中的 ID,然后在终端运行以下命令:
1234567BOT_TOKEN='replace_me_with_bot_token'CLIENT_ID='replace_me_with_client_id'curl -X POST \-H 'Content-Type: application/json' \-H "Authorization: Bot $BOT_TOKEN" \-d '{"name":"hello","description":"Greet a person","options":[{"name":"name","description":"The name of the person","type":3,"required":true}]}' \"https://discord.com/api/v8/applications/$CLIENT_ID/commands"
这将注册一个名为 hello
的斜杠命令,该命令接受一个必填的字符串类型参数 name
。
代码实现
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889// Sift 是一个小型路由库,它抽象了诸如在端口上启动监听器等细节,// 并提供了一个简单函数(serve),该函数具有为特定路径调用函数的API。import { json, serve, validateRequest } from 'https://deno.land/x/sift@0.6.0/mod.ts'// TweetNaCl 是一个加密库,我们用它来验证来自 Discord 的请求。import nacl from 'https://cdn.skypack.dev/tweetnacl@v1.0.3?dts'enum DiscordCommandType { Ping = 1, ApplicationCommand = 2,}// 对于所有发送到"/"端点的请求,我们都希望调用home()处理函数。serve({ '/discord-bot': home,})// Discord 斜杠命令的主要逻辑定义在这个函数中。async function home(request: Request) { // validateRequest() 确保请求是POST方法且包含以下头部信息。 const { error } = await validateRequest(request, { POST: { headers: ['X-Signature-Ed25519', 'X-Signature-Timestamp'], }, }) if (error) { return json({ error: error.message }, { status: error.status }) } // verifySignature() 验证请求是否来自 Discord。 // 当请求签名无效时,我们返回401状态码,这很重要, // 因为 Discord 会发送无效请求来测试我们的验证机制。 const { valid, body } = await verifySignature(request) if (!valid) { return json( { error: '无效请求' }, { status: 401, } ) } const { type = 0, data = { options: [] } } = JSON.parse(body) // Discord 会执行 Ping 交互来测试我们的应用。 // 请求中的类型1表示Ping交互。 if (type === DiscordCommandType.Ping) { return json({ type: 1, // 响应中的类型1表示Pong交互响应类型。 }) } // 请求中的类型2是ApplicationCommand交互。 // 表示用户已发出命令。 if (type === DiscordCommandType.ApplicationCommand) { const { value } = data.options.find( (option: { name: string; value: string }) => option.name === 'name' ) return json({ // 类型4的响应会保留用户输入在顶部,并返回以下消息。 type: 4, data: { content: `你好,${value}!`, }, }) } // 我们将返回一个错误请求响应,因为有效的 Discord 请求不应该执行到这里。 return json({ error: '错误请求' }, { status: 400 })}/** 验证请求是否来自 Discord。 */async function verifySignature(request: Request): Promise<{ valid: boolean; body: string }> { const PUBLIC_KEY = Deno.env.get('DISCORD_PUBLIC_KEY')! // Discord 会在每个请求中发送这些头部信息。 const signature = request.headers.get('X-Signature-Ed25519')! const timestamp = request.headers.get('X-Signature-Timestamp')! const body = await request.text() const valid = nacl.sign.detached.verify( new TextEncoder().encode(timestamp + body), hexToUint8Array(signature), hexToUint8Array(PUBLIC_KEY) ) return { valid, body }}/** 将十六进制字符串转换为 Uint8Array。 */function hexToUint8Array(hex: string) { return new Uint8Array(hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16)))}
部署斜杠命令处理器
12supabase functions deploy discord-bot --no-verify-jwtsupabase secrets set DISCORD_PUBLIC_KEY=your_public_key
在 Supabase 仪表板中导航至您的函数详情页面获取端点 URL。
配置 Discord 应用使用我们的 URL 作为交互端点
- 返回 Discord 开发者门户中您的应用(Greeter)页面
- 在交互端点 URL字段中填入获取的URL,点击保存更改
应用现已配置完成。让我们继续下一节进行安装。
在 Discord 服务器上安装斜杠命令
要使用 hello
斜杠命令,我们需要将 Greeter 应用安装到 Discord 服务器。步骤如下:
- 在 Discord 开发者门户中进入应用的OAuth2部分
- 选择
applications.commands
权限范围,点击下方的复制按钮 - 粘贴并访问该URL,选择您的服务器后点击授权
打开 Discord,输入 /Promise
并按回车。
本地运行
12supabase functions serve discord-bot --no-verify-jwt --env-file ./supabase/.env.localngrok http 54321