Prejsť na obsah
Aktuálny kupón:zľava 10 %Uplatniť v objednávke

Prvý bot v discord.js: slash príkaz krok za krokom

Napíšete bota v discord.js 14 so slash príkazmi /ping a /pozdrav, zaregistrujete príkazy cez REST, naučíte sa odpovedať do 3 sekúnd a bota spustíte u StormHostu.

  • pokročilý
  • ~30 min
  • Aktualizované 9. 10. 2026

Napíšeme bota v knižnici discord.js s dvoma slash príkazmi: /ping a /pozdrav s parametrom. Každý riadok vysvetlíme a nakoniec bota spustíme u StormHostu.

Čo budete potrebovať

  • Node.js vo verzii LTS v počítači (stiahnutie na nodejs.org (otvorí sa v novom okne)) a editor kódu, napríklad Visual Studio Code
  • Aplikáciu v Discord Developer Portali, token a bota pozvaného na váš testovací server so scope applications.commands (návod)
  • Discord bota u StormHostu na trvalú prevádzku
  • Približne 30 minút

Ako to funguje#

Bot má so slash príkazmi tri časti:

  1. Definícia príkazov – názov, opis a parametre príkazu. Discordu ich oznámite raz samostatným skriptom cez REST API (registrácia).
  2. Bežiaci bot – pripojí sa k Discordu (gateway) a čaká na udalosti.
  3. Obsluha interakcie – keď niekto príkaz použije, Discord pošle botovi interakciu a bot na ňu musí do 3 sekúnd odpovedať. Inak používateľ uvidí, že aplikácia neodpovedala.

Registrácia a beh bota sú oddelené: príkazy stačí zaregistrovať znova iba vtedy, keď zmeníte ich definíciu (názov, opis, parametre). Zmeny v kóde odpovede registráciu nepotrebujú.

