# 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 ` | поиск с выбором из списка | | `!skip [n]`, `!stop`, `!pause`, `!resume` | управление воспроизведением | | `!queue [страница]`, `!nowplaying` | очередь и текущий трек | | `!volume [0-200]`, `!loop [off\|track\|queue]`, `!shuffle` | звук и порядок | | `!remove `, `!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).