Use Cases (Diperbarui: 21/7/2026)

Membuat Discord Bot dengan Claude Code: Slash Command discord.js

Buat Discord Bot aman dengan Claude Code, discord.js, tiga Slash Command, permission minimum, dan tes lokal.

Membuat Discord Bot dengan Claude Code: Slash Command discord.js

Anda membuka channel support dan menemukan tiga pesan: “tidak jalan”, “ada yang bisa bantu?”, lalu sebuah @everyone yang terkirim tanpa sengaja. Moderator tetap harus menanyakan error, tingkat urgensi, dan langkah yang sudah dicoba. Masalahnya bukan kurang responsif, melainkan bentuk laporan yang tidak konsisten.

Panduan ini memakai Claude Code dan discord.js untuk membuat Discord Bot dengan tiga Slash Command: /support, /faq, dan /handoff. Contohnya ramah pemula, menggunakan Node 24 LTS, mengunci versi dependency, memberi balasan privat, dan membatasi permission sejak awal.

Hasil yang akan Anda buat:

  • project siap salin dengan discord.js 14.27.0 dan dotenv 17.2.3;
  • registrasi command yang hanya menyentuh test server;
  • alur deferReply() lalu editReply() agar interaction tidak terlambat;
  • perlindungan token dan dua lapis pencegahan mention;
  • fixture lokal yang tidak login ke Discord.

Bagian untuk Claude Code dan bagian yang harus diputuskan manusia

Claude Code dapat membuat file, membaca diff, menjalankan pemeriksaan lokal, dan menunjukkan permission yang terlalu luas. Namun, alat ini tidak mengetahui kebijakan komunitas Anda dan tidak boleh menentukan sendiri siapa yang boleh membaca laporan privat.

Dapat disiapkan Claude CodeHarus diputuskan manusia
Struktur project dan kode Slash CommandSiapa yang boleh memakai /handoff
Validasi env dan tes lokalChannel mana yang menerima data privat
Sanitasi mention dan error pathBerapa lama pesan dan log disimpan
Checklist deploy dan rollbackKapan command siap masuk produksi

Mulai dengan prompt yang sempit: “Buat bot support Node 24 LTS dengan discord.js 14.27.0. Sediakan /support, /faq, /handoff, private reply, minimum permission, dan tes lokal tanpa koneksi jaringan.” Setelah itu, review perubahan per file sebelum menjalankan instalasi.

Jadikan dokumentasi resmi sebagai acuan: Discord Application Commands, Receiving and Responding to Interactions, serta API discord.js 14.27.0 yang sesuai dengan versi contoh.

Alur kerja sebelum dan sesudah bot

SebelumSesudah
Laporan pendek tenggelam di channel umum/support meminta summary, severity, dan context
Moderator mengulang jawaban yang sama/faq memberi jawaban pendek secara privat
Pergantian petugas kehilangan konteks/handoff mencatat target, penulis, dan langkah berikutnya
Input pengguna dapat memicu notifikasiSanitasi dan allowedMentions menahan ping

Bot merapikan pintu masuk, bukan menggantikan moderator. Penentuan prioritas, diagnosis, dan jawaban akhir tetap membutuhkan manusia.

Membuat project dengan versi yang terkunci

Contoh ini ditulis untuk Node 24 LTS. Field engines menyatakan Node 18 atau lebih baru; tetap uji compatibility pada platform hosting yang akan digunakan.

mkdir discord-support-bot
cd discord-support-bot
npm init -y
mkdir src

Ganti isi package.json:

{
  "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"
  }
}

Instal dependency yang sudah ditetapkan dan simpan package-lock.json:

npm install

Buat .gitignore:

node_modules/
.env

Buat .env hanya di komputer atau secret store milik Anda:

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

Perlakukan DISCORD_TOKEN seperti password. Jangan tempel nilainya ke prompt, Git, screenshot, atau log CI. Jika token pernah terlihat di salah satu tempat itu, rotasi lewat Developer Portal sebelum bot dipakai lagi.

Kode praktis /support, /faq, dan /handoff

Buat src/safety.js. Fungsi kecil ini mengubah mention dari input pengguna menjadi teks biasa.

export function neutralizeMentions(value) {
  return value
    .replaceAll("@everyone", "@ everyone")
    .replaceAll("@here", "@ here")
    .replace(/<@!?(\d+)>/g, "user:$1")
    .replace(/<@&(\d+)>/g, "role:$1");
}