Založenie projektu#

  1. Vytvorte priečinok muj-bot a otvorte v ňom terminál (vo VS Code Ctrl+`).

  2. Založte projekt a nainštalujte knižnice:

    bash
    npm init -y
    npm install discord.js dotenv
    • npm init -y – vytvorí package.json s predvolenými hodnotami (-y = na všetky otázky odpovie „áno“).
    • npm install discord.js dotenv – nainštaluje discord.js a dotenv (načítanie súboru .env) a zapíše ich do dependencies.
  3. V package.json nastavte "main": "index.js" a "type": "commonjs". Ukážky používajú require(). Význam polí vysvetľuje návod nahranie a spustenie bota.

  4. Vytvorte súbor .env:

    .envini
    DISCORD_TOKEN=<TOKEN_BOTA>
    CLIENT_ID=<APPLICATION_ID>
    GUILD_ID=<ID_SERVERU>

    Za <TOKEN_BOTA> dosaďte token bota, za <APPLICATION_ID> Application ID zo stránky General Information v Developer Portali a za <ID_SERVERU> ID vášho testovacieho Discord servera. Kde ich nájsť, opisuje návod token a intenty.

  5. Vytvorte .gitignore s dvoma riadkami node_modules a .env.

Výsledná štruktúra:

text
muj-bot/
├── .env
├── .gitignore
├── package.json
├── package-lock.json
├── index.js
├── deploy-commands.js
└── commands/
    └── utility/
        ├── ping.js
        └── pozdrav.js

Príkazy#

Každý príkaz má vlastný súbor s dvoma časťami: data (definícia pre Discord) a execute (čo sa stane po použití).

commands/utility/ping.jsJavaScript
const { SlashCommandBuilder, MessageFlags } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Odpovie Pong! a ukáže odozvu bota.'),

  async execute(interaction) {
    await interaction.reply({
      content: `Pong! Odozva: ${interaction.client.ws.ping} ms`,
      flags: MessageFlags.Ephemeral,
    });
  },
};
RiadokČo robí
require('discord.js')Načíta z knižnice triedu SlashCommandBuilder (stavba definície príkazu) a MessageFlags (príznaky správ).
.setName('ping')Názov príkazu, ktorý používateľ píše za lomku. 1–⁠32 znakov, iba malé písmená, číslice, - a _, bez medzier.
.setDescription('…')Opis v ponuke príkazov. Povinný, 1–⁠100 znakov.
async execute(interaction)Funkcia, ktorú bot zavolá pri použití príkazu. interaction nesie všetko o použití (kto, kde, parametre).
interaction.reply({ … })Odpoveď na príkaz. Musí prísť do 3 sekúnd.
interaction.client.ws.pingOdozva spojenia bota s Discordom v milisekundách. Hneď po štarte môže byť -1, kým sa nezmeria.
flags: MessageFlags.EphemeralOdpoveď uvidí iba ten, kto príkaz použil. Ostatní v kanáli ju nevidia.

Príkaz s parametrom:

commands/utility/pozdrav.jsJavaScript
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('pozdrav')
    .setDescription('Pozdraví zadaného človeka.')
    .addStringOption((option) =>
      option.setName('jmeno').setDescription('Koho pozdraviť').setRequired(true),
    ),

  async execute(interaction) {
    const jmeno = interaction.options.getString('jmeno');
    await interaction.reply(`Ahoj, ${jmeno}! Zdraví ťa ${interaction.user.username}.`);
  },
};
  • .addStringOption(…) – pridá textový parameter. Discord ho používateľovi ponúkne ako pole jmeno:.
  • .setRequired(true) – parameter je povinný, bez neho príkaz nejde odoslať.
  • interaction.options.getString('jmeno') – prečíta hodnotu parametra.
  • interaction.user.username – používateľské meno toho, kto príkaz použil.
  • interaction.reply('…') – verejná odpoveď do kanála.

Ďalšie typy parametrov: addIntegerOption (celé číslo), addBooleanOption (áno/nie), addUserOption (člen servera), addChannelOption (kanál), addRoleOption (rola).

Registrácia príkazov#

Skript deploy-commands.js načíta definície všetkých príkazov a pošle ich Discordu. Spúšťate ho ručne, iba keď pridáte príkaz alebo zmeníte jeho definíciu.

deploy-commands.jsJavaScript
const fs = require('node:fs');
const path = require('node:path');
require('dotenv').config({ path: path.join(__dirname, '.env'), quiet: true });
const { REST, Routes } = require('discord.js');

const commands = [];
const foldersPath = path.join(__dirname, 'commands');
for (const folder of fs.readdirSync(foldersPath)) {
  const commandsPath = path.join(foldersPath, folder);
  for (const file of fs.readdirSync(commandsPath).filter((f) => f.endsWith('.js'))) {
    const command = require(path.join(commandsPath, file));
    if ('data' in command && 'execute' in command) {
      commands.push(command.data.toJSON());
    } else {
      console.log(`[WARNING] Súbor ${file} nemá "data" alebo "execute".`);
    }
  }
}

const rest = new REST().setToken(process.env.DISCORD_TOKEN);

(async () => {
  try {
    console.log(`Registrujem príkazy: ${commands.length}`);
    const data = await rest.put(
      Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
      { body: commands },
    );
    console.log(`Hotovo, zaregistrovaných príkazov: ${data.length}`);
  } catch (error) {
    console.error(error);
  }
})();
ČasťČo robí
fs.readdirSync(foldersPath)Prejde podpriečinky commands (napríklad utility) a v nich súbory .js.
command.data.toJSON()Prevedie definíciu príkazu do tvaru, ktorý očakáva Discord API.
new REST().setToken(…)Klient REST API Discordu prihlásený tokenom bota. Nepripája sa ako bot, iba pošle požiadavku.
Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID)Adresa pre príkazy jedného servera (guild).
rest.put(…, { body: commands })Nahradí všetky príkazy na serveri týmto zoznamom. Príkaz, ktorý v zozname chýba, z Discordu zmizne.

Spustite ho v počítači v priečinku projektu:

bash
node deploy-commands.js
Výstuptext
Registrujem príkazy: 2
Hotovo, zaregistrovaných príkazov: 2

Serverové, alebo globálne príkazy?#

DruhRouteKde fungujúKedy použiť
Serverové (guild)Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID)Iba na serveri GUILD_ID.Vývoj a testovanie, bot pre jeden server.
GlobálneRoutes.applicationCommands(CLIENT_ID)Na všetkých serveroch, kde je bot pozvaný so scope applications.commands, a v predvolenom stave aj v súkromných správach.Hotový bot pre viac serverov.

Keď máte rovnaký príkaz zaregistrovaný serverovo aj globálne, uvidíte ho na testovacom serveri dvakrát. Pred prechodom na globálnu registráciu serverové príkazy zmažte: spustite serverovú registráciu s prázdnym zoznamom (body: []).

Discord obmedzuje vytváranie príkazov na 200 za deň na jeden server. Registračný skript preto nespúšťajte pri každom štarte bota.

Hlavný súbor bota#

index.jsJavaScript
const fs = require('node:fs');
const path = require('node:path');
require('dotenv').config({ path: path.join(__dirname, '.env'), quiet: true });
const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js');

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

// Načítanie príkazov z priečinka commands
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
for (const folder of fs.readdirSync(foldersPath)) {
  const commandsPath = path.join(foldersPath, folder);
  for (const file of fs.readdirSync(commandsPath).filter((f) => f.endsWith('.js'))) {
    const command = require(path.join(commandsPath, file));
    if ('data' in command && 'execute' in command) {
      client.commands.set(command.data.name, command);
    }
  }
}

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

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  const command = interaction.client.commands.get(interaction.commandName);
  if (!command) {
    console.error(`Príkaz ${interaction.commandName} neexistuje.`);
    return;
  }

  try {
    await command.execute(interaction);
  } catch (error) {
    console.error(error);
    const odpoved = { content: 'Pri vykonávaní príkazu nastala chyba.', flags: MessageFlags.Ephemeral };
    if (interaction.replied || interaction.deferred) {
      await interaction.followUp(odpoved);
    } else {
      await interaction.reply(odpoved);
    }
  }
});

client.login(process.env.DISCORD_TOKEN);
ČasťČo robí
new Client({ intents: [GatewayIntentBits.Guilds] })Vytvorí bota. Intent Guilds stačí pre slash príkazy a nie je privilegovaný. Čítanie bežných správ by vyžadovalo GuildMessages a MessageContent.
client.commands = new Collection()Zoznam príkazov podľa názvu. Collection je rozšírená Map z discord.js.
client.commands.set(command.data.name, command)Uloží príkaz pod jeho názov, napríklad ping.
client.once(Events.ClientReady, …)Spustí sa raz po úspešnom prihlásení. readyClient.user.tag je meno bota.
client.on(Events.InteractionCreate, …)Spustí sa pri každej interakcii (príkaz, tlačidlo, formulár).
interaction.isChatInputCommand()Prepustí iba slash príkazy, ostatné interakcie ignoruje.
client.commands.get(interaction.commandName)Nájde súbor príkazu podľa názvu, ktorý používateľ použil.
try { … } catch (error) { … }Chyba v príkaze nesmie zhodiť bota. Používateľ dostane chybovú odpoveď a chyba sa vypíše do konzoly.
interaction.replied / interaction.deferredKeď už bot odpovedal alebo odpoveď odložil, ďalšiu správu musí poslať cez followUp.
client.login(process.env.DISCORD_TOKEN)Prihlási bota tokenom z .env.

Bota vyskúšajte v počítači:

bash
node index.js
Výstuptext
Ready! Logged in as MujBot#1234

Na Discorde napíšte /ping a vyberte príkaz svojho bota.

Odpoveď na dlhšie trvajúci príkaz#

Keď príkaz potrebuje viac než 3 sekundy (dopyt do databázy, volanie iného API), najprv odpoveď odložte a výsledok pošlite neskôr:

JavaScript
async execute(interaction) {
  await interaction.deferReply();
  const vysledek = await nejakaDlouhaPrace();
  await interaction.editReply(`Hotovo: ${vysledek}`);
}
  • interaction.deferReply() – okamžite potvrdí príjem. Používateľ vidí „bot premýšľa“. Pre odpoveď viditeľnú iba jemu použite deferReply({ flags: MessageFlags.Ephemeral }).
  • interaction.editReply(…) – nahradí „premýšľa“ skutočnou odpoveďou. Odloženú odpoveď musíte poslať do 15 minút.
  • nejakaDlouhaPrace() – vaša funkcia, ukážkový názov.

Odpoveď s embedom#

Embed je správa s farebným prúžkom, nadpisom a poľami. Hodí sa na prehľadné informácie. Príkaz /server ukáže údaje o serveri:

commands/utility/server.jsJavaScript
const { SlashCommandBuilder, EmbedBuilder, InteractionContextType } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('server')
    .setDescription('Informácie o tomto Discord serveri.')
    .setContexts(InteractionContextType.Guild),

  async execute(interaction) {
    const guild = interaction.guild;
    const embed = new EmbedBuilder()
      .setColor(0xe11d48)
      .setTitle(guild.name)
      .addFields(
        { name: 'Členov', value: String(guild.memberCount), inline: true },
        { name: 'Založený', value: guild.createdAt.toLocaleDateString('sk-SK'), inline: true },
      )
      .setTimestamp();
    await interaction.reply({ embeds: [embed] });
  },
};
ČasťČo robí
.setContexts(InteractionContextType.Guild)Príkaz pôjde použiť iba na serveri, nie v súkromných správach (tam by interaction.guild neexistoval).
new EmbedBuilder()Začne stavať embed.
.setColor(0xe11d48)Farba prúžku ako šestnástkové číslo (tu červená).
.setTitle(guild.name)Nadpis, tu názov servera.
.addFields({ name, value, inline })Pole s popisom a hodnotou. value musí byť text, preto String(…). inline: true dá polia vedľa seba.
guild.memberCountPočet členov servera. Funguje s intentom Guilds, bez privilegovaného intentu.
.setTimestamp()Do pätičky pridá aktuálny čas.
interaction.reply({ embeds: [embed] })Pošle embed. Do jednej správy je možné vložiť viac embedov v poli.

Po pridaní súboru spustite znova node deploy-commands.js, nový príkaz sa musí zaregistrovať.

Kto smie príkaz použiť#

Predvolené oprávnenia nastavíte priamo v definícii príkazu:

commands/moderace/smazat.jsJavaScript
const { SlashCommandBuilder, PermissionFlagsBits, InteractionContextType, MessageFlags } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('smazat')
    .setDescription('Zmaže posledné správy v kanáli.')
    .addIntegerOption((o) =>
      o.setName('pocet').setDescription('Koľko správ (1–100)').setMinValue(1).setMaxValue(100).setRequired(true),
    )
    .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
    .setContexts(InteractionContextType.Guild),

  async execute(interaction) {
    const pocet = interaction.options.getInteger('pocet');
    const smazane = await interaction.channel.bulkDelete(pocet, true);
    await interaction.reply({ content: `Zmazaných správ: ${smazane.size}`, flags: MessageFlags.Ephemeral });
  },
};
  • .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) – príkaz v predvolenom stave uvidia iba členovia s oprávnením Spravovať správy. Admin servera to môže v nastaveniach servera (Integrácie) zmeniť.
  • .setMinValue(1) a .setMaxValue(100) – Discord nedovolí zadať číslo mimo rozsahu.
  • interaction.channel.bulkDelete(pocet, true) – hromadne zmaže správy. true preskočí správy staršie než 14 dní, ktoré hromadne zmazať nejde.
  • Bot sám potrebuje oprávnenie Manage Messages v kanáli, inak skončí DiscordAPIError[50013]: Missing Permissions.

Členov s oprávnením Administrator žiadne nastavenie príkazu nezastaví. Príkazy iba pre vás (napríklad eval) preto registrujte iba na súkromnom testovacom serveri.

Udalosť: uvítanie nového člena#

Okrem príkazov reaguje bot na udalosti. Uvítanie nového člena potrebuje privilegovaný intent Server Members Intent: zapnite ho v Developer Portali na stránke Bot a doplňte ho do kódu.

events/guildMemberAdd.jsJavaScript
const { Events } = require('discord.js');

module.exports = {
  name: Events.GuildMemberAdd,
  once: false,
  async execute(member) {
    const kanal = member.guild.systemChannel;
    if (!kanal) return;
    await kanal.send(`Vitaj na serveri, ${member}!`);
  },
};

V index.js upravte intenty a pridajte načítanie udalostí (za načítanie príkazov):

index.jsJavaScript
const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
});

// … načítanie príkazov …

const eventsPath = path.join(__dirname, 'events');
for (const file of fs.readdirSync(eventsPath).filter((f) => f.endsWith('.js'))) {
  const event = require(path.join(eventsPath, file));
  if (event.once) {
    client.once(event.name, (...args) => event.execute(...args));
  } else {
    client.on(event.name, (...args) => event.execute(...args));
  }
}
  • GatewayIntentBits.GuildMembers – Discord potom posiela udalosti príchodu a odchodu členov. Bez zapnutého intentu v Developer Portali bot hlási Used disallowed intents.
  • member.guild.systemChannel – systémový kanál servera (kam Discord píše uvítania). Keď ho server nemá, bot nič nepošle.
  • ${member} – vloží zmienku nového člena (@meno).
  • client.once / client.on – udalosť s once: true sa spracuje iba raz (štart bota), ostatné zakaždým.

Štruktúra väčšieho bota#

  • Príkazy podľa kategórií v priečinkoch commands/moderace, commands/zabava … Načítanie vyššie ich prejde všetky.
  • Udalosti v priečinku events, každá vo vlastnom súbore s name, once a execute (pozri vyššie). Do events môžete presunúť aj obsluhu ClientReady a InteractionCreate.
  • Konfigurácia (ID kanálov, rolí) do config.json, tajné údaje do .env.
  • Dáta (body, varovania) do databázy, nie do premenných v pamäti, ktoré sa reštartom stratia.

Spustenie u StormHostu#

  1. Registráciu príkazov (node deploy-commands.js) spustite v počítači. Na serveri to nie je potrebné.
  2. Bota v počítači vypnite (Ctrl+C). Dve spustené kópie s rovnakým tokenom si navzájom kradnú interakcie.
  3. Nahrajte index.js, package.json, package-lock.json a priečinok commands so všetkými podpriečinkami do záložky Súbory. Ak ste pridali uvítanie nového člena, nahrajte aj priečinok events. Bez neho bot pri štarte spadne s chybou ENOENT: no such file or directory. .env vytvorte priamo na serveri. Postup: nahranie a spustenie bota.
  4. V záložke Nastavenia skontrolujte hlavný súbor index.js.
  5. V záložke Konzola kliknite na Spustiť.

Overenie#

  • V Konzole je Ready! Logged in as … a bot je na Discorde online.
  • Po napísaní / na testovacom serveri ponuka obsahuje ping a pozdrav s ikonou vášho bota.
  • /ping odpovie správou viditeľnou iba vám, /pozdrav jmeno:Jan verejnou správou.

Keď niečo nefunguje#

Príkazy sa v ponuke nezobrazujú#

  • Neprebehla registrácia: spustite node deploy-commands.js a skontrolujte výpis Hotovo, zaregistrovaných príkazov: ….
  • GUILD_ID je ID iného servera, než na ktorom skúšate.
  • Bot bol pozvaný bez scope applications.commands. Pozvite ho znova odkazom s bot aj applications.commands.
  • Discord si ponuku pamätá. Reštartujte aplikáciu Discord (Ctrl+R).

Registrácia skončí DiscordAPIError[50001]: Missing Access#

Bot nie je na serveri GUILD_ID, alebo na ňom nemá scope applications.commands. Skontrolujte ID servera a bota pozvite znova.

Discord píše „The application did not respond“ (aplikácia neodpovedala)#

Bot neodpovedal do 3 sekúnd. Buď nebeží (skontrolujte Konzolu), alebo príkaz trvá dlho a chýba deferReply(), alebo v príkaze nastala chyba pred odpoveďou (nájdete ju v Konzole).

DiscordAPIError[10062]: Unknown interaction#

Odpoveď prišla neskoro (po 3 sekundách) alebo bežia dve kópie bota s rovnakým tokenom a interakciu už spracovala tá druhá. Použite deferReply() a nechajte bežať iba jednu kópiu.

DiscordAPIError[40060]: Interaction has already been acknowledged#

Na jednu interakciu ste zavolali reply() dvakrát. Druhú správu pošlite cez followUp(), alebo po deferReply() použite editReply().

[WARNING] Súbor … nemá "data" alebo "execute".#

Súbor v priečinku commands neexportuje obe časti. Skontrolujte module.exports = { data, execute } a preklepy.

require is not defined in ES module scope alebo Cannot use import statement outside a module#

Miešanie CommonJS a ES modulov. Ukážky sú pre CommonJS: v package.json musí byť "type": "commonjs" (alebo pole type chýba). Podrobnosti v návode logy a chyby.

Zhrnutie

  • Slash príkaz = definícia (SlashCommandBuilder), registrácia (deploy-commands.js cez REST) a obsluha (Events.InteractionCreate).
  • Registráciu spúšťajte iba pri zmene definície. Pri vývoji serverovo (applicationGuildCommands), hotového bota globálne (applicationCommands).
  • Na interakciu odpovedzte do 3 sekúnd. Dlhšiu prácu začnite deferReply() a dokončite editReply().
  • Chyby v príkazoch chytajte cez try/catch, aby jedna chyba nezhodila bota. Nikdy nespúšťajte dve kópie s rovnakým tokenom.

Hľadanie v návodoch