# Conflicts: # zyrarbot/ui/api/routers/auth_router.py
ŻyrarBOT
Bot muzyczny Discord z panelem webowym: kolejka odtwarzania, historia, serwerowa baza plików i sterowanie na żywo przez WebSocket.
Uruchomienie
pip install -r requirements.txt
python main.py # albo: python -m zyrarbot
Zmienne środowiskowe (plik .env):
| Zmienna | Opis |
|---|---|
DISCORD_TOKEN |
Token bota Discord. |
DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRET |
Dane aplikacji OAuth2 z Discord Developer Portal. |
DISCORD_REDIRECT_URI |
Publiczny adres callbacku, np. https://bot.example.com/api/auth/discord/callback. |
DISCORD_ADMIN_IDS |
Opcjonalne, przecinkami oddzielone Discord User ID administratorów panelu. |
GUILD_ID |
Opcjonalnie - synchronizuje komendy tylko z jednym serwerem. |
POSTGRES_PASSWORD |
Hasło użytkownika bazy; domyślnie zyrarbot, zalecana zmiana w .env. |
POSTGRES_DB / POSTGRES_USER |
Opcjonalna nazwa bazy i użytkownika; domyślnie zyrarbot. |
Panel działa pod http://localhost:8000 podczas uruchomienia lokalnego.
Docker: development i production
Środowisko deweloperskie uruchom poleceniem:
docker compose up --build
Kod Pythona jest montowany z katalogu projektu; po zmianach zrestartuj backend
poleceniem docker compose restart discord-bot. Frontend działa w osobnym
kontenerze i przebudowuje frontend/dist w trybie watch. Python serwuje ten sam
współdzielony katalog pod http://localhost. Port PostgreSQL 5432 jest
dostępny lokalnie. Obraz dev zawiera też Ruff, a kontener frontendu instaluje
narzędzia z package-lock.json (ESLint i vue-tsc).
Sprawdzenia uruchom w kontenerze backendu, np. docker compose exec discord-bot ruff check . i docker compose exec discord-bot pytest; dla frontendu użyj
docker compose exec frontend npm run lint lub npm run typecheck.
Production uruchom z pliku compose.prod.yaml:
docker compose -f compose.prod.yaml up --build -d
W tym wariancie frontend jest budowany podczas tworzenia obrazu, a port PostgreSQL nie jest publikowany na hoście.
Konta i dostępy panelu
Panel obsługuje logowanie hasłem oraz Discord OAuth2. Przy logowaniu przez Discord
dostęp do sterowania na danym serwerze dostaje
użytkownik, który ma tam uprawnienie Manage Server (lub Administratora), o ile bot
również jest na tym serwerze. Dostęp odświeża się przy każdym logowaniu. Administratorów
panelu wskaż przez DISCORD_ADMIN_IDS (Discord User ID, oddzielone przecinkami); mają
dostęp do /admin i wszystkich serwerów, na których działa bot. Uprawnienie Administratora
na serwerze daje dostęp do sterowania tym serwerem, ale samo nie czyni użytkownika
administratorem panelu. Logowanie hasłem nadal działa. Przycisk Discord pojawia się tylko,
gdy ustawione są wszystkie trzy zmienne OAuth: Client ID, Client Secret i Redirect URI.
Dodaj do .env wartości aplikacji OAuth2 (nie publikuj Client Secret):
DISCORD_CLIENT_ID=123456789012345678
DISCORD_CLIENT_SECRET=wklej_client_secret
DISCORD_REDIRECT_URI=https://bot.example.com/api/auth/discord/callback
DISCORD_ADMIN_IDS=123456789012345678
Konfiguracja Discord Developer Portal
- Wybierz lub utwórz aplikację w Discord Developer Portal.
- W OAuth2 → General skopiuj Client ID i wygeneruj Client Secret.
- W OAuth2 → General → Redirects dodaj dokładnie wartość
DISCORD_REDIRECT_URI, np.https://bot.example.com/api/auth/discord/callback. Adres musi być osiągalny przez przeglądarkę użytkownika. Lokalnie z Docker Compose użyjhttp://localhost/api/auth/discord/callback; przy uruchomieniu bezpośrednio na porcie 8000 użyjhttp://localhost:8000/api/auth/discord/callback. - Ustaw
DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRETiDISCORD_REDIRECT_URIw.env.DISCORD_ADMIN_IDSjest opcjonalne; wpisz tam własny User ID, jeśli konto ma zarządzać panelem. Aby skopiować ID, włącz Developer Mode w Discordzie, kliknij swój profil prawym przyciskiem i wybierz Copy User ID. Kolejne ID oddzielaj przecinkami. - Aby dodać bota na serwery, w Installation włącz instalację dla serwerów i zakresy
botorazapplications.commands; przyznaj wymagane uprawnienia bota (co najmniej View Channels, Connect, Speak i Send Messages). OAuth logowania używa zakresówidentifyiguilds; nie wymaga dodatkowych intentów Gateway. - Zrestartuj usługę:
docker compose up -d --build. Otwórz panel i wybierz Zaloguj przez Discord. Discord poprosi o zakresyidentifyiguilds; zaakceptuj je, aby panel mógł sprawdzić konto i serwery użytkownika.
Panel obsługuje wiele serwerów Discorda. Wybór serwera w nagłówku przełącza jego
osobną kolejkę, historię, bazę plików i ustawienia odtwarzania. Ustawienia, historia
i migawki kolejek przechodzą przez porty persystencji, a PostgreSQL jest obecnym
adapterem wybranym w korzeniu aplikacji. Baza działa jako usługa Compose, a jej dane
trwale przechowuje wolumen postgres_data. Przy pierwszym uruchomieniu
bot importuje istniejące pliki JSON z config/ do bazy, a pliki źródłowe zostają
na miejscu jako kopia. Starsze wspólne dane są przypisywane do pierwszego serwera,
do którego bot należy. Historia jest zapisywana w kolumnach id, title, url,
duration i is_local, wraz z guild_id oraz pozycją. Uploady pozostają w
katalogach uploads/<guild_id>/.
Migracje bazy
Przy uruchomieniu bot automatycznie stosuje oczekujące migracje PostgreSQL.
Można je też uruchomić jawnie przez make migrate. Makefile korzysta wyłącznie
z Dockera i sprawdza, czy dostępne są Docker oraz docker compose.
Zastosowane wersje i ich nazwy są zapisywane w tabeli schema_migrations.
Każda migracja jest osobną klasą z metodami apply i rollback, w osobnym
pliku w zyrarbot/storage/adapters/postgres/migrations/. Pliki należy nazywać
numerami z zerami wiodącymi, aby porządek leksykograficzny pokrywał się z kolejnością,
na przykład 0001_initial_schema.py, 0002_add_something.py. Migrację trzeba
dodać także do krotki MIGRATIONS w migrations/__init__.py. Obecna migracja
0001_initial_schema tworzy początkowy schemat.
Rollback wszystkich wersji wykonaj poleceniem:
make migration-rollback
Aby cofnąć wersje nowsze niż wskazana, ustaw TO, na przykład
make migration-rollback TO=1 pozostawi migrację w wersji 1. Przed rollbackiem
zatrzymaj bota poleceniem docker compose stop discord-bot. Rollback wykonuje migracje w odwrotnej kolejności,
usuwa ich wpisy z schema_migrations i może trwale skasować dane z cofanych tabel.
Po rollbacku uruchomienie bota ponownie zastosuje brakujące migracje.
Komendy Discorda
Wszystkie komendy działają w kontekście bieżącego serwera. Discord pokazuje wybór utworów przez autocomplete, a kanały przez natywne selektory — nie trzeba wpisywać indeksów ręcznie.
| Komenda | Działanie |
|---|---|
/player |
Publiczny panel odtwarzacza z przyciskami sterowania |
/play query |
Dodaje utwór, link lub zapytanie wyszukiwania |
/playfile filename |
Odtwarza plik z biblioteki serwera |
/files |
Otwiera listę plików z natywnym wyborem Discorda |
/upload file |
Przesyła i waliduje plik audio/wideo do biblioteki |
/deletefile filename |
Usuwa wybrany plik z biblioteki |
/replay track_id |
Ponownie odtwarza utwór z historii |
/queue |
Pokazuje kolejkę w embedzie |
/history |
Pokazuje historię odtwarzania |
/restorequeue |
Przywraca ostatnią zapisaną kolejkę |
/jump track_id |
Rozpoczyna wybrany utwór z kolejki |
/remove track_id |
Usuwa wybrany utwór z kolejki |
/move track_id before_id |
Przenosi utwór przed wybrany utwór docelowy |
/trackplays track_id count |
Ustawia liczbę odtworzeń utworu |
/pause, /resume |
Wstrzymuje lub wznawia odtwarzanie |
/seek seconds |
Przewija bieżący utwór do wskazanej sekundy |
/skip |
Pomija bieżący utwór |
/stop |
Zatrzymuje odtwarzanie i czyści kolejkę |
/volume volume |
Ustawia głośność w zakresie 1–100 |
/nowplaying |
Pokazuje bieżący utwór |
/loop |
Przełącza tryb zapętlania |
/loopplaylist |
Włącza lub wyłącza zapętlanie kolejki |
/defaultplays count |
Ustawia domyślną liczbę odtworzeń dla nowych utworów |
/connect channel |
Łączy bota z wybranym kanałem głosowym |
/notificationchannel channel |
Ustawia kanał powiadomień |
/leave |
Rozłącza bota z kanału głosowego |
Testy
python -m unittest discover -s tests -t .
Struktura pakietu
Każda klasa mieszka w osobnym pliku, a pliki są pogrupowane w przestrzenie nazw odpowiadające warstwom aplikacji:
zyrarbot/
├── settings.py Settings - konfiguracja ze zmiennych środowiskowych
├── application.py Application - korzeń kompozycji, spina bota i API
├── storage/
│ ├── ports/ GuildConfigRepository, HistoryRepository,
│ │ QueueRepository, UploadsRepository, Persistence
│ ├── adapters/
│ │ ├── postgres/ adaptery PostgreSQL oraz migrations/ z runnerem
│ │ ├── filesystem/ Adapter plików uploadu
│ │ └── json/ Adapter plikowego magazynu JSON
│ └── upload_file_info.py UploadFileInfo
├── audio/ Pozyskiwanie dźwięku
│ ├── ytdl_options.py YtdlOptions
│ ├── ytdl_source.py YTDLSource
│ ├── media_probe.py MediaProbe
│ └── track_factory.py TrackFactory
├── music/ Domena odtwarzania
│ ├── loop_mode.py LoopMode
│ ├── history_entry.py HistoryEntry
│ ├── guild_music_state.py GuildMusicState
│ ├── track_embed_factory.py TrackEmbedFactory
│ ├── notification_channel_resolver.py NotificationChannelResolver
│ └── music_player.py MusicPlayer
└── ui/ Warstwa interfejsów użytkownika
├── api/ API panelu (FastAPI)
│ ├── factory.py mount_api - konfiguracja i routery API
│ ├── connection_manager.py ConnectionManager
│ ├── status_service.py StatusService
│ ├── status_broadcaster.py StatusBroadcaster
│ ├── dependencies.py wstrzykiwanie zależności z app.state
│ ├── requests/ modele żądań - jeden na plik
│ └── routers/ trasy pogrupowane tematycznie
└── bot/ Warstwa Discorda
├── client.py MusicBot
├── discord_theme.py wspólny styl embedów Discorda
├── guild_resolver.py GuildResolver
└── cogs/ komendy i listenery Discorda
├── common.py wspólne zależności i helpery
├── playback_cog.py PlaybackCog
├── queue_cog.py QueueCog
├── control_cog.py ControlCog
├── voice_cog.py VoiceCog
└── settings_cog.py SettingsCog
Zasady
MusicPlayerjest jedynym właścicielem stanu odtwarzania. Zarówno komendy Discorda, jak i endpointy panelu operują wyłącznie przez niego - nie ma już dwóch niezależnych ścieżek modyfikujących kolejkę.- Kolejka jest nieusuwalna, pozycję śledzi indeks (
GuildMusicState.index), dzięki czemu działa przeskakiwanie i przewijanie w tył. - Zależności wędrują przez konstruktory, a w API przez
app.statei funkcje zapi/dependencies.py. Nie ma globalnych singletonów.