Files
zyrarbot/README.md
T
mmacierzanka 6f50fce3f9 feat: introduce database as a persistent storage for settings and history
refactor: arrange persistence layer as a port-adapter schema
2026-09-30 17:42:09 +02:00

137 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ż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`.
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. Uploady pozostają w
katalogach `uploads/<guild_id>/`.
## 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/ PostgresGuildConfig, PostgresHistory, PostgresQueue,
│ │ │ PostgresPersistence, PostgresDatabase
│ │ ├── 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.