制作一个 Discord 机器人
在 Discord 开发者门户上创建一个应用 #
🌐 Create an application on Discord Developer portal
- 前往 https://discord.com/developers/applications(如有需要,请使用你的 Discord 账号登录)。
- 点击你头像左边的 新建应用 按钮。
- 给你的应用命名,然后点击 创建。
- 进入 机器人 部分,点击 添加机器人,最后点击 是,执行! 来确认。
我们创建了一个新应用,它将容纳我们的斜杠命令。不要关闭这个标签页,因为在开发过程中我们需要从这个应用页面获取信息。
🌐 A new application is created which will hold our Slash Command. Don't close the tab as we need information from this application page throughout our development.
在我们写代码之前,我们需要用 curl 调用一个 Discord 接口,在我们的应用里注册一个 Slash 命令。
🌐 Before we can write some code, we need to curl a discord endpoint to register a Slash Command in our app.
在 Bot 部分填入 DISCORD_BOT_TOKEN 的令牌,在页面的 General Information 部分填入 CLIENT_ID 的 ID,然后在终端运行命令。
🌐 Fill DISCORD_BOT_TOKEN with the token available in the Bot section and CLIENT_ID with the ID available on the General Information section of the page and run the command on your terminal.
1BOT_TOKEN='replace_me_with_bot_token'2CLIENT_ID='replace_me_with_client_id'3curl -X POST \4-H 'Content-Type: application/json' \5-H "Authorization: Bot $BOT_TOKEN" \6-d '{"name":"hello","description":"Greet a person","options":[{"name":"name","description":"The name of the person","type":3,"required":true}]}' \7"https://discord.com/api/v8/applications/$CLIENT_ID/commands"这将注册一个名为 hello 的斜杠命令,它接受一个名为 name 的字符串类型参数。
🌐 This will register a Slash Command named hello that accepts a parameter named name of type string.
代码 #
🌐 Code
1// Sift is a small routing library that abstracts away details like starting a2// listener on a port, and provides a function (serve) that has an API3// to invoke a function for a specific path.45// TweetNaCl is a cryptography library that we use to verify requests6// from Discord.7import nacl from 'https://cdn.skypack.dev/tweetnacl@v1.0.3?dts'8import { json, serve, validateRequest } from 'https://deno.land/x/sift@0.6.0/mod.ts'910enum DiscordCommandType {11 Ping = 1,12 ApplicationCommand = 2,13}1415// For all requests to "/" endpoint, we want to invoke home() handler.16serve({17 '/discord-bot': home,18})1920// The main logic of the Discord Slash Command is defined in this function.21async function home(request: Request) {22 // validateRequest() ensures that a request is of POST method and23 // has the following headers.24 const { error } = await validateRequest(request, {25 POST: {26 headers: ['X-Signature-Ed25519', 'X-Signature-Timestamp'],27 },28 })29 if (error) {30 return json({ error: error.message }, { status: error.status })31 }3233 // verifySignature() verifies if the request is coming from Discord.34 // When the request's signature is not valid, we return a 401 and this is35 // important as Discord sends invalid requests to test our verification.36 const { valid, body } = await verifySignature(request)37 if (!valid) {38 return json(39 { error: 'Invalid request' },40 {41 status: 401,42 }43 )44 }4546 const { type = 0, data = { options: [] } } = JSON.parse(body)47 // Discord performs Ping interactions to test our application.48 // Type 1 in a request implies a Ping interaction.49 if (type === DiscordCommandType.Ping) {50 return json({51 type: 1, // Type 1 in a response is a Pong interaction response type.52 })53 }5455 // Type 2 in a request is an ApplicationCommand interaction.56 // It implies that a user has issued a command.57 if (type === DiscordCommandType.ApplicationCommand) {58 const { value } = data.options.find(59 (option: { name: string; value: string }) => option.name === 'name'60 )61 return json({62 // Type 4 responds with the below message retaining the user's63 // input at the top.64 type: 4,65 data: {66 content: `Hello, ${value}!`,67 },68 })69 }7071 // We will return a bad request error as a valid Discord request72 // shouldn't reach here.73 return json({ error: 'bad request' }, { status: 400 })74}7576/** Verify whether the request is coming from Discord. */77async function verifySignature(request: Request): Promise<{ valid: boolean; body: string }> {78 const PUBLIC_KEY = Deno.env.get('DISCORD_PUBLIC_KEY')!79 // Discord sends these headers with every request.80 const signature = request.headers.get('X-Signature-Ed25519')!81 const timestamp = request.headers.get('X-Signature-Timestamp')!82 const body = await request.text()83 const valid = nacl.sign.detached.verify(84 new TextEncoder().encode(timestamp + body),85 hexToUint8Array(signature),86 hexToUint8Array(PUBLIC_KEY)87 )8889 return { valid, body }90}9192/** Converts a hexadecimal string to Uint8Array. */93function hexToUint8Array(hex: string) {94 return new Uint8Array(hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16)))95}部署斜杠命令处理器 #
🌐 Deploy the slash command handler
1supabase functions deploy discord-bot --no-verify-jwt2supabase secrets set DISCORD_PUBLIC_KEY=your_public_key在 Supabase 控制台中导航到你的函数详情,获取你的端点 URL。
🌐 Navigate to your Function details in the Supabase Dashboard to get your Endpoint URL.
把 Discord 应用配置成使用我们的 URL 作为交互端点 URL #
🌐 Configure Discord application to use our URL as interactions endpoint URL
- 返回你的应用(Greeter)页面,在 Discord 开发者门户
- 在 INTERACTIONS ENDPOINT URL 字段中填写 URL,然后点击 保存更改。
应用现在已经准备好了。继续下一步进行安装吧。
🌐 The application is now ready. Proceed to the next section to install it.
在你的 Discord 服务器上安装斜杠命令 #
🌐 Install the slash command on your Discord server
所以要使用 hello 斜线命令,我们需要在 Discord 服务器上安装我们的问候应用。步骤如下:
🌐 So to use the hello Slash Command, we need to install our Greeter application on our Discord server. Here are the steps:
- 前往 Discord 开发者门户中 Discord 应用页面的 OAuth2 部分
- 选择
applications.commands范围,然后点击下面的 复制 按钮。 - 现在在浏览器中粘贴并访问这个网址。选择你的服务器,然后点击 授权。
打开 Discord,输入 /Promise 然后按 Enter。
🌐 Open Discord, type /Promise and press Enter.
本地运行 #
🌐 Run locally
1supabase functions serve discord-bot --no-verify-jwt --env-file ./supabase/.env.local2ngrok http 54321