VulCode
Blog

Jak zrobić bota Discord w Pythonie: od tokenu do komend slash

Zakładasz aplikację, zapraszasz bota, piszesz komendy slash w discord.py 2.7.1 i uruchamiasz go w kontenerze, który wstaje sam po restarcie serwera. Ten sam układ ma nasz bot mailcheck.

VulCodeVulCode11 min czytania
Jak zrobić bota Discord w Pythonie: od tokenu do komend slash - VulCode
Co zbudujeszPython, 9 kroków
  1. Aplikacja i tokenPortalBot w Discord Developer Portal.
  2. ZaproszenieOAuth2Zakresy bot i applications.commands.
  3. Komendy slashdiscord.pyCog z /ping i /serwer, embed.
  4. Bot, który wstaje samDockerKontener z restart: always.

Poradnik w skrócie

Zbudujesz bota z dwiema komendami slash: /ping odpowiada tylko pytającemu, a /serwer pokazuje embed z nazwą serwera, liczbą członków i datą założenia w strefie czasowej czytającego. Kod jest podzielony na cogi, więc kolejne komendy dokładasz w osobnych plikach.

Bot Discord w Pythonie

Grupa
Gry i Discord
Poziom
podstawowy
Czas czytania
około 11 min
Kod
Python

Pliki: bot.py, serwer.py, raport.py, Dockerfile, docker-compose.yml; do tego polecenia w terminalu.

Kroki
9 kroków

W 7 z nich kod albo polecenia do skopiowania.

Sprawdzenie
discord.py 2.7.1, bez łączenia z Discordem

Cog załadowany, w drzewie dokładnie /ping i /serwer

Dokumentacja
4 oficjalne dokumenty

discord.py, dokumentacja; discord.py, oficjalny przykład komend slash; discord.py w PyPI; Discord: odbieranie i odpowiadanie na interakcje

Z naszego kodu
mailcheck

boty/mailcheck/mailcheck.py

Zanim zaczniesz

Na końcu bot działa w kontenerze, który sam wstaje po restarcie serwera albo po awarii. Ten sam układ, czyli commands.Bot, cogi ładowane w setup_hook i synchronizacja komend z jednym serwerem w trakcie pracy, ma nasz bot mailcheck.

Co musisz mieć
Bot Discord w Pythonie
Konto Discord i własny serwer testowy
Na serwerze testowym musisz mieć uprawnienie Zarządzanie serwerem, żeby zaprosić bota. Identyfikator serwera skopiujesz po włączeniu trybu dewelopera w ustawieniach Discorda.
Python 3.8 lub nowszy
discord.py 2.7.1 wymaga Pythona 3.8 lub nowszego. W kontenerze z przykładu jest Python 3.12.
Miejsce na token
Token bota daje pełną kontrolę nad botem. Trzymaj go w zmiennej środowiskowej albo pliku .env poza repozytorium. Jeśli wycieknie, zresetuj go w portalu.
Serwer albo VPS na produkcję
Bot musi działać cały czas, więc w produkcji nie uruchamiasz go na swoim komputerze. Wystarczy mały VPS z Dockerem.

Co jest czym w Developer Portal

PoleGdzieDo czego
Application IDGeneral InformationIdentyfikator aplikacji, potrzebny w linku zaproszenia.
TokenBot > Reset TokenHasło bota do bramki Discorda. Pokazywany raz.
Privileged IntentsBotCzłonkowie, obecność, treść wiadomości. Ten bot ich nie potrzebuje.
OAuth2 URL GeneratorOAuth2Link zaproszenia z zakresami i uprawnieniami.

Krok po kroku

9 kroków. Okno obok pokazuje plik, polecenia albo pola kroku, który właśnie czytasz.

Utwórz aplikację i bota

W Discord Developer Portal kliknij New Application i nadaj nazwę. W zakładce Bot kliknij Reset Token i skopiuj token: portal pokazuje go tylko raz.

