Use Cases (更新: 2026/6/7)

discord.jsでスラッシュコマンドのDiscord Botを作る:登録・3秒ルール・ホスティングまで

discord.jsでスラッシュコマンドのDiscord Botをゼロから作る手順。コマンド登録、Gateway Intents、interaction応答の3秒ルール、トークン管理、ホスティングをコピペコード付きで解説。

discord.jsでスラッシュコマンドのDiscord Botを作る:登録・3秒ルール・ホスティングまで

初めて作ったDiscord Botは、ローカルでは完璧に動いていました。/support を叩くと受付チャンネルにメッセージが飛ぶ。テストサーバーで何度も確認した。

ところが本番サーバーに招待した瞬間、コマンドが入力欄に出てこない。出てきたと思ったら今度は「アプリケーションが応答しませんでした」という赤いエラー。原因を探って数時間溶かしました。

犯人は2つでした。ひとつはコマンドをテストサーバーにしか登録していなかったこと。もうひとつは、最初のコマンドで外部APIを叩いて3秒以上待たせていたこと。Discord Botには「3秒以内に何か返さないとトークンが死ぬ」という独特のルールがあって、僕はそれを知らずにハマったんです。

この記事では、その手の事故を踏まないDiscord Botの作り方を、discord.jsで最初から最後まで通します。コマンド登録の仕組み、Gateway Intents、interactionの3秒ルール、トークンの守り方、そしてどこで24時間動かすか。コードはコピペでそのまま動く形で置きます。

この記事の要点

  • Discord Botの本体は「interaction(スラッシュコマンドやボタンの操作)を受けて返事する」プログラム。discord.jsはこれをNode.jsで書くための定番ライブラリ。
  • コマンドは書くだけでは出ない。Discordへ別途「登録(deploy)」する必要がある。開発中は反映が一瞬のguildコマンド、本番は全サーバー向けのglobalコマンドを使い分ける。
  • interactionには3秒以内に最初の応答を返すルールがある。重い処理は先に deferReply() で時間を稼ぐ。
  • Gateway Intentsは「Botがどのイベントを受け取るか」の宣言。スラッシュコマンドだけなら Guilds で足り、メッセージ本文を読むには特権インテントの許可が要る。
  • Botトークンが漏れたら誰でもBotを乗っ取れる。.env に隔離し、Git・ログ・スクショに出さない。漏れたら即rotate。
  • ホスティングは「24時間動く場所」より先に「秘密情報を分けられて、ログが見られて、再起動できる場所」を選ぶ。

Discord Botは賢い会話AIではなく「受付窓口」

Discord Botを「サーバーの中で人間の代わりにしゃべるAI」だと思って作り始めると、たいてい遠回りします。実務で効くBotの正体は、もっと地味です。決まった操作を受け取って、決まった形で返す自動受付。これがいちばん近い。

たとえばコミュニティに質問が来たとき、一般チャンネルに自由投稿だと「動きません」の一言で流れて消えます。そこに /support というコマンドを置いて、「何が起きたか」「緊急度」「再現手順」を埋めてもらうだけで、後から読む人の負担がまるで変わる。受付番号のついた問い合わせ票になるイメージです。

だからこの記事で作るのも、会話AIではなく受付窓口です。実装するコマンドは3つ。

コマンド役割誰が使える
/supportサポート依頼を受付チャンネルへ整形して送る全員
/faqよくある質問に短く答える全員
/handoffモデレーターへの引き継ぎメモを作るManage Messages権限を持つ人だけ

会話の流れはこうです。ユーザーが /support を実行 → Discordがinteractionを送ってくる → Botが受付チャンネルに整形メッセージを投稿 → 実行した本人にだけ「送りました」と見える。最後の「本人にだけ見える」が ephemeral(エフェメラル)応答で、Discord Botならではの便利な機能です。

discord.jsとスラッシュコマンドの全体像

まず言葉を整理します。難しい単語が3つだけ出てくるので、ここで噛み砕いておきます。

application commands(アプリケーションコマンド) は、Discordの入力欄に / を打つと出てくる公式のコマンド機能です。その代表が /support のような slash command(スラッシュコマンド)。昔ながらの !help みたいなメッセージ監視型より、名前・説明・入力欄・選択肢・権限をDiscord側のUIで提示できるので、受付には断然こちらが向いています。

