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
2026-09-08 19:37:00 +00:00

stoat-mbot

Музыкальный бот для self-hosted Stoat с веб-панелью: поиск и воспроизведение в голосовых каналах, очередь, перемотка, громкость — из чата и из браузера, состояние синхронизировано в обе стороны через 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/core/player.ts, src/core/manager.ts
Чат-бот src/bot
REST + WebSocket src/api/server.ts
Веб-панель (React) web/src

Установка рядом со Stoat

Предполагается раскладка /opt/stoat (инстанс) и /opt/stoat-mbot (этот репозиторий).

1. Клонировать репозиторий

cd /opt && git clone https://gitea.hsrv.site/mrleo1nid/stoat-mbot.git stoat-mbot

2. Создать бота в Stoat

Settings → My Bots → создать бота → скопировать токен → пригласить бота на сервер. Боту нужны права: читать сообщения, писать сообщения и подключаться к голосовым каналам.

3. Заполнить .env

cd /opt/stoat-mbot
cp .env.example .env
openssl rand -hex 32   # → JWT_SECRET

Минимум, что нужно указать:

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):

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):

http://music.example.com {
	reverse_proxy http://mbot:3005
}

Схема http:// — потому что у вас TLS терминирует внешний прокси (Caddy слушает 127.0.0.1:8880). WebSocket проксируется автоматически.

6. Запустить

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. Что стоит знать:

  • REQUIRE_DJ_ROLE=true — управлять смогут только владелец сервера, обладатели ManageServer и роли из DJ_ROLE_NAME. По умолчанию false: играть может любой участник сервера.
  • IDLE_TIMEOUT_SECONDS — через сколько секунд простоя (или пустого канала) бот выходит из войса.
  • LOCAL_MEDIA_DIR — примонтируйте том с музыкой и укажите путь внутри контейнера, тогда заработают local: и поиск по медиатеке.
  • YTDLP_COOKIES — путь к cookies.txt, если YouTube просит подтверждения возраста или логина.

Разработка

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.

S
Description
No description provided
Readme MIT
928 KiB
Languages
TypeScript 92.5%
CSS 4.1%
JavaScript 2.1%
Dockerfile 0.9%
Vim Snippet 0.2%
Other 0.2%