边缘函数

构建 Discord 机器人


在 Discord 开发者门户创建应用

  1. 访问 https://discord.com/developers/applications(如需登录请使用您的 Discord 账号)。
  2. 点击个人资料图片左侧的 New Application(新建应用)按钮。
  3. 为应用命名后点击 Create(创建)。
  4. 进入 Bot(机器人)版块,点击 Add Bot(添加机器人),最后点击 Yes, do it!(确认)完成创建。

现在已创建一个新应用来承载我们的斜杠命令。请不要关闭此页面,因为在后续开发中我们需要从这里获取信息。

在编写代码之前,我们需要通过 curl 调用 Discord 接口来注册一个斜杠命令到应用中。

DISCORD_BOT_TOKEN 替换为 Bot 版块中的令牌,CLIENT_ID 替换为页面 General Information(基本信息)版块中的 ID,然后在终端运行以下命令:

1
2
3
4
5
6
7
BOT_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

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 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)))}

部署斜杠命令处理器

1
2
supabase functions deploy discord-bot --no-verify-jwtsupabase secrets set DISCORD_PUBLIC_KEY=your_public_key

在 Supabase 仪表板中导航至您的函数详情页面获取端点 URL。

配置 Discord 应用使用我们的 URL 作为交互端点

  1. 返回 Discord 开发者门户中您的应用(Greeter)页面
  2. 交互端点 URL字段中填入获取的URL,点击保存更改

应用现已配置完成。让我们继续下一节进行安装。

在 Discord 服务器上安装斜杠命令

要使用 hello 斜杠命令,我们需要将 Greeter 应用安装到 Discord 服务器。步骤如下:

  1. 在 Discord 开发者门户中进入应用的OAuth2部分
  2. 选择 applications.commands 权限范围,点击下方的复制按钮
  3. 粘贴并访问该URL,选择您的服务器后点击授权

打开 Discord,输入 /Promise 并按回车

本地运行

1
2
supabase functions serve discord-bot --no-verify-jwt --env-file ./supabase/.env.localngrok http 54321