interaction(インタラクション) は、ユーザーがスラッシュコマンドやボタン、セレクトメニュー、モーダルを操作したときにBotへ届くイベントのこと。discord.jsでは Events.InteractionCreate で受け取ります。

Gateway Intents(ゲートウェイインテント) は、「うちのBotはこの種類のイベントを受け取ります」という事前申告です。全部のイベントを垂れ流しで受けると重いので、必要なものだけ宣言する仕組みになっています。スラッシュコマンドに反応するだけなら GatewayIntentBits.Guilds ひとつで足ります。メッセージの本文を読み取りたい場合は MessageContent という特権インテントが要り、これはDeveloper Portalで明示的にオンにしないと使えません。最初のBotで本文を読む必要はまずないので、ここは触らなくて大丈夫です。

仕様の一次情報は必ず公式で確認してください。コマンドの種類やguildとglobalの違いは Discord公式 Application Commands に、interactionの応答ルールは Receiving and Responding to Interactions にまとまっています。discord.js側のAPIは discord.js documentation、入門の流れは discord.js guide が基準です。

コマンドは「書いただけ」では出ない:登録の仕組み

ここが最初の関門で、僕も最初にコケた場所です。SlashCommandBuilder でコマンドを定義しても、それだけではDiscordの入力欄に出てきません。定義したコマンドをDiscordのAPIへ「登録(deploy)」するという別ステップが要ります。

登録先は2種類あって、性質がまるで違います。

  • guildコマンド: 指定した1サーバーだけに登録。反映が一瞬。開発・テスト中はこれ一択。
  • globalコマンド: Botが入っている全サーバーに登録。反映に時間がかかる(伝播待ちがある)。本番公開用。

開発中にglobalで登録すると「変更したのに出てこない、いや出てきた、消えた」と混乱します。冒頭で僕がハマったのもこれの逆パターンで、テスト中はguildでよかったのに、本番招待時にglobalへ切り替え忘れていました。開発はguild、安定したらglobal。この順番だけ覚えてください。

discord.jsでは登録先をルートで切り替えます。後で出てくるコードでは、環境変数に DISCORD_GUILD_ID がある間はguild、無ければglobalに自動で切り替わるようにしてあります。

import { REST, Routes } from "discord.js";

const rest = new REST({ version: "10" }).setToken(token);
// guildId があればそのサーバーだけ(即反映)、なければ全サーバー(伝播待ち)
const route = guildId
  ? Routes.applicationGuildCommands(clientId, guildId)
  : Routes.applicationCommands(clientId);

await rest.put(route, { body: commands }); // put は「この内容で総入れ替え」

put は「送った配列でコマンド一覧を丸ごと上書き」する動きをします。だからコマンドを消したいときは配列から外して再実行すれば消えます。

先に決める:トークンと最小権限

コードを書く前に、Discord側の準備と「絶対に守ること」を固めます。Botを作るには Discord Developer Portal でアプリケーションを作り、Botユーザーを追加して、招待URLに botapplications.commands の2つのscopeを付けます。

招待のとき、楽だからと管理者権限を付けないでください。乗っ取られたときの被害がサーバー全体に広がります。今回のBotに要るのは「受付チャンネルを見て、送れる」権限だけです。

項目注意点
Node.js22.12.0以上discord.js v14の公式要件
scopebot, applications.commandsスラッシュコマンド登録に必須
Bot権限View Channels, Send Messages最小から始める
DISCORD_TOKENBotトークンGit・記事・ログ・スクショに出さない
DISCORD_CLIENT_IDアプリケーションIDDeveloper Portalからコピー
DISCORD_GUILD_IDテスト用サーバーID開発中のguild登録に使う
SUPPORT_CHANNEL_ID受付先チャンネルIDBotが送信できるか確認

チャンネルIDやサーバーIDは、Discordクライアントの設定で「開発者モード」をオンにすると、右クリックメニューから「IDをコピー」で取れます。

環境変数の分け方は Claude Codeで環境変数を安全に管理する に詳しく書きました。Botの失敗時の返し方を整える話は Claude Codeでエラー処理パターンを整える が参考になります。

コピペで動くdiscord.jsスターター

ここからは実際のコードです。最小構成ですが、受付・FAQ・引き継ぎ・エラー時のephemeral応答・mention事故の無効化まで入れてあります。Node.js 22.12.0以上で動きます。

まずプロジェクトを作ります。

mkdir discord-support-bot
cd discord-support-bot
npm init -y
npm install discord.js dotenv
mkdir src

