# ŻyrarBOT Bot muzyczny Discord z panelem webowym: kolejka odtwarzania, historia, serwerowa baza plików i sterowanie na żywo przez WebSocket. ## Uruchomienie ```bash pip install -r requirements.txt python main.py # albo: python -m zyrarbot ``` Wymagane zmienne środowiskowe (plik `.env`): | Zmienna | Opis | | --------------- | ----------------------------------------------------------- | | `DISCORD_TOKEN` | Token bota Discord. | | `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. W Dockerze jest dostępny pod `http://localhost` po wykonaniu `docker compose up --build`. ### Konta i dostępy panelu Panel wymaga logowania. Utwórz pierwszego administratora w działającym kontenerze: ```bash docker compose exec discord-bot python -m zyrarbot create-admin admin ``` Komenda poprosi o hasło (minimum 10 znaków). Administrator ma dostęp do wszystkich serwerów i strony `/admin`, gdzie może tworzyć konta użytkowników, przypisywać im dostęp do serwerów oraz restartować bota. Zwykły użytkownik widzi wyłącznie przypisane mu serwery. Konta i sesje są przechowywane w PostgreSQL. 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//`. ### 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: ```bash 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 ```bash 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 - **`MusicPlayer` jest 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.state` i funkcje z `api/dependencies.py`. Nie ma globalnych singletonów.