discord-bot-architect について
discord-bot-architectは、JavaScriptやPythonでDiscordボットを開発する人向けのスキルです。スラッシュコマンド、ゲートウェイイベント、インタラクティブコンポーネントの実装パターンと、よくある注意点を扱います。
主な機能
- 01Discord.js v14ボットの構成
- 02Pycordボットの構成
- 03スラッシュコマンドのパターン
- 04ボタン、メニュー、モーダル
- 05ボット開発の注意点
活用シーン
- 01JavaScriptまたはTypeScriptでDiscordボットを開始
- 02アプリケーションコマンド対応のPythonボット開発
- 03インタラクティブなメニューやフォームの追加
- 04コマンドやイベントループの一般的な問題の回避
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ davila7/ claude-code-templates --skill discord-bot-architectインストール後、Claude Code で/discord-bot-architectを入力して読み込まれたか確認します。
詳細
Discord Bot Architect
パターン
Discord.js v14 の基盤
Discord.js v14 とスラッシュコマンドによる、最新の Discord ボットのセットアップ
使用する場面: ['JavaScript/TypeScript で Discord ボットを構築する場合', 'イベントを伴う完全なゲートウェイ接続が必要な場合', '複雑なインタラクションを備えたボットを構築する場合']
```javascript
// src/index.js
const { Client, Collection, GatewayIntentBits, Events } = require('discord.js');
const fs = require('node:fs');
const path = require('node:path');
require('dotenv').config();
// Create client with minimal required intents
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
// Add only what you need:
// GatewayIntentBits.GuildMessages,
// GatewayIntentBits.MessageContent, // PRIVILEGED - avoid if possible
]
});
// Load commands
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);
}
}
// Load events
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('Replies with Pong!'),
async execute(interaction) {
const sent = await interaction.reply({
content: 'Pinging...',
fetchReply: true
});
const latency = sent.createdTimestamp - interaction.createdTimestamp;
await interaction.editReply(`Pong! Latency: ${latency}ms`);
}
};
// src/events/interactionCreate.js
const { Events } = require('discord.js');
module.exports = {
name: Event
Pycord ボットの基盤
Pycord (Python) とアプリケーションコマンドを使用した Discord ボット
使用する場面: ['Python で Discord ボットを構築する場合', 'async/await パターンを好む場合', '優れたスラッシュコマンドのサポートが必要な場合']
```python
# main.py
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
# Configure intents - only enable what you need
intents = discord.Intents.default()
# intents.message_content = True # PRIVILEGED - avoid if possible
# intents.members = True # PRIVILEGED
bot = commands.Bot(
command_prefix="!", # Legacy, prefer slash commands
intents=intents
)
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
# Sync commands (do this carefully - see sharp edges)
# await bot.sync_commands()
# Slash command
@bot.slash_command(name="ping", description="Check bot latency")
async def ping(ctx: discord.ApplicationContext):
latency = round(bot.latency * 1000)
await ctx.respond(f"Pong! Latency: {latency}ms")
# Slash command with options
@bot.slash_command(name="greet", description="Greet a user")
async def greet(
ctx: discord.ApplicationContext,
user: discord.Option(discord.Member, "User to greet"),
message: discord.Option(str, "Custom message", required=False)
):
msg = message or "Hello!"
await ctx.respond(f"{user.mention}, {msg}")
# Load cogs
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="Bot information")
async def info(self, ctx: discord.ApplicationContext):
embed = discord.Embed(
title="Bot Info",
description="A helpful Discord bot",
color=discord.Color.blue()
)
embed.add_field(name="Servers", value=len(self.bot.guilds))
embed.add_field(name="Latency", value=f"{round(self.bot.latency * 1000)}ms")
await ctx.respond(embed=embed)
@commands.Cog.
インタラクティブコンポーネントのパターン
ボタン、セレクトメニュー、モーダルを使用した、リッチな UX の実現
使用する場面: ['インタラクティブなユーザーインターフェースが必要な場合', 'スラッシュコマンドのオプション以外でユーザー入力を収集する場合', 'メニュー、確認画面、フォームを構築する場合']
```javascript
// Discord.js - Buttons and Select Menus
const {
SlashCommandBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
StringSelectMenuBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle
} = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('menu')
.setDescription('Shows an interactive menu'),
async execute(interaction) {
// Button row
const buttonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('confirm')
.setLabel('Confirm')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setLabel('Documentation')
.setURL('https://discord.js.org')
.setStyle(ButtonStyle.Link) // Link buttons don't emit events
);
// Select menu row (one per row, takes all 5 slots)
const selectRow = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId('select-role')
.setPlaceholder('Select a role')
.setMinValues(1)
.setMaxValues(3)
.addOptions([
{ label: 'Developer', value: 'dev', emoji: '💻' },
{ label: 'Designer', value: 'design', emoji: '🎨' },
{ label: 'Community', value: 'community', emoji: '🎉' }
])
);
await interaction.reply({
content: 'Choose an option:',
components: [buttonRow, selectRow]
});
// Collect responses
const collector = interaction.channel.createMessageComponentCollector({
filter: i => i.user.id === interaction.user.id,
time: 60_000 // 60 seconds timeout
});
collector.on('collect', async i => {
if (i.customId === 'confirm') {
await i.update({ content: 'Confirmed!', components: [] });
collector.stop();
} else if (i.custo
アンチパターン
❌ コマンドに Message Content を使用する
問題点: Message Content Intent は特権インテントであり、ボットのコマンドには非推奨です。 スラッシュコマンドが推奨される方法です。
❌ 起動のたびにコマンドを同期する
問題点: コマンドの登録にはレート制限があります。グローバルコマンドの反映には最大 1 時間かかります。 起動のたびに同期すると API 呼び出しが無駄になり、制限に達する可能性があります。
❌ イベントループをブロックする
問題点: Discord ゲートウェイは定期的なハートビートを必要とします。 ブロッキング処理があるとハートビートを逃し、切断につながります。
⚠️ 注意すべき点
| 問題 | 重大度 | 解決策 |
|---|---|---|
| 問題 | critical | ## 即座に応答し、処理は後で行う |
| 問題 | critical | ## ステップ 1: Developer Portal で有効化する |
| 問題 | high | ## 別のデプロイスクリプトを使用する(起動時には行わない) |
| 問題 | critical | ## トークンをハードコーディングしない |
| 問題 | high | ## 正しい招待 URL を生成する |
| 問題 | medium | ## 開発時: ギルドコマンドを使用する |
| 問題 | medium | ## イベントループをブロックしない |
| 問題 | medium | ## モーダルは即座に表示する |