package.jsontype と起動スクリプトを足します。

{
  "type": "module",
  "scripts": {
    "start": "node src/bot.js"
  },
  "dependencies": {
    "discord.js": "latest",
    "dotenv": "latest"
  }
}

.env を作ります。値はDeveloper Portalと開発者モードから取ってきてください。

DISCORD_TOKEN=replace_with_bot_token
DISCORD_CLIENT_ID=replace_with_application_id
DISCORD_GUILD_ID=replace_with_test_guild_id
SUPPORT_CHANNEL_ID=replace_with_support_channel_id
DEPLOY_COMMANDS=true

そして本体 src/bot.js。長く見えますが、やっていることは「コマンド定義 → 登録 → interactionを受けて返事」の3ブロックだけです。

import "dotenv/config";
import {
  Client,
  Events,
  GatewayIntentBits,
  MessageFlags,
  PermissionFlagsBits,
  REST,
  Routes,
  SlashCommandBuilder,
} from "discord.js";

const token = process.env.DISCORD_TOKEN;
const clientId = process.env.DISCORD_CLIENT_ID;
const guildId = process.env.DISCORD_GUILD_ID;
const supportChannelId = process.env.SUPPORT_CHANNEL_ID;

// 起動前チェック:必須の値が無ければ、ここで原因をはっきり出して止める
for (const [name, value] of Object.entries({ token, clientId, supportChannelId })) {
  if (!value) throw new Error(`${name} is required.`);
}

// --- 1. コマンド定義 ---
const commands = [
  new SlashCommandBuilder()
    .setName("support")
    .setDescription("Send a support request to the team")
    .addStringOption((option) =>
      option
        .setName("summary")
        .setDescription("What happened?")
        .setMaxLength(900)
        .setRequired(true),
    )
    .addStringOption((option) =>
      option
        .setName("severity")
        .setDescription("How urgent is it?")
        .setRequired(true)
        .addChoices(
          { name: "low", value: "low" },
          { name: "normal", value: "normal" },
          { name: "high", value: "high" },
        ),
    )
    .addStringOption((option) =>
      option
        .setName("context")
        .setDescription("Steps, links, or error messages")
        .setMaxLength(1500),
    ),
  new SlashCommandBuilder()
    .setName("faq")
    .setDescription("Show a short answer for a common topic")
    .addStringOption((option) =>
      option
        .setName("topic")
        .setDescription("FAQ topic")
        .setRequired(true)
        .addChoices(
          { name: "setup", value: "setup" },
          { name: "permissions", value: "permissions" },
          { name: "rollout", value: "rollout" },
        ),
    ),
  new SlashCommandBuilder()
    .setName("handoff")
    .setDescription("Create a moderator handoff note")
    // このコマンドは Manage Messages 権限を持つ人にだけ表示される
    .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
    .addUserOption((option) =>
      option.setName("target").setDescription("User to hand off").setRequired(true),
    )
    .addStringOption((option) =>
      option
        .setName("note")
        .setDescription("What should the next moderator know?")
        .setMaxLength(1500)
        .setRequired(true),
    ),
].map((command) => command.toJSON());

// --- 2. Botクライアント(Intentsは最小限)---
const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, (readyClient) => {
  console.log(`Logged in as ${readyClient.user.tag}`);
});

// --- 3. interactionを受けて返事する ---
client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  try {
    if (!interaction.inGuild()) {
      await interaction.reply({
        content: "Please use this command inside the server.",
        flags: MessageFlags.Ephemeral,
      });
      return;
    }

    if (interaction.commandName === "support") await handleSupport(interaction);
    else if (interaction.commandName === "faq") await handleFaq(interaction);
    else if (interaction.commandName === "handoff") await handleHandoff(interaction);
    else await safeReply(interaction, "Unknown command.");
  } catch (error) {
    console.error("Interaction failed:", error);
    // 例外でも必ず何かを返す。沈黙は赤いエラー表示になる
    await safeReply(interaction, "Something went wrong. Please contact a moderator.");
  }
});