Komendy slash nie wymagają uprzywilejowanych intencji. Nie włączaj Message Content, Server Members ani Presence, dopóki bot naprawdę nie czyta treści wiadomości albo listy członków.

Zaproś bota na serwer

W OAuth2 > URL Generator zaznacz zakresy bot i applications.commands, a niżej tylko te uprawnienia, których bot używa. Dla tego bota wystarczy Send Messages i Embed Links. Otwórz wygenerowany link i wybierz serwer testowy.

Bez zakresu applications.commands komendy slash nie pojawią się na serwerze, nawet jeśli bot jest na liście członków.

Załóż projekt i zainstaluj discord.py

Osobne środowisko wirtualne i przypięta wersja biblioteki chronią przed sytuacją, w której aktualizacja na serwerze zmienia zachowanie bota bez Twojej wiedzy.

Napisz główny plik bota

commands.Bot ma wbudowane drzewo komend (self.tree). W setup_hook, który działa raz przed połączeniem, ładujesz cogi i synchronizujesz komendy. copy_global_to kopiuje komendy na jeden serwer, gdzie pojawiają się od razu, zamiast czekać na rozesłanie komend globalnych.

Dodaj komendy w cogu

Cog to klasa z komendami, ładowana przez load_extension. Opis komendy z dekoratora widzi użytkownik w Discordzie. ephemeral=True sprawia, że odpowiedź widzi tylko osoba, która wpisała komendę.

discord.utils.format_dt zamienia datę na znacznik <t:...:D>, który każdy czytający widzi w swojej strefie czasowej. Ten sam znacznik zbudujesz w naszym generatorze znaczników czasu.

Uruchom bota

Token i identyfikator serwera przekazujesz przez zmienne środowiskowe. Po starcie bot wypisze liczbę zsynchronizowanych komend i swoją nazwę. Wpisz / na serwerze testowym: /ping i /serwer powinny być na liście.

Odpowiadaj w 3 sekundy, nawet gdy praca trwa dłużej

Discord wymaga pierwszej odpowiedzi na interakcję w ciągu 3 sekund, inaczej token interakcji traci ważność i użytkownik widzi błąd. Gdy komenda liczy coś dłużej, najpierw wywołaj defer, a wynik wyślij przez followup: token jest ważny 15 minut.

Uruchom bota w kontenerze

Kontener z restart: always wstaje po restarcie serwera i po awarii bota. Token trafia do kontenera z pliku .env, który nie jest w obrazie ani w repozytorium.

Przejdź na komendy globalne

Kiedy bot ma działać na wielu serwerach, synchronizuj drzewo bez parametru guild. Komendy globalne rozchodzą się dłużej niż komendy jednego serwera, więc w trakcie pracy zostań przy serwerze testowym.