Berikutnya, buat 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);

Mengapa memakai deferReply() sebelum proses lain? Mengambil channel atau memanggil layanan tambahan dapat memakan waktu. Defer memberi tahu Discord bahwa interaction sudah diterima dan menyiapkan balasan privat. Setelah pekerjaan selesai, editReply() mengisi hasil akhirnya.

Di Developer Portal, gunakan scopes bot dan applications.commands. Mulai dengan View Channels dan Send Messages pada channel support. Kode juga memeriksa Manage Messages pada anggota yang menjalankan /handoff; bot tidak membutuhkan Administrator.

Tiga Use case yang dapat langsung dipetakan

1. Menerima permintaan support

Input: /support summary:"npm install gagal" severity:normal context:"error EACCES".

Output bot: konfirmasi privat untuk pengguna dan catatan terstruktur di channel support tanpa mention aktif.

Keputusan manusia: mereproduksi error, meminta log yang sudah dibersihkan dari secret, dan menentukan prioritas. Bot tidak menetapkan status insiden akhir.

2. Menjawab FAQ pendek

Input: /faq topic:permissions.

Output bot: pesan privat yang menyarankan View Channel dan Send Messages sebagai titik awal, bukan Administrator.

Keputusan manusia: memperbarui jawaban ketika kebijakan tim berubah dan mencocokkannya dengan dokumentasi resmi.

3. Handoff antar-moderator

Input: /handoff target:@user note:"periksa versi Node dan log yang sudah disanitasi".

Output bot: catatan internal berisi target, moderator asal, dan tindakan berikutnya tanpa mengirim ping.

Keputusan manusia: memilih pemilik kasus berikutnya, membatasi akses channel, dan menghapus data sesuai masa retensi.

Pitfall dan cara memperbaikinya

Interaction terlambat dijawab. Jangan menunggu proses eksternal sebelum merespons. Panggil deferReply() lebih dulu, lalu tutup semua jalur dengan editReply().

Memakai versi yang mengambang. Instalasi berikutnya dapat mengubah perilaku tanpa perubahan kode. Kunci versi dan review package-lock.json.

Mendaftarkan global command saat development. Contoh memakai applicationGuildCommands agar terbatas pada test server. Periksa nama, permission, dan rollback sebelum publikasi global.

Hanya mengganti teks @everyone. Sanitasi membuat pesan lebih aman dibaca, tetapi pengaman utama tetap allowedMentions: { parse: [] }. Pertahankan keduanya.

Menghapus token dari file setelah bocor. Token yang sempat masuk Git, log, screenshot, atau prompt sudah harus dianggap bocor. Rotasi di Developer Portal dan nonaktifkan token lama.

Perhitungan ROI sederhana dengan asumsi terbuka

Ini bukan hasil pengukuran. Anggap ada 30 permintaan per hari, 20 hari kerja per bulan, dan input terstruktur menghemat 45 detik triage per permintaan. Waktu yang berpotensi dihemat adalah 30 x 20 x 45 / 3600 = 7,5 jam/bulan. Dengan asumsi biaya moderator Rp75.000 per jam, nilainya 7,5 x 75.000 = Rp562.500/bulan.

Catat dua minggu sebelum dan sesudah: jumlah permintaan, waktu menuju respons pertama yang berguna, serta laporan yang tidak memiliki konteks. Jika moderator masih mencari data yang sama di tempat lain, penghematannya nol.

Verifikasi lokal tanpa terhubung ke Discord

Buat 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");

Jalankan:

npm run verify

Perintah ini memeriksa sintaks bot.js dan perilaku sanitasi mention. Tidak ada login, registrasi Slash Command, atau pengiriman pesan. Untuk pengujian Discord berikutnya, gunakan server terpisah, data dummy, dan token khusus environment tersebut.

Untuk melanjutkan dengan template dan checklist siap pakai, lihat produk ClaudeCodeLab.

Hasil pengujian yang benar-benar dilakukan

Pengujian dibatasi pada fixture lokal. Input berisi @everyone, mention pengguna, mention role, dan @here berubah menjadi teks netral; semua assertion selesai tanpa error. Sintaks src/bot.js juga diperiksa memakai node --check. Tidak ada klaim bahwa bot sudah login, mendaftarkan Slash Command, atau mengirim pesan ke server Discord nyata; semuanya masih menjadi langkah verifikasi manusia di test server.

#Claude Code #Discord Bot #discord.js #chatbot #komunitas
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.