mcpField
D
discord-bot-architect

discord-bot-architect

github.com/davila7開発33.1k332 回インストール今日更新
リポジトリ

discord-bot-architect について

discord-bot-architectは、JavaScriptやPythonでDiscordボットを開発する人向けのスキルです。スラッシュコマンド、ゲートウェイイベント、インタラクティブコンポーネントの実装パターンと、よくある注意点を扱います。

主な機能

  1. 01Discord.js v14ボットの構成
  2. 02Pycordボットの構成
  3. 03スラッシュコマンドのパターン
  4. 04ボタン、メニュー、モーダル
  5. 05ボット開発の注意点

活用シーン

  1. 01JavaScriptまたはTypeScriptでDiscordボットを開始
  2. 02アプリケーションコマンド対応のPythonボット開発
  3. 03インタラクティブなメニューやフォームの追加
  4. 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## モーダルは即座に表示する

コメント