Chat commands:
- !skip: per-user 60 s cooldown (was global 5 s). Each chatter gets one
skip per minute; rejection includes a wait hint so users know
it's their own gate.
- !queue: peek next 3 tracks from the MPD queue. Read-only, no cooldown.
- !info: full metadata for now-playing — artist · title · album (year)
· genre, with missing tags skipped to avoid empty parens.
Mattermost going-live notifier (optional, MM_HOOK env):
- Polls https://ophi118.com/api/status.json (the public service in
station/server/) every 60 s and POSTs a single message to the
configured Mattermost webhook on each offline→live transition.
- Transition detection keys on Twitch's started_at, not just the live
flag, so a Helix flap inside one stream doesn't double-fire. Cold
start mid-stream is intentionally silent — records the current
started_at, awaits next genuine transition. Override the polled
endpoint with STATUS_URL for private status services.
.env.example documents the new MM_HOOK / STATUS_URL knobs.
Self-hosted async Python bot, runs as systemd --user unit
(obs-twitch-bot.service, sibling to obs-mpd-bridge). Connects to Twitch IRC
over TLS as vault118, listens in #ophi118, advances MPD on !skip and posts
the new track back to chat. 5s cooldown swallows skip-spam silently.
Strips Unicode Tags-block codepoints (U+E0000–U+E007F) before matching the
command so Twitch's anti-duplicate suffix doesn't break every-other !skip
when the broadcaster fires the same command twice in a row.
Bot account is separate (vault118) to keep the broadcaster's display name
out of bot replies; .env holds access token + refresh token + client id
(refresh + client id stored for future token rotation, unused today).