
- README.md
开发一个 Discord ChatGPT 机器人
我们在构建什么
在本教程中,我们将使用 JavaScript 与 AirCode 引导你构建一个 Discord 应用程序,并且使用命令行方式让它与 ChatGPT 交互。
最终效果图:
在本教程使用到的资源
- AirCode 用于构建和托管 Node.js 应用程序的在线平台。
- discord.js discord-interactions Discord API 和类型。
- OpenAI 用于向 ChatGPT 发送消息。
第一步: 创建应用和机器人
首先,如果你还没有 Discord 应用,请在开发者后台创建一个应用:
- 前往 Discord Developer Portal (使用你的 Discord 帐户登录)。点击右上角的 "新建应用" 按钮。给应用取一个名称,然后点击 "创建"。
- 复制你的 Public Key 公钥和 Application ID 应用 ID,并将它们保存在本地某个地方(我们稍后会用到)
- 在屏幕左侧的 "Bot" 选项卡上点击 "Bot" 按钮,然后点击右侧的 "添加 Bot" 按钮。创建完机器人后,重置你的机器人 Token,并将其安全地保存在某个地方(我们稍后会用到)
创建应用和机器人后,你可以添加图标、描述等进行自定义。
第二步: 设置范围和权限
点击左侧边栏中的 OAuth2,然后选择 URL Generator。
添加两个 Scope 范围:
- applications.commands:允许你的应用创建命令行。
- bot:添加你的机器人用户,在选择 bot 后,你还可以为你的机器人选择不同的权限。目前只需选中 "Send Messages" 进行发送消息。
复制上面生成的 URL,并将其粘贴到浏览器中。你将会被引导完成授权流程,如下图选择一个你的 Discord 服务器进行授权添加 Bot 以及安装命令行。
授权完成后,回到你的 Discord 服务器中,能看到频道中消息提示机器人 GPT 已经加入了服务器。✨
第三步: Get a copy 并且部署应用
本指南使用 AirCode 来托管代码。在获取副本之前,请确保你拥有一个 AirCode 帐户。
- 前往 Discord ChatGPT 应用. 点击 "Get a copy" 按钮。
- 你将会重定向到创建应用页面,使用默认名称或输入自己的应用名称,然后点击“创建”。
将 Bot Token、OpenAI Key、Public Key、Application ID、先前从平台保存的内容粘贴到环境变量中。确保它们在正确的 key value 键值位置。
如果没有 OpenAI API Key,需要准备帐号登陆 OpenAI 的控制台,点「Create new secret key」生成并且复制你的 OpenAI Key。
点击“Deploy”按钮来部署所有文件,在部署后环境变量的更改将生效。
第四步: 注册命令行
该项目包含一个注册命令的脚本,你可以使用它来安装命令,该脚本定义在 register-command.js 中。
选择 register-command.js 文件,将会看到如下的一个调用网址,复制该网址并粘贴到浏览器的 URL 地址栏中进行访问。
你将在你的 Discord 服务器中注册 /chatgpt 命令行。
第五步: 处理交互
为了使你的应用能够接收命令行请求,Discord 需要一个公共 URL 来发送它们。
选择 interactions.js 文件,你将找到如下的一个调用 URL,复制该链接。
返回 Discord 应用程序,在应用的“常规信息”页面上,有一个“交互式端点 URL”选项,将从 AirCode 复制的调用 URL 粘贴到里面。
点击“保存更改”按钮,确保保存成功。
通过输入命令 "/chatgpt" 来测试你的 ChatGPT 机器人。
反馈
- 加入我们的 Discord 服务器 获取帮助和讨论。
- interactions.js
1// @see https://docs.aircode.io/guide/functions/ 2const axios = require('axios'); 3const nacl = require('tweetnacl'); 4const { Configuration, OpenAIApi } = require('openai'); 5 6const { 7 EmbedBuilder, 8} = require('discord.js'); 9 10const { 11 verifyKey, 12 InteractionType, 13 InteractionResponseType, 14} = require('discord-interactions'); 15 16const apiKey = process.env.OPENAI_KEY; 17 18const discordToken = process.env.DISCORD_BOT_TOKEN; 19 20// Your public key can be found on your application in the Developer Portal 21const discordPublicKey = process.env.DISCORD_PUBLIC_KEY; 22 23// Slash command's name you use tointeract with bots on Discord after typing / 24const slashCommandName = 'chatgpt'; 25 26// Use openAI API to fetch ChatGPT completion 27const getOpenAIChatCompletion = async (question) => { 28 29 try { 30 const configuration = new Configuration({ 31 apiKey, 32 }); 33 34 const openai = new OpenAIApi(configuration); 35 36 const completion = await openai.createChatCompletion({ 37 // OpenAI models https://platform.openai.com/docs/models 38 model: 'gpt-3.5-turbo', 39 messages: [{ role: 'assistant', content: question }], 40 }); 41 42 console.log( 43 'ChatGPT completion data:', 44 completion.data.choices[0].message.content.trim() 45 ); 46 return { 47 code: 0, 48 data: completion.data.choices[0].message.content.trim(), 49 }; 50 } catch (error) { 51 console.error(`OpenAI api error: ${error.message}`); 52 return { 53 code: 1, 54 message: `OpenAI api error: ${error.message}`, 55 }; 56 } 57}; 58 59// Build an Embed (rich embed) object for replied message 60const createResponseEmbed = (response) => { 61 const embed = new EmbedBuilder() 62 .setColor('#37393E') 63 .setDescription(response); 64 65 return { embeds: [embed] }; 66}; 67 68module.exports = async function (params, context) { 69 70 console.log('params context', params); 71 72 // Get signature and timestamp from headers 73 const signature = context.headers['X-Signature-Ed25519']; 74 const timestamp = context.headers['X-Signature-Timestamp']; 75 // Get rawBody content 76 const body = context.rawBody; 77 78 // Verify whether the request from discord 79 const isVerified = nacl.sign.detached.verify( 80 Buffer.from(timestamp + body), 81 Buffer.from(signature, 'hex'), 82 Buffer.from(discordPublicKey, 'hex') 83 ); 84 85 if (!isVerified) { 86 context.status(401); 87 return 'Invalid request signature'; 88 } 89 90 // Interaction type and data 91 const { type, id, data, token } = params; 92 93 console.log('params', params); 94 // Handle verification requests reply with PONG 95 if (type === InteractionType.PING) { 96 return { type: InteractionResponseType.PONG }; 97 }; 98 99 // Handle the user registered interaction command 100 if ( 101 type === InteractionType.APPLICATION_COMMAND && 102 data.name === slashCommandName 103 ) { 104 const userInput = data.options[0].value; 105 106 // Immediately acknowledge the interaction 107 try { 108 await axios.post( 109 `https://discord.com/api/v9/interactions/${id}/${token}/callback`, 110 { 111 type: InteractionResponseType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, 112 } 113 ); 114 } catch (error) { 115 console.error( 116 'Error acknowledging the interaction:', 117 error 118 ); 119 return; 120 }; 121 122 const response = await getOpenAIChatCompletion(userInput); 123 console.log('response form OpenAI ChatCompletion: ', response); 124 125 // Patch message 126 if (response.code === 0) { 127 try { 128 await axios.patch( 129 `https://discord.com/api/v9/webhooks/${process.env.DISCORD_APP_ID}/${token}/messages/@original`, 130 { 131 content: `> ${userInput} \n ${response.data}`, 132 } 133 ); 134 } catch (error) { 135 console.error('Error editing the original response:', error.message); 136 return; 137 } 138 }; 139 } 140}; 141
- register-command.js
1// @see https://docs.aircode.io/guide/functions/ 2const axios = require('axios'); 3const Discord = require('discord.js'); 4 5async function registerCommands(appId, commands) { 6 const { DISCORD_BOT_TOKEN } = process.env; 7 8 try { 9 // Use discord sdk register chatgpt command 10 const rest = new Discord.REST({ version: '10' }).setToken( 11 DISCORD_BOT_TOKEN 12 ); 13 const data = await rest.put(Discord.Routes.applicationCommands(appId), { 14 body: commands, 15 }); 16 17 console.log('Command register response:', data); 18 19 if (data) { 20 return { 21 success: true, 22 message: `WOW, slash command ${data 23 .map((command) => `/${command.name}`) 24 .join(' ')} registered`, 25 }; 26 } 27 } catch (err) { 28 console.error('Error from registering commands:', err); 29 } 30} 31 32module.exports = async function (params, context) { 33 console.log('Received params:', params); 34 const slashCommandName = 'chatgpt'; 35 36 const ChatGPTCommand = { 37 name: slashCommandName, 38 description: 'Interact with ChatGPT', 39 options: [ 40 { 41 name: 'prompt', 42 type: 3, 43 description: 'The message sending to ChatGPT', 44 required: true, 45 }, 46 ], 47 }; 48 49 const commands = [ChatGPTCommand]; 50 // Register ChatGPT command 51 const res = await registerCommands(process.env.DISCORD_APP_ID, commands); 52 53 return res; 54}; 55
- Runtime
- Node.js versionnode/v16
- Function execution timeout90 s
- Dependencies
- aircode0.4.1
- axios1.4.0
- discord-interactions3.4.0
- discord.js14.11.0
- openai3.2.1
- tweetnacl1.0.3
- Environments
- DISCORD_BOT_TOKEN
- OPENAI_KEY
- DISCORD_PUBLIC_KEY
- DISCORD_APP_ID