Okno kodukrok 1 z 9
Discord Developer Portal2 pola
TokenBot > Reset Token
Hasło bota do bramki Discorda. Pokazywany raz.
Privileged IntentsBot
Członkowie, obecność, treść wiadomości. Ten bot ich nie potrzebuje.
Discord Developer Portal2 pola
Application IDGeneral Information
Identyfikator aplikacji, potrzebny w linku zaproszenia.
OAuth2 URL GeneratorOAuth2
Link zaproszenia z zakresami i uprawnieniami.
bash5 poleceń
mkdir bot && cd botpython3 -m venv .venvsource .venv/bin/activatepip install "discord.py==2.7.1"mkdir cogs
bot.pyNowy plik, 30 linii
import os import discordfrom discord.ext import commands GUILD_ID = int(os.environ["GUILD_ID"])  class Bot(commands.Bot):    def __init__(self) -> None:        super().__init__(command_prefix="!", intents=discord.Intents.default())     async def setup_hook(self) -> None:        await self.load_extension("cogs.serwer")        guild = discord.Object(id=GUILD_ID)        self.tree.copy_global_to(guild=guild)        synced = await self.tree.sync(guild=guild)        print(f"Zsynchronizowane komendy: {len(synced)}")  bot = Bot()  @bot.eventasync def on_ready() -> None:    print(f"Zalogowano jako {bot.user} (ID {bot.user.id})")  if __name__ == "__main__":    bot.run(os.environ["DISCORD_TOKEN"])
cogs/serwer.pyNowy plik, 26 linii
import discordfrom discord import app_commandsfrom discord.ext import commands  class Serwer(commands.Cog):    def __init__(self, bot: commands.Bot) -> None:        self.bot = bot     @app_commands.command(name="ping", description="Sprawdza, czy bot odpowiada")    async def ping(self, interaction: discord.Interaction) -> None:        delay = round(self.bot.latency * 1000)        await interaction.response.send_message(f"Pong, {delay} ms", ephemeral=True)     @app_commands.command(name="serwer", description="Podstawowe informacje o serwerze")    @app_commands.guild_only()    async def serwer(self, interaction: discord.Interaction) -> None:        guild = interaction.guild        embed = discord.Embed(title=guild.name, color=0x1679FA)        embed.add_field(name="Członkowie", value=str(guild.member_count))        embed.add_field(name="Założony", value=discord.utils.format_dt(guild.created_at, "D"))        await interaction.response.send_message(embed=embed)  async def setup(bot: commands.Bot) -> None:    await bot.add_cog(Serwer(bot))
bash3 polecenia
export DISCORD_TOKEN="token-z-zakladki-bot"export GUILD_ID="123456789012345678"python bot.py
cogs/raport.pyNowy plik, 5 linii
@app_commands.command(name="raport", description="Raport, który liczy się dłużej")async def raport(self, interaction: discord.Interaction) -> None:    await interaction.response.defer(thinking=True)  # odpowiedź w 3 sekundy    wynik = await policz_raport()                     # tu możesz pracować do 15 minut    await interaction.followup.send(wynik)
Dockerfile, docker-compose.ymlNowy plik, 13 linii
# DockerfileFROM python:3.12-slimWORKDIR /appRUN pip install --no-cache-dir "discord.py==2.7.1"COPY . .CMD ["python", "-u", "bot.py"] # docker-compose.ymlservices:  bot:    build: .    restart: always    env_file: .env
bot.pyZmiana w pliku: 1 linia
async def setup_hook(self) -> None:    await self.load_extension("cogs.serwer")    synced = await self.tree.sync()  # komendy globalne, na każdym serwerze z botem    print(f"Zsynchronizowane komendy: {len(synced)}")

Sprawdź, czy działa

Kod z poradnika załadowaliśmy 6 października 2026 r. w środowisku z discord.py 2.7.1, bez łączenia z Discordem: cog się załadował, a drzewo komend miało dokładnie /ping i /serwer z opisami. format_dt dla 6 października 2026, 12:00 UTC dał znacznik <t:1791288000:D>. Po uruchomieniu z prawdziwym tokenem w konsoli zobaczysz liczbę zsynchronizowanych komend i nazwę bota.

terminal6 października 2026discord.py 2.7.1, bez łączenia z Discordem
$ python -c "import discord; print(discord.__version__)"
2.7.1
$ python test_drzewa.py
[('ping', 'Sprawdza, czy bot odpowiada'),
('serwer', 'Podstawowe informacje o serwerze')]
<t:1791288000:D>
Cog załadowany, w drzewie dokładnie /ping i /serwer
Co znaczą linie wyniku
  1. linie 1-2Wersja biblioteki

    W środowisku jest discord.py 2.7.1, ta sama wersja, pod którą napisany jest poradnik.

  2. linie 3-5Drzewo komend

    Cog się załadował, a drzewo ma dokładnie /ping i /serwer z opisami.

  3. linie 6Data w embedzie

    format_dt dla 6 października 2026, 12:00 UTC daje znacznik <t:1791288000:D>, który Discord pokazuje w strefie czasowej czytającego.

