Files
stoat-mbot/README.md
T
Leonid PershinandClaude Opus 5 a9b7ccdd16 Add music bot for self-hosted Stoat with web control panel
Plays audio into Stoat voice channels over LiveKit and exposes the same
player through both chat commands and a browser panel, so the two never
drift apart: everything routes through a single MusicManager.

- core: per-server GuildPlayer (queue, loop, shuffle, seek, volume,
  idle auto-leave) driving revoice.js/@livekit/rtc-node and ffmpeg
- sources: yt-dlp for YouTube/SoundCloud, direct media URLs and internet
  radio, optional local library with path-traversal guards
- bot: 18 chat commands with aliases, plus !panel one-time login links
- api: Fastify REST + WebSocket, sessions authenticated against the
  instance's own /auth/session/login (TOTP supported), permissions
  re-checked against Stoat membership and roles on every request
- web: React panel with search, queue editing, seek and volume
- deploy: Dockerfile, compose.override.yml and Caddyfile snippets for
  dropping the service into an existing /opt/stoat stack

Verified with npm run typecheck, both builds, and scripts/smoke-api.mjs
(9 API checks). Voice playback itself needs a live instance to test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 23:12:43 +03:00

180 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.
# stoat-mbot
Музыкальный бот для self-hosted [Stoat](https://github.com/stoatchat/self-hosted) с веб-панелью:
поиск и воспроизведение в голосовых каналах, очередь, перемотка, громкость — из чата и из браузера,
состояние синхронизировано в обе стороны через WebSocket.
**Источники:** YouTube, SoundCloud, прямые ссылки и интернет-радио, локальная медиатека (опционально).
**Голос:** LiveKit — тот же, что и в вашем инстансе (`revoice.js` + `@livekit/rtc-node`), звук готовит `ffmpeg`.
**Вход в панель:** учётными записями вашего же Stoat (пароль уходит прямо в `/auth/session/login`
вашего инстанса, бот его не хранит) либо одноразовой ссылкой по команде `!panel`.
---
## Как это устроено
```
Stoat (чат) ──messageCreate──► bot/commands ─┐
├──► MusicManager ──► GuildPlayer ──► LiveKit
Браузер ──REST + WebSocket──► api/server ───┘ (очередь, (ffmpeg,
громкость, yt-dlp)
повтор)
```
Чат-команды и панель дергают один и тот же `MusicManager`, поэтому «нажал в браузере — увидел в чате»
работает без рассинхрона. Каждое действие панели повторно проверяет членство и права в Stoat,
так что доступ живёт в ролях Stoat, а не в отдельной базе бота.
| Слой | Файлы |
| --- | --- |
| Источники и yt-dlp | [src/sources](src/sources) |
| Плеер и очередь | [src/core/player.ts](src/core/player.ts), [src/core/manager.ts](src/core/manager.ts) |
| Чат-бот | [src/bot](src/bot) |
| REST + WebSocket | [src/api/server.ts](src/api/server.ts) |
| Веб-панель (React) | [web/src](web/src) |
---
## Установка рядом со Stoat
Предполагается раскладка `/opt/stoat` (инстанс) и `/opt/stoat-mbot` (этот репозиторий).
### 1. Клонировать репозиторий
```bash
cd /opt && git clone https://gitea.hsrv.site/mrleo1nid/stoat-mbot.git stoat-mbot
```
### 2. Создать бота в Stoat
Settings → **My Bots** → создать бота → скопировать токен → пригласить бота на сервер.
Боту нужны права: читать сообщения, писать сообщения и подключаться к голосовым каналам.
### 3. Заполнить `.env`
```bash
cd /opt/stoat-mbot
cp .env.example .env
openssl rand -hex 32 # → JWT_SECRET
```
Минимум, что нужно указать:
```dotenv
STOAT_API_URL=https://stoat.example.com/api
STOAT_BOT_TOKEN=<токен бота>
PUBLIC_URL=https://music.example.com
JWT_SECRET=<случайные 32 байта>
```
`STOAT_API_URL` лучше указывать публичный (`https://домен/api`): бот тогда ведёт себя как обычный
клиент и получает от `join_call` тот же LiveKit-URL, что и все. Внутренний `http://api:14702` тоже
работает, но убедитесь, что LiveKit-URL из ответа резолвится изнутри контейнера.
### 4. Добавить сервис в `compose.override.yml`
В `/opt/stoat/compose.override.yml` (готовый пример — [deploy/compose.override.yml.example](deploy/compose.override.yml.example)):
```yaml
services:
caddy:
ports: !override
- "127.0.0.1:8880:80"
mbot:
build: ../stoat-mbot
restart: always
env_file: ../stoat-mbot/.env
depends_on:
api:
condition: service_started
volumes:
- ../stoat-mbot/data:/data
```
Сервис попадает в тот же compose-проект и ту же сеть, поэтому Caddy видит его как `http://mbot:3005`,
а бот ходит в Stoat API по имени `api`.
### 5. Пробросить панель через Caddy
В `/opt/stoat/Caddyfile` добавьте блок ([deploy/Caddyfile.snippet](deploy/Caddyfile.snippet)):
```caddyfile
http://music.example.com {
reverse_proxy http://mbot:3005
}
```
Схема `http://` — потому что у вас TLS терминирует внешний прокси (Caddy слушает `127.0.0.1:8880`).
WebSocket проксируется автоматически.
### 6. Запустить
```bash
cd /opt/stoat && docker compose up -d --build mbot && docker compose logs -f mbot
```
В логах должно появиться `yt-dlp detected`, `bot is ready` и `panel is listening`.
---
## Использование
В чате (префикс по умолчанию `!`):
| Команда | Что делает |
| --- | --- |
| `!play <ссылка или название>` | добавить трек/плейлист в очередь |
| `!playnext`, `!playnow` | следующим / немедленно |
| `!search <запрос>``!pick <n>` | поиск с выбором из списка |
| `!skip [n]`, `!stop`, `!pause`, `!resume` | управление воспроизведением |
| `!queue [страница]`, `!nowplaying` | очередь и текущий трек |
| `!volume [0-200]`, `!loop [off\|track\|queue]`, `!shuffle` | звук и порядок |
| `!remove <n>`, `!clear`, `!seek 1:23` | правка очереди и перемотка |
| `!join`, `!leave` | зайти в ваш голосовой канал / выйти |
| `!panel` | личная ссылка на веб-панель (действует 10 минут) |
| `!help` | список команд |
Префиксы поиска: `sc:` — SoundCloud, `yt:` — YouTube, `local:` — локальная медиатека.
В панели: поиск с добавлением в очередь/следующим/сейчас, drag-free перестановка треков стрелками,
клик по полосе прогресса — перемотка, слайдер громкости, выбор голосового канала, история.
---
## Настройки
Все параметры — в [.env.example](.env.example). Что стоит знать:
- `REQUIRE_DJ_ROLE=true` — управлять смогут только владелец сервера, обладатели `ManageServer`
и роли из `DJ_ROLE_NAME`. По умолчанию `false`: играть может любой участник сервера.
- `IDLE_TIMEOUT_SECONDS` — через сколько секунд простоя (или пустого канала) бот выходит из войса.
- `LOCAL_MEDIA_DIR` — примонтируйте том с музыкой и укажите путь внутри контейнера,
тогда заработают `local:` и поиск по медиатеке.
- `YTDLP_COOKIES` — путь к `cookies.txt`, если YouTube просит подтверждения возраста или логина.
## Разработка
```bash
npm install && npm --prefix web install
cp .env.example .env # STOAT_API_URL можно указать публичный адрес инстанса
npm run dev # бот + API на :3005
npm run web:dev # панель на :5180 с проксированием на :3005
```
Проверки: `npm run typecheck`, `npm run build`, `npm --prefix web run build`.
Локально нужен `yt-dlp` в `PATH` (или укажите `YTDLP_PATH`); `ffmpeg` приезжает с `ffmpeg-static`.
## Ограничения
- `revoice.js` — сторонняя библиотека без ретраев на разрыв LiveKit-соединения; при падении войса
бот выходит из канала, повторный `!join` восстанавливает работу.
- Перемотка для YouTube/SoundCloud перезапускает поток с нужной позиции (одна лишняя обращение к
yt-dlp), для локальных файлов и прямых ссылок — мгновенная.
- Скачивание с YouTube формально противоречит его ToS; используйте на своё усмотрение,
для «чистого» сценария есть SoundCloud, прямые ссылки и локальная медиатека.
## Лицензия
MIT — см. [LICENSE](LICENSE).