Files
stoat-mbot/.env.example
T
Leonid PershinandClaude Opus 5 49b87dc172 Publish the clip as a screen share alongside the audio
VIDEO_ENABLED makes the bot publish a second LiveKit track with the
picture. Stoat only grants screen_share in the call token when the bot
has the Video permission and the instance has video enabled, so a refusal
is reported in chat and playback continues with sound alone.

Two constraints shaped the pipeline, both found by testing rather than
assumption:

- Only progressive formats can be streamed. Separate video+audio streams
  make yt-dlp download both in full before muxing a single byte, and
  direct CDN URLs handed to ffmpeg simply hang — YouTube no longer serves
  them to other clients. That caps video at the 360p single file YouTube
  offers, and the format is checked before committing to the video path,
  since audio would otherwise come from the same broken pipeline.
- One ffmpeg with two outputs, paced by -re: an unpaced decode races
  ahead of the sound and eats memory at 1.4 MB per frame.

The same CDN-URL finding removes the audio seek shortcut, which resolved
such a URL and would have hung the same way; seeking now decodes up to
the offset like it already did behind a proxy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:10:11 +03:00

112 lines
6.5 KiB
Bash

# ---------------------------------------------------------------- Stoat ----
# Публичный адрес API вашего инстанса (тот же, что в клиенте).
# Бот ходит по нему как обычный клиент; домен резолвится в хост через
# extra_hosts в compose.yml, поэтому TLS валидный и NAT loopback не нужен.
STOAT_API_URL=https://chat.example.com/api
# Домен вашего Stoat — тот же, что в STOAT_API_URL, без схемы.
# Используется в compose.yml: контейнер резолвит его в сам хост, где внешний
# Caddy держит валидный TLS. Сам compose.yml править не нужно.
STOAT_DOMAIN=chat.example.com
# Токен бота: Settings → My Bots → создать бота → скопировать токен.
STOAT_BOT_TOKEN=
# Префикс команд в чате.
COMMAND_PREFIX=!
# Удалять сообщение с командой после её распознавания (нужно право ManageMessages).
DELETE_COMMAND_MESSAGES=true
# Имя LiveKit-ноды из Revolt.toml, секция [hosts.livekit].
# В стандартном self-hosted это "worldwide".
VOICE_NODE=worldwide
# ------------------------------------------------------------- Веб-панель ---
PORT=3005
HOST=0.0.0.0
# Адрес, по которому панель открывается в браузере (без слэша в конце).
PUBLIC_URL=https://music.example.com
# Секрет для подписи сессионных cookie. Сгенерируйте: openssl rand -hex 32
JWT_SECRET=
# Срок жизни сессии панели, часов.
SESSION_TTL_HOURS=168
# ---------------------------------------------------------------- Аудио ----
# Путь к yt-dlp. В docker-образе он уже установлен.
YTDLP_PATH=yt-dlp
# Необязательно: cookies.txt аккаунта YouTube — снимает возрастные ограничения,
# «Sign in to confirm you're not a bot» и открывает приватные/платные видео.
# Файл должен быть доступен на ЗАПИСЬ: yt-dlp обновляет в нём ротируемые куки.
# Подробности — в README, раздел «Учётка YouTube (cookies)».
# YTDLP_COOKIES=/data/cookies.txt
# Необязательно: прокси для всех запросов yt-dlp — YouTube и SoundCloud,
# поиск, метаданные и сам аудиопоток. Пригодится, когда из сети сервера
# YouTube недоступен (Psiphon, свой SOCKS и т.п.).
# Схемы: http://, https://, socks5:// и socks5h:// (socks5h резолвит DNS на
# стороне прокси — обычно нужен именно он). Можно с логином и паролем:
# socks5h://user:pass@host:1080
# YTDLP_PROXY=socks5h://127.0.0.1:1080
# Где применять прокси: all — везде (по умолчанию), search — только поиск и
# метаданные, а сам аудиопоток качать напрямую. Второй вариант выручает, когда
# выходной IP прокси ловит от YouTube «Sign in to confirm you're not a bot»,
# а без прокси скачивание работает.
# YTDLP_PROXY_SCOPE=search
#
# Внимание: 127.0.0.1 внутри контейнера — это сам контейнер. Если прокси поднят
# на хосте, используйте socks5h://host.docker.internal:1080 и добавьте в
# compose.yml к extra_hosts строку "host.docker.internal:host-gateway".
# JS-рантайм для yt-dlp: YouTube требует исполнять свой JS, иначе извлечение
# деградирует и начинаются бот-проверки. В образе уже есть Node, поэтому по
# умолчанию используется он. Пустое значение отключает флаг.
YTDLP_JS_RUNTIME=node
# Необязательно: дополнительные --extractor-args, через ";".
# Помогает, когда YouTube не отдаёт форматы серверному IP:
# YTDLP_EXTRACTOR_ARGS=youtube:player_client=default,web_safari
# Необязательно: каталог с локальной медиатекой (смонтируйте том).
# LOCAL_MEDIA_DIR=/media/music
# Показывать клип в голосовом канале как демонстрацию экрана (только YouTube).
# Требует прав Video у бота и включённого видео в конфигурации инстанса.
# YouTube отдаёт одним файлом только 360p, а стримить можно лишь такой формат —
# отсюда размер по умолчанию. Кодирование видео заметно грузит CPU сервера.
VIDEO_ENABLED=false
VIDEO_WIDTH=640
VIDEO_HEIGHT=360
VIDEO_FPS=24
DEFAULT_VOLUME=60
MAX_QUEUE_SIZE=500
# Сколько треков максимум добавит одна ссылка на плейлист или микс.
MAX_PLAYLIST_TRACKS=100
SEARCH_RESULT_LIMIT=10
# Через сколько секунд после ухода ПОСЛЕДНЕГО человека бот покидает голосовой
# канал (0 — не выходить никогда). Пока в канале кто-то есть, бот остаётся,
# даже если очередь давно закончилась.
EMPTY_TIMEOUT_SECONDS=120
# --------------------------------------------------------------- Доступ ----
# false — управлять может любой участник сервера.
# true — только владелец, ManageServer или роль DJ_ROLE_NAME.
REQUIRE_DJ_ROLE=false
DJ_ROLE_NAME=DJ
# true — позвать бота можно только в тот голосовой канал, где вы сами находитесь
# (и панель, и команды). false — разрешить выбирать канал вручную.
REQUIRE_LISTENER=true
# ----------------------------------------------------------------- Прочее ---
LOG_LEVEL=info
NODE_ENV=production