Claude Code से Discord Bot बनाएं: discord.js Slash Command गाइड
Claude Code और discord.js से सुरक्षित Discord Bot बनाएं: तीन Slash Command, permissions और local test.
Support channel खोलते ही तीन message दिखते हैं: “काम नहीं कर रहा”, “कोई देख सकता है?” और गलती से भेजा गया @everyone। Moderator मदद करना चाहता है, लेकिन उसे error, urgency और अगला कदम समझने के लिए बार-बार सवाल पूछने पड़ते हैं।
इस गाइड में Claude Code और discord.js से ऐसा Discord Bot बनाएंगे जो /support, /faq और /handoff Slash Command देता है। यह शुरुआती पाठक के लिए है: Node 24 LTS, fixed dependency versions, private reply, minimum permissions और token safety को शुरू से शामिल किया गया है।
इस लेख से आपको मिलेगा:
discord.js14.27.0 औरdotenv17.2.3 वाला copy-paste project;- केवल test server में Slash Command register करने का तरीका;
- timeout से बचने के लिए
deferReply()सेeditReply()का flow; - user input से mentions बंद करने की दो परतें;
- Discord से connect किए बिना चलने वाला local verification fixture।
Claude Code को क्या सौंपें, और इंसान क्या तय करे
Claude Code files बना सकता है, diff review कर सकता है, local checks चला सकता है और जरूरत से ज्यादा permission को चिन्हित कर सकता है। लेकिन community policy और private support data का owner कौन है, यह उसे तय नहीं करना चाहिए।
| Claude Code का काम | इंसान का फैसला |
|---|---|
| Project structure और Slash Command code | /handoff कौन चला सकता है |
| Env validation और local test | Private data किस channel में जाएगा |
| Mention sanitization और error path | Logs कितने दिन रखे जाएंगे |
| Deploy और rollback checklist | Command को production में कब भेजना है |
पहला prompt छोटा लेकिन साफ रखें: “Node 24 LTS और discord.js 14.27.0 से support bot बनाओ। /support, /faq, /handoff, private replies, minimum permissions और बिना network वाला local test शामिल करो।” फिर generated diff को file-by-file पढ़ें।
सही व्यवहार के लिए official documentation देखें: Discord Application Commands, Receiving and Responding to Interactions, और इसी लेख में इस्तेमाल version की discord.js 14.27.0 API।
Bot लगाने से पहले और बाद का workflow
| पहले | बाद में |
|---|---|
| General channel में अधूरा सवाल | /support summary, severity और context मांगता है |
| एक ही सवाल का बार-बार जवाब | /faq छोटा private answer देता है |
| Moderator बदलते समय context गायब | /handoff target, author और next action लिखता है |
| User text notification चला सकता है | Sanitization और allowedMentions ping रोकते हैं |
Bot केवल input को व्यवस्थित करता है। Priority, सही diagnosis और user को दिया जाने वाला final answer अभी भी इंसान की जिम्मेदारी है।
Fixed versions के साथ project बनाएं
उदाहरण Node 24 LTS पर चलाने के लिए लिखा गया है। engines में Node 18 या नया घोषित है; deploy platform पर वास्तविक compatibility खुद verify करें।
mkdir discord-support-bot
cd discord-support-bot
npm init -y
mkdir src
package.json को इस content से बदलें:
{
"name": "discord-support-bot",
"private": true,
"type": "module",
"engines": {
"node": ">=18"
},
"scripts": {
"start": "node src/bot.js",
"verify": "node --check src/bot.js && node src/verify.js"
},
"dependencies": {
"discord.js": "14.27.0",
"dotenv": "17.2.3"
}
}
अब exact versions install करें और बना हुआ package-lock.json commit करें:
npm install
.gitignore बनाएं:
node_modules/
.env
Local .env में ये values रखें:
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
DISCORD_TOKEN password जैसा secret है। इसे prompt, Git, screenshot या CI log में न डालें। गलती से बाहर आ जाए तो Developer Portal में token rotate करें; सिर्फ file delete करना पर्याप्त नहीं है।
/support, /faq और /handoff का practical code
पहले src/safety.js बनाएं। यह user text में मौजूद mass mention और ID mention को सामान्य text में बदलता है।
export function neutralizeMentions(value) {
return value
.replaceAll("@everyone", "@ everyone")
.replaceAll("@here", "@ here")
.replace(/<@!?(\d+)>/g, "user:$1")
.replace(/<@&(\d+)>/g, "role:$1");
}
अब src/bot.js बनाएं:
import "dotenv/config";
import {
Client,
Events,
GatewayIntentBits,
MessageFlags,
PermissionFlagsBits,
REST,
Routes,
SlashCommandBuilder,
} from "discord.js";
import { neutralizeMentions } from "./safety.js";
const env = {
token: process.env.DISCORD_TOKEN,
clientId: process.env.DISCORD_CLIENT_ID,
guildId: process.env.DISCORD_GUILD_ID,
supportChannelId: process.env.SUPPORT_CHANNEL_ID,
};
for (const [name, value] of Object.entries(env)) {
if (!value) throw new Error(`Missing environment variable: ${name}`);
}
const commands = [
new SlashCommandBuilder()
.setName("support")
.setDescription("Send a structured support request")
.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 or error text").setMaxLength(1500),
),
new SlashCommandBuilder()
.setName("faq")
.setDescription("Show one short answer")
.addStringOption((option) =>
option
.setName("topic")
.setDescription("Choose a 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")
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.addUserOption((option) =>
option.setName("target").setDescription("User related to the case").setRequired(true),
)
.addStringOption((option) =>
option.setName("note").setDescription("What should happen next?").setMaxLength(1500).setRequired(true),
),
].map((command) => command.toJSON());
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
async function supportChannel() {
const channel = await client.channels.fetch(env.supportChannelId);
if (!channel?.isTextBased() || typeof channel.send !== "function") {
throw new Error("SUPPORT_CHANNEL_ID must point to a writable text channel");
}
return channel;
}
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 supportChannel();
await channel.send({
content: [
"**New support request**",
`Reporter: ${interaction.user.tag} (${interaction.user.id})`,
`Severity: ${severity}`,
`Summary: ${neutralizeMentions(summary)}`,
`Context: ${neutralizeMentions(context)}`,
].join("\n"),
allowedMentions: { parse: [], repliedUser: false },
});
await interaction.editReply("Your request was sent to the support team.");
}
async function handleFaq(interaction) {
const answers = {
setup: "Use Node 24 LTS, configure .env, then test guild commands.",
permissions: "Start with View Channel and Send Messages; do not grant Administrator.",
rollout: "Test in one guild before registering global commands.",
};
const topic = interaction.options.getString("topic", true);
await interaction.editReply(answers[topic] ?? "No answer is configured for this topic.");
}
async function handleHandoff(interaction) {
if (!interaction.memberPermissions?.has(PermissionFlagsBits.ManageMessages)) {
await interaction.editReply("Manage Messages permission is required.");
return;
}
const target = interaction.options.getUser("target", true);
const note = interaction.options.getString("note", true);
const channel = await supportChannel();
await channel.send({
content: [
"**Moderator handoff**",
`Target: ${target.tag} (${target.id})`,
`From: ${interaction.user.tag} (${interaction.user.id})`,
`Next action: ${neutralizeMentions(note)}`,
].join("\n"),
allowedMentions: { parse: [], repliedUser: false },
});
await interaction.editReply("The handoff note was created.");
}
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (!interaction.inGuild()) {
await interaction.reply({ content: "Use this command inside the server.", flags: MessageFlags.Ephemeral });
return;
}
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
try {
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 interaction.editReply("Unknown command.");
} catch (error) {
console.error("Interaction failed:", error instanceof Error ? error.message : error);
await interaction.editReply("The command failed. Ask a moderator to check the logs.");
}
});
async function deployGuildCommands() {
const rest = new REST({ version: "10" }).setToken(env.token);
await rest.put(Routes.applicationGuildCommands(env.clientId, env.guildId), { body: commands });
}
if (process.env.DEPLOY_COMMANDS === "true") await deployGuildCommands();
client.once(Events.ClientReady, (readyClient) => console.log(`Ready as ${readyClient.user.tag}`));
await client.login(env.token);
deferReply() पहले क्यों है? Channel fetch या किसी बाहरी service में समय लग सकता है। Defer Discord को बताता है कि command स्वीकार हो गया है और private response reserved है। काम पूरा होने पर editReply() उसी response को update करता है।
Developer Portal में invite scopes bot और applications.commands रखें। Bot को support channel पर केवल View Channels और Send Messages से शुरू करें। /handoff चलाने वाले member के लिए code Manage Messages check करता है; Bot को Administrator देने की जरूरत नहीं है।
तीन numbered Use case
1. Support request लेना
Input: /support summary:"npm install fail" severity:normal context:"EACCES error"।
Bot output: User को private confirmation और support channel में structured note। User का text किसी को mention नहीं करता।
इंसान का फैसला: Error reproduce करना, secret हटाकर log मांगना और priority तय करना। Bot incident severity को अंतिम सत्य नहीं मानता।
2. छोटी FAQ देना
Input: /faq topic:permissions।
Bot output: Private message कि शुरुआत View Channel और Send Messages से करें, Administrator से नहीं।
इंसान का फैसला: Team policy बदलने पर answer update करना और official docs से उसकी जाँच करना।
3. Moderator handoff
Input: /handoff target:@user note:"Node version और sanitized log verify करें"।
Bot output: Internal channel में target, note लिखने वाला moderator और next action। कोई ping नहीं जाता।
इंसान का फैसला: अगला owner चुनना, private channel access सीमित करना और retention period के बाद data हटाना।
Pitfall और उनका सीधा fix
Interaction का देर से जवाब। External work से पहले reply न देने पर command fail दिख सकता है। शुरुआत में deferReply() और अंत में editReply() रखें।
Dependency version खुला छोड़ना। अगला install बिना code change के behavior बदल सकता है। Versions fixed रखें और package-lock.json review करें।
Development में global commands। यह sample applicationGuildCommands इस्तेमाल करता है, इसलिए test server तक सीमित रहता है। Production rollout से पहले naming, permissions और rollback जाँचें।
सिर्फ text replacement पर भरोसा। @everyone को बदलना पढ़ने में मदद करता है, लेकिन असली सुरक्षा allowedMentions: { parse: [] } भी है। दोनों layers रखें।
Token leak के बाद केवल commit हटाना। Git history, CI log या prompt में token जा चुका हो तो वह compromised है। Developer Portal से rotate करें और पुराना token बंद करें।
सरल ROI, स्पष्ट assumptions के साथ
यह measured result नहीं, planning example है। मान लें रोज 30 support requests आते हैं, महीने में 20 working days हैं और structured input हर request की triage में 45 seconds बचाता है। समय बचत 30 x 20 x 45 / 3600 = 7.5 hours/month होगी। यदि moderator time की मान्य लागत ₹800/hour है, तो संभावित बचत 7.5 x 800 = ₹6,000/month है।
Bot लगाने से दो हफ्ते पहले और बाद में requests, पहली उपयोगी response तक समय और missing-context messages गिनें। यदि moderator को वही data दूसरे channel से खोजना पड़े, तो वास्तविक saving शून्य है।
Discord से जुड़े बिना local verification
src/verify.js बनाएं:
import assert from "node:assert/strict";
import { neutralizeMentions } from "./safety.js";
const input = "@everyone <@123> <@&456> @here";
const output = neutralizeMentions(input);
assert.equal(output, "@ everyone user:123 role:456 @ here");
assert.ok(!output.includes("@everyone"));
assert.ok(!output.includes("@here"));
console.log("local safety fixture: ok");
चलाएं:
npm run verify
यह command bot.js की syntax और mention sanitization जाँचता है। यह login, Slash Command registration या message send नहीं करता। बाद में Discord test करना हो तो अलग test server, dummy data और उसी environment के लिए बनाया token इस्तेमाल करें।
अगले चरण के templates और checklists के लिए ClaudeCodeLab products देखें।
वास्तव में आज़माने पर क्या परिणाम मिला
केवल local fixture चलाया गया। @everyone, user mention, role mention और @here वाला input neutral text में बदला, और सभी assertions बिना error पूरी हुईं। node --check से src/bot.js की syntax भी जाँची गई। किसी real Discord Bot से login, Slash Command registration या server पर message भेजने का दावा नहीं है; वे test server में इंसान द्वारा किए जाने वाले अगले verification steps हैं।
संबंधित लेख
Claude Code से Slack Bot बनाएं: triage, incident response और daily reports
Bolt JS, Socket Mode, slash commands, सुरक्षा, tests और production checklist के साथ Slack Bot guide.
Claude Code से chart library चुनने की व्यावहारिक गाइड
Claude Code से Recharts, Chart.js या D3 चुनें और real data के लिए मजबूत dashboard बनाएं.
Claude Code और Next.js full-stack: App Router की व्यावहारिक गाइड
Claude Code और Next.js App Router से full-stack फीचर बनाते समय server/client सीमा, Server Actions, API, validation, auth और review सीखें।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.