Po czym poznasz, że działa u Ciebie

  • Po starcie konsola pokazuje "Zsynchronizowane komendy: 2" i nazwę bota.
  • /ping odpowiada opóźnieniem w milisekundach i widzi to tylko pytający.
  • /serwer pokazuje embed z nazwą, liczbą członków i datą założenia.
  • Po docker compose up -d i restarcie serwera bot wraca sam.

Najczęstsze błędy

Ta sama droga co w "Co zbudujesz" na górze strony, z miejscami, w których praca staje najczęściej. Przy każdym błędzie: objaw, przyczyna i co zrobić.

Gdzie staje i dlaczego

Aplikacja i token

Portal

Bot w Discord Developer Portal.

  • Zatrzymuje się: LoginFailure: Improper token has been passed

    Token przepisany z błędem albo zresetowany w portalu. Co zrobić: Zresetuj token w zakładce Bot i wklej go ponownie do .env.

  • Zatrzymuje się: PrivilegedIntentsRequired przy starcie

    Kod włącza intencję members albo message_content, której nie zaznaczono w portalu. Co zrobić: Włącz intencję w zakładce Bot albo usuń ją z kodu, jeśli bot jej nie potrzebuje.

Zaproszenie

OAuth2

Zakresy bot i applications.commands.

  • Zatrzymuje się: Komendy nie pojawiają się na serwerze

    Brak zakresu applications.commands w zaproszeniu albo komendy globalne, które jeszcze się nie rozeszły. Co zrobić: Zaproś bota ponownie z oboma zakresami i synchronizuj z serwerem testowym przez copy_global_to.

  • Zatrzymuje się: 403 Missing Access przy synchronizacji

    Bota nie ma na serwerze o podanym GUILD_ID albo ID jest błędne. Co zrobić: Sprawdź ID serwera w trybie dewelopera i zaproś bota na ten serwer.

Komendy slash

discord.py

Cog z /ping i /serwer, embed.

  • Zatrzymuje się: "Aplikacja nie odpowiada" po komendzie

    Brak odpowiedzi w ciągu 3 sekund. Co zrobić: Wywołaj interaction.response.defer() na początku i odpowiedz przez followup.

Bot, który wstaje sam

Docker

Kontener z restart: always.

    Jak to robimy u siebie

    Ten sam temat w kodzie, który utrzymujemy: mailcheck.

    Serwer Discord z powitaniem bota w embedzie i przyciskami wyboru ról

    Tak startuje nasz bot mailcheck

    Mailcheck to nasz bot w discord.py 2.7.1, który pilnuje skrzynek pocztowych i odpowiada na komendy slash. Ma trzy cogi: administracyjny, statystyki i przeglądarkę wiadomości, ładowane w setup_hook dokładnie tak jak w poradniku.

    Jedna różnica: GUILD_ID jest opcjonalny. Z nim komendy trafiają na jeden serwer od razu, bez niego bot synchronizuje je globalnie. Na koniec setup_hook uruchamia w tle pętlę sprawdzania poczty, która działa obok obsługi komend.

    boty/mailcheck/mailcheck.pypython, 20 linii
    class MailcheckBot(commands.Bot):    def __init__(self):        super().__init__(command_prefix="!", intents=discord.Intents.default())        self._last_check: dict[str, float] = {}     async def setup_hook(self):        await self.load_extension("cog_admin")        await self.load_extension("cog_stats")        await self.load_extension("cog_browser")         if GUILD_ID:            guild = discord.Object(id=GUILD_ID)            self.tree.copy_global_to(guild=guild)            synced = await self.tree.sync(guild=guild)        else:            synced = await self.tree.sync()        names = ", ".join(f"/{c.name}" for c in synced)        print(f"[sync] {len(synced)} komend: {names}")         asyncio.create_task(self._mail_loop())

    Fragment bota mailcheck.

    Powiązane strony i źródła

    Usługa, narzędzia i hasła, do których odsyła ten poradnik, i kolejne poradniki.

    Oficjalna dokumentacja

    Wolisz, żeby zrobił to ktoś, kto już to robił?

    Opisz swój system. Odpiszemy w godzinę z planem integracji i wyceną.

    Pytania