async function handleSupport(interaction) {
  const summary = interaction.options.getString("summary", true);
  const severity = interaction.options.getString("severity", true);
  const context = interaction.options.getString("context") ?? "No extra context.";
  const channel = await fetchSupportChannel();

  await channel.send({
    content: [
      "**New support request**",
      `Reporter: ${interaction.user.tag} (${interaction.user.id})`,
      `Severity: ${severity}`,
      `Channel: <#${interaction.channelId}>`,
      `Summary: ${neutralizeMentions(summary)}`,
      `Context: ${neutralizeMentions(context)}`,
    ].join("\n"),
    allowedMentions: { parse: [] }, // 入力に紛れた @everyone 等を通知させない
  });

  await interaction.reply({
    content: "Thanks. Your request was sent to the support team.",
    flags: MessageFlags.Ephemeral, // 本人にだけ見える返信
  });
}

async function handleFaq(interaction) {
  const topic = interaction.options.getString("topic", true);
  const answers = {
    setup: "Install Node.js 22.12+, invite the bot with bot and applications.commands scopes, then run npm start.",
    permissions: "Start with View Channels and Send Messages. Reserve Manage Messages for moderator-only commands.",
    rollout: "Use guild commands for testing. Promote to global commands only after rollback and logging are checked.",
  };

  await interaction.reply({
    content: answers[topic],
    flags: MessageFlags.Ephemeral,
  });
}

async function handleHandoff(interaction) {
  // 表示制限とは別に、実行時にも権限を二重チェックする
  if (!interaction.memberPermissions?.has(PermissionFlagsBits.ManageMessages)) {
    await interaction.reply({
      content: "You need Manage Messages permission to use this command.",
      flags: MessageFlags.Ephemeral,
    });
    return;
  }

  const target = interaction.options.getUser("target", true);
  const note = interaction.options.getString("note", true);
  const channel = await fetchSupportChannel();

  await channel.send({
    content: [
      "**Moderator handoff**",
      `Target: ${target.tag} (${target.id})`,
      `From: ${interaction.user.tag} (${interaction.user.id})`,
      `Note: ${neutralizeMentions(note)}`,
    ].join("\n"),
    allowedMentions: { parse: [] },
  });

  await interaction.reply({
    content: "Handoff note created.",
    flags: MessageFlags.Ephemeral,
  });
}

async function fetchSupportChannel() {
  const channel = await client.channels.fetch(supportChannelId);
  if (!channel || !channel.isTextBased() || typeof channel.send !== "function") {
    throw new Error("SUPPORT_CHANNEL_ID must be a text channel the bot can send to.");
  }
  return channel;
}

// @everyone やロールmentionを無害化する。公開サーバーでは必須
function neutralizeMentions(value) {
  return value
    .replaceAll("@everyone", "@ everyone")
    .replaceAll("@here", "@ here")
    .replace(/<@!?(\d+)>/g, "user:$1")
    .replace(/<@&(\d+)>/g, "role:$1");
}

// 既に返信済みかどうかを見て、reply と followUp を使い分ける
async function safeReply(interaction, content) {
  const payload = { content, flags: MessageFlags.Ephemeral };
  if (interaction.replied || interaction.deferred) await interaction.followUp(payload);
  else await interaction.reply(payload);
}

// --- コマンド登録(DEPLOY_COMMANDS=true のときだけ走らせる)---
async function deployCommands() {
  const rest = new REST({ version: "10" }).setToken(token);
  const route = guildId
    ? Routes.applicationGuildCommands(clientId, guildId)
    : Routes.applicationCommands(clientId);

  await rest.put(route, { body: commands });
  console.log(guildId ? "Guild commands deployed." : "Global commands deployed.");
}

if (process.env.DEPLOY_COMMANDS === "true") {
  await deployCommands();
}

await client.login(token);

動かす手順はこうです。node --version で22.12.0以上を確認してから npm start。最初はテストサーバーで /support を叩き、受付チャンネルに飛ぶか・本人にだけephemeral応答が見えるかを確認します。次に /faq、最後に権限を持つアカウントで /handoff。一度コマンドが登録できたら、毎回登録し直す必要はないので .envDEPLOY_COMMANDSfalse に戻しておくと起動が軽くなります。

interactionの3秒ルールとdeferReply

冒頭で僕がハマったもうひとつの罠が、これです。Discordは interactionを受けてから3秒以内に最初の応答を返すことを求めます。3秒を過ぎると、その操作に紐づくトークンが無効になり、ユーザーには「アプリケーションが応答しませんでした」と赤く出ます(一次情報は Receiving and Responding を参照)。

上のコードはどのコマンドも一瞬で reply を返すので3秒に収まります。問題は、外部APIやデータベース、生成AIを呼んで時間がかかるとき。そういう処理を reply の前に挟むと、平気で3秒を超えます。

