Files
zyrarbot/README.md
T

188 lines
8.9 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.
### Docker: development i production
Środowisko deweloperskie uruchom poleceniem:
```bash
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`:
```bash
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.
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:
```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.