名称: discord-bot-architect 描述: “专注于构建生产就绪的 Discord 机器人的专业技能。涵盖 Discord.js(JavaScript)和 Pycord(Python),网关意图,斜杠命令,交互组件,速率限制,和分片。” 来源: vibeship-spawner-skills (Apache 2.0)
Discord 机器人架构师
模式
Discord.js v14 基础
使用 Discord.js v14 和斜杠命令的现代 Discord 机器人设置
使用时机: [‘构建使用 JavaScript/TypeScript 的 Discord 机器人’, ‘需要完整网关连接和事件’, ‘构建具有复杂交互的机器人’]
// src/index.js
const { Client, Collection, GatewayIntentBits, Events } = require('discord.js');
const fs = require('node:fs');
const path = require('node:path');
require('dotenv').config();
// 创建客户端,仅启用所需意图
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
// 只添加需要的:
// GatewayIntentBits.GuildMessages,
// GatewayIntentBits.MessageContent, // 特权意图 - 尽可能避免
]
});
// 加载命令
client.commands = new Collection();
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
if ('data' in command && 'execute' in command) {
client.commands.set(command.data.name, command);
}
}
// 加载事件
const eventsPath = path.join(__dirname, 'events');
const eventFiles = fs.readdirSync(eventsPath).filter(f => f.endsWith('.js'));
for (const file of eventFiles) {
const filePath = path.join(eventsPath, file);
const event = require(filePath);
if (event.once) {
client.once(event.name, (...args) => event.execute(...args));
} else {
client.on(event.name, (...args) => event.execute(...args));
}
}
client.login(process.env.DISCORD_TOKEN);
// src/commands/ping.js
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('回复 Pong!'),
async execute(interaction) {
const sent = await interaction.reply({
content: '正在 Ping...',
fetchReply: true
});
const latency = sent.createdTimestamp - interaction.createdTimestamp;
await interaction.editReply(`Pong! 延迟: ${latency}ms`);
}
};
// src/events/interactionCreate.js
const { Events } = require('discord.js');
module.exports = {
name: Events.InteractionCreate,
async execute(interaction) {
if (!interaction.isCommand()) return;
const command = interaction.client.commands.get(interaction.commandName);
if (!command) {
console.error(`未找到命令: ${interaction.commandName}`);
return;
}
try {
await command.execute(interaction);
} catch (error) {
console.error(error);
await interaction.reply({ content: '执行命令时出错', ephemeral: true });
}
}
};
Pycord 机器人基础
使用 Pycord(Python)和应用程序命令的 Discord 机器人
使用时机: [‘构建使用 Python 的 Discord 机器人’, ‘偏好异步/等待模式’, ‘需要良好的斜杠命令支持’]
# main.py
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
# 配置意图 - 仅启用需要的
intents = discord.Intents.default()
# intents.message_content = True # 特权意图 - 尽可能避免
# intents.members = True # 特权意图
bot = commands.Bot(
command_prefix="!", # 传统命令,推荐使用斜杠命令
intents=intents
)
@bot.event
async def on_ready():
print(f"登录为 {bot.user}")
# 同步命令(小心使用 - 见注意事项)
# await bot.sync_commands()
# 斜杠命令
@bot.slash_command(name="ping", description="检查机器人延迟")
async def ping(ctx: discord.ApplicationContext):
latency = round(bot.latency * 1000)
await ctx.respond(f"Pong! 延迟: {latency}ms")
# 带选项的斜杠命令
@bot.slash_command(name="greet", description="问候用户")
async def greet(
ctx: discord.ApplicationContext,
user: discord.Option(discord.Member, "要问候的用户"),
message: discord.Option(str, "自定义消息", required=False)
):
msg = message or "你好!"
await ctx.respond(f"{user.mention}, {msg}")
# 加载扩展
for filename in os.listdir("./cogs"):
if filename.endswith(".py"):
bot.load_extension(f"cogs.{filename[:-3]}")
bot.run(os.environ["DISCORD_TOKEN"])
# cogs/general.py
import discord
from discord.ext import commands
class General(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.slash_command(name="info", description="机器人信息")
async def info(self, ctx: discord.ApplicationContext):
embed = discord.Embed(
title="机器人信息",
description="一个有用的 Discord 机器人",
color=discord.Color.blue()
)
embed.add_field(name="服务器数量", value=len(self.bot.guilds))
embed.add_field(name="延迟", value=f"{round(self.bot.latency * 1000)}ms")
await ctx.respond(embed=embed)
@commands.Cog.listener()
async def on_ready(self):
print("General cog loaded")
def setup(bot):
bot.add_cog(General(bot))
交互组件模式
使用按钮、选择菜单和模态对话框实现丰富的用户体验
使用时机: [‘需要交互式用户界面’, ‘收集用户输入超越斜杠命令选项’, ‘构建菜单、确认或表单’]
// Discord.js - 按钮和选择菜单
const {
SlashCommandBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
StringSelectMenuBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle
} = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('menu')
.setDescription('显示交互式菜单'),
async execute(interaction) {
// 按钮行
const buttonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('confirm')
.setLabel('确认')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('cancel')
.setLabel('取消')
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setLabel('文档')
.setURL('https://discord.js.org')
.setStyle(ButtonStyle.Link) // 链接按钮不触发事件
);
// 选择菜单行(每行一个,占用全部5个槽位)
const selectRow = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId('select-role')
.setPlaceholder('选择一个角色')
.setMinValues(1)
.setMaxValues(3)
.addOptions([
{ label: '开发者', value: 'dev', emoji: '💻' },
{ label: '设计师', value: 'design', emoji: '🎨' },
{ label: '社区', value: 'community', emoji: '🎉' }
])
);
await interaction.reply({
content: '选择一个选项:',
components: [buttonRow, selectRow]
});
// 收集响应
const collector = interaction.channel.createMessageComponentCollector({
filter: i => i.user.id === interaction.user.id,
time: 60_000 // 60秒超时
});
collector.on('collect', async i => {
if (i.customId === 'confirm') {
await i.update({ content: '已确认!', components: [] });
collector.stop();
} else if (i.customId === 'cancel') {
await i.update({ content: '已取消', components: [] });
collector.stop();
} else if (i.customId === 'select-role') {
const roles = i.values.join(', ');
await i.update({ content: `你选择了: ${roles}`, components: [] });
}
});
collector.on('end', collected => {
console.log(`收集到 ${collected.size} 个交互`);
});
}
};
反模式
❌ 使用消息内容进行命令
为什么不好: 消息内容意图是特权性的,并且对于机器人命令已弃用。斜杠命令是推荐的方法。
❌ 每次启动都同步命令
为什么不好: 命令注册有速率限制。全局命令传播可能需要长达1小时。每次启动同步浪费 API 调用,并可能触及限制。
❌ 阻塞事件循环
为什么不好: Discord 网关需要定期心跳。阻塞操作会导致错过心跳和断开连接。
⚠️ 注意事项
| 问题 | 严重性 | 解决方案 |
|---|---|---|
| 未启用特权意图 | 关键 | ## 立即在开发者门户启用 |
| 速率限制 | 高 | ## 使用单独的部署脚本(不要在启动时) |
| 硬编码令牌 | 关键 | ## 永远不要硬编码令牌 |
| 邀请 URL 错误 | 高 | ## 生成正确的邀请 URL |
| 开发时全局命令 | 中等 | ## 开发时使用公会命令 |
| 阻塞事件循环 | 中等 | ## 永远不要阻塞事件循环 |
| 模态对话框延迟 | 中等 | ## 立即显示模态对话框 |