対処はシンプルで、先に「考え中」を宣言すること。deferReply() を最初に呼ぶと、Discord側に「いま処理してます」と即座にACK(受領)を返し、ユーザーには読み込み中の表示が出ます。その後、時間のかかる処理が終わってから editReply() で本当の答えに差し替えます。

async function handleSlowTask(interaction) {
  // まず3秒以内に「考え中」を返してトークンを延命する
  await interaction.deferReply({ flags: MessageFlags.Ephemeral });

  // ここで時間のかかる処理をしてOK(外部API・DB・LLMなど)
  const result = await callSomethingSlow(interaction.options.getString("query", true));

  // 終わったら本当の答えに差し替える
  await interaction.editReply({ content: result });
}

順番が大事です。重い処理を呼ぶdeferReply()。後から呼んでも手遅れです。Claude APIを叩いて回答を整形するようなBotに育てるなら、この defer → 処理 → editReply が基本形になります。

ユースケース:受付・FAQ・引き継ぎ・研修サポート

このBotが実務でどう効くか、4つ挙げます。

1. サポート受付 質問者に長いフォームを書かせる前に、summaryseveritycontext の3つだけ取れば最低限のtriage(緊急度と担当先の仕分け)ができます。僕が小さな検証サーバーで試したときも、自由投稿より /support のほうが状況・再現手順・優先度が揃いやすく、後から読む人の往復回数が明らかに減りました。

2. FAQの短縮導線 Botに長いドキュメントを丸ごと喋らせるより、短い答えと正しいリンクを返すほうが強いです。セットアップは1段落で返して、詳細は Claude Codeのはじめ方CLIツール開発ガイド に送る。記事とBotの回答を同じ場所で更新すると、古い答えが残りにくくなります。

3. モデレーター引き継ぎ 夜間対応・課金・荒らし・障害報告では「誰が、誰について、何を次に見るか」が抜けると現場が混乱します。/handoff はManage Messages権限のある人だけに絞り、専用チャンネルへ整形済みメモを送る設計にしました。本文に個人情報を盛り込みすぎないのも忘れずに。

4. 研修コミュニティの受講者サポート 研修をやると、受講者が同じエラーを何度も投げてきます。Botに「Nodeのバージョン」「実行したコマンド」「エラーの最後の10行」を聞かせるだけで、講師は原因に近い情報から対応に入れます。チーム導入の運用設計までやるなら 研修・相談 とつながる話です。

本番前に潰しておく落とし穴

僕が実際に踏んだ/踏みかけた地雷を、優先度順に並べます。

  1. Botトークンの露出。トークンはパスワードより危険で、漏れたら誰でもBotを乗っ取れます。記事・Git・スクショ・ログのどこにも出さない。漏れたらDeveloper Portalで即rotateして古いトークンを無効化。Claude Codeにコードを書かせるときも「トークン値を出力するな」「.env.example だけ作れ」と先に指示します。
  2. コマンドの登録ミス。開発中はguild、本番はglobal。globalは反映に伝播待ちがあるので、開発で使うと「直したのに出ない」と消耗します。逆に本番招待でguildのままだと、招待先サーバーにコマンドが出ません(僕の初回がこれ)。
  3. 応答しない経路を残す。成功時だけ reply して、例外時や未知コマンド時に何も返さないと、ユーザーには赤いエラーしか見えません。上のコードは catchsafeReply で全経路をふさいでいます。
  4. mention爆撃。ユーザー入力をそのまま受付チャンネルに流すと、@everyone で通知事故が起きます。allowedMentions: { parse: [] } と文字列の無害化、両方やって初めて安全です。
  5. 管理者権限を渡す。動作確認は楽になりますが、侵害時の被害が広がります。View ChannelsとSend Messagesから始めて、必要になった権限だけレビューして足します。権限設計の考え方は コードレビュー チェックリスト の発想に近いです。

デプロイ前にこのチェックリストを通してください。

  • Botトークンを再発行できる担当者を決めた
  • .env はGit管理外、.env.example にはダミー値だけ
  • 招待URLは botapplications.commands scopeだけから
  • Bot権限はView ChannelsとSend Messagesが基本
  • /handoff はManage Messages権限のある人だけ
  • DISCORD_GUILD_ID でテストしてからglobalへ
  • すべてのinteraction経路にreplyかfollowUpがある
  • ユーザー入力のmentionを無効化している
  • ログにトークン・メール・決済情報・個人情報を出さない
  • デプロイ先がNode.js 22.12.0以上

