Cookies were already wired up but only mentioned in passing, and the
non-obvious parts were undocumented: yt-dlp has no username/password
support for YouTube, it rewrites the cookie file to persist rotated
cookies (so a read-only file expires early), and the export has to
happen in a private window that is logged out before closing.
Startup now reports whether the cookie file is usable, missing, or
read-only, and YTDLP_EXTRACTOR_ARGS is passed through for the cases
where YouTube blocks a server IP outright.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cloning straight into /opt fails with "Permission denied", and a clone
left owned by root would also make the ./data bind mount unwritable for
the container's node user.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The instance's internal Caddy is only for Stoat itself, so the panel no
longer goes through it: the bot ships its own compose project, publishes
3005 on loopback, and the host's external Caddy gives it a domain.
extra_hosts pins the instance domain to host-gateway, so the bot reaches
the API, gateway and LiveKit through the external proxy with a valid
certificate instead of depending on router NAT loopback. The old
in-project layout stays available as a fallback example, together with a
step-by-step guide for when voice fails to connect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>