ホスティング:どこで24時間動かすか

Botはプロセスが生きている間しか反応しません。ローカルでターミナルを閉じれば止まります。だから常時起動できる場所が要りますが、選ぶ基準は「24時間動くか」より先に3つあります。秘密情報を環境変数として分けられるか・ログを見られるか・再起動の手順があるか。ここが弱いと、事故ったときに何も追えません。

小規模なら選択肢はこのあたりです。

候補向いている場面ひとこと
Railway / Render個人〜小規模、すぐ動かしたい環境変数UIとログが分かりやすい
Fly.io軽量・常駐させたいCLI中心、無料枠で小型Botに十分
VPS(さくら/Conoha等)自分で全部握りたいsystemd 等で常駐管理。自由だが運用は自前
社内サーバー業務用・データを外に出せないネットワークと再起動手順を要整備

どこを選ぶにせよ、Claude Codeに任せるときは「デプロイ先の名前」だけでなく、環境変数の登録方法・ヘルスチェック・再起動手順・ログ確認方法までREADMEに書かせます。これをやっておくと、半年後に自分が見ても、別の人が引き継いでも迷いません。Botのコード品質を仕組みで保つ話は Claude Codeでコードレビューを仕組み化する、運用ルールの文書化は CLAUDE.mdテンプレート が地続きです。

よくある質問

Q. スラッシュコマンドが入力欄に出てきません。 ほぼ「登録していない」か「登録先が違う」です。DEPLOY_COMMANDS=true で一度起動してdeployし、開発中はguild(DISCORD_GUILD_ID あり)に登録します。globalは反映に伝播待ちがあるので、出るまで時間がかかることがあります。

Q. 「アプリケーションが応答しませんでした」と赤く出ます。 3秒ルール違反が筆頭候補です。重い処理の前に deferReply() を呼んでから処理し、editReply() で差し替えてください。あわせて、例外時にも safeReply で必ず何か返すようにします。

Q. Gateway Intentsは何を指定すればいいですか。 スラッシュコマンドに反応するだけなら GatewayIntentBits.Guilds だけで足ります。メッセージ本文の読み取りやメンバー情報の取得には特権インテント(MessageContent など)が必要で、Developer Portalでの明示的な許可が要ります。必要になるまで増やさないのが安全です。

Q. Botトークンを誤ってGitに上げてしまいました。 すぐDeveloper Portalでトークンをrotate(再発行)して古いものを無効化してください。Gitの履歴から消すだけでは不十分で、無効化が必須です。今後は .env.gitignore に入れ、.env.example にダミー値だけ置きます。

Q. discord.jsとPythonのdiscord.py、どっちがいいですか。 Node.js(JavaScript/TypeScript)に慣れているなら、この記事のままdiscord.jsが素直です。Webフロントや他のNodeツールと資産を共有しやすいのも利点。Python中心の環境ならdiscord.pyでも構いませんが、本記事のコードはそのまま使えません。

Q. このBotにClaude APIを組み込めますか。 できます。handleSupport のようなハンドラの中で deferReply() を先に呼び、Claude APIを叩いて回答を作り、editReply() で返す形にします。3秒ルールがあるので defer は必須。トークンや会話履歴の扱いは、上のトークン管理と同じ慎重さで。

実際に試した結果

このコードを小さな検証サーバーで動かして、いちばん効いたのはAIらしい回答ではなく、受付の型を /support に固定したことでした。自由投稿だと「動きません」で止まる相談が、summaryseveritycontext を埋める形にしただけで、最初の返信で聞き返す回数がはっきり減ったんです。

逆に、油断するとすぐ事故になるのはトークン・コマンド登録・mention・権限の4つでした。どれも「賢さ」とは関係なく、地味な周辺です。だからClaude Codeに頼むときも、Bot本体だけでなく .env.example・権限表・失敗時の返答・デプロイ前チェックリストまで一緒に作らせるのが、結局いちばん早い。Discord Botの出来は、会話の賢さより、この外側の作り込みで決まる、というのが今の実感です。

まずは上のスターターをguildサーバーで動かして、/support をひとつ通すところから始めてみてください。再利用できるテンプレートやチェックリストが欲しくなったら 教材一覧 を、チームでDiscord運用や権限設計まで整えるなら 研修・相談 をのぞいてみてください。

#Discord Bot #discord.js #スラッシュコマンド #Claude Code #Node.js
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。