Commit Graph

218 Commits

Author SHA1 Message Date
user
ad12843e75 docs: add music discovery, autoplay, and Mumble admin sections
Some checks failed
CI / gitleaks (push) Failing after 3s
CI / lint (push) Failing after 23s
CI / test (3.11) (push) Has been skipped
CI / test (3.12) (push) Has been skipped
CI / test (3.13) (push) Has been skipped
CI / build (push) Has been skipped
- USAGE.md: music discovery (!similar, !tags), autoplay discovery
  config, Mumble admin (!mu) command reference
- CHEATSHEET.md: music discovery and Mumble admin quick reference
- ROADMAP.md: mark v2.4.0 as done, add MB fallback + !mu + autoplay
- TODO.md: mark music discovery and performance items as done
- PROJECT.md: update plugin categories table
- TASKS.md: close open doc items

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:04:03 +01:00
user
62a4191200 docs: update TASKS.md with MusicBrainz fallback sprint
Some checks failed
CI / gitleaks (push) Failing after 3s
CI / lint (push) Failing after 25s
CI / test (3.11) (push) Has been skipped
CI / test (3.12) (push) Has been skipped
CI / test (3.13) (push) Has been skipped
CI / build (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:00:02 +01:00
user
135a3791e2 feat: add MusicBrainz fallback to !similar and !tags commands
Some checks failed
CI / gitleaks (push) Failing after 2s
CI / lint (push) Failing after 23s
CI / test (3.11) (push) Has been skipped
CI / test (3.12) (push) Has been skipped
CI / test (3.13) (push) Has been skipped
CI / build (push) Has been skipped
Remove early return on missing Last.fm API key. Both commands now
fall back to MusicBrainz (mb_search_artist -> mb_artist_tags ->
mb_find_similar_recordings) when no API key is configured or when
Last.fm returns empty results. Same pattern used by discover_similar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:56:39 +01:00
user
a87f75adf1 feat: add Mumble server admin plugin (!mu)
Admin-only command with subcommand dispatch for server management:
kick, ban, mute/unmute, deafen/undeafen, move, users, channels,
mkchan, rmchan, rename, desc. Auto-loads on merlin via except_plugins.
2026-02-23 21:44:38 +01:00
user
da9ed51c74 feat: auto-discover similar tracks during autoplay via Last.fm/MusicBrainz
Every Nth autoplay pick (configurable via discover_ratio), query Last.fm
for similar tracks. When Last.fm has no key or returns nothing, fall back
to MusicBrainz tag-based recording search (no API key needed). Discovered
tracks are resolved via yt-dlp and deduplicated within the session. If
discovery fails, the kept-deck shuffle continues as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:19:41 +01:00
user
56f6b9822f fix: revert pymumble protocol version patch that broke audio
Reporting version 1.5.0 to mumble-server caused it to expect the newer
audio frame format (terminator bit on opus length varint). pymumble
1.6.1 does not implement this, so the server silently dropped every
UDPTunnel audio packet. Revert to native 1.2.4 -- audio works, only
side effect is a cosmetic ChannelListener warning.

Also remove --cprofile from docker-compose command.
2026-02-23 18:51:53 +01:00
user
09880624d5 fix: add bot name to stream_audio log lines
Multi-bot setups need to know which bot is streaming. Prefixes all
stream_audio log messages with [username] for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:15:46 +01:00
user
3c475107e3 refactor: simplify audition to single-bot playback
derp now handles both listening and speaking, so audition no longer
needs cross-bot lookup or dual-play through merlin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:07:08 +01:00
user
b3006b02e2 feat: auto-register mumble bots on first connect
Self-register with the Mumble server on initial connection so both
derp and merlin get persistent identities (cert-bound user_id).
Skips registration on reconnect or if already registered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 22:32:36 +01:00
user
8b504364a9 fix: patch pymumble protocol version and harden mumble connections
- Update reported protocol version from 1.2.4 to 1.5.0 so modern
  Murmur servers treat PyMumble as a compatible client
- Fix OS string to report actual platform instead of "PyMumble 1.6.1"
  (was shown as [Invalid] by Murmur)
- Raise pymumble reconnect retry interval to 15s to prevent autoban
  when running multiple bots from the same IP
- Enable TCP keepalive on control socket (10s idle) to prevent NAT
  gateways from dropping long-lived connections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 22:28:57 +01:00
user
40c6bf8c53 feat: playlist import, show, and shuffle-on-load
Add !playlist import <name> <url> to resolve and save tracks from a URL
without queueing them. Add !playlist show <name> to display tracks in a
saved playlist via long_reply (auto-pastes on overflow). Add optional
'shuffle' keyword to !playlist load for randomized playback order.
2026-02-22 20:31:54 +01:00
user
a76d46b1de fix: graceful SIGTERM shutdown for IRC and Mumble bots
Send IRC QUIT before closing the socket so the server releases the nick
immediately. Wrap readline() in wait_for(timeout=2.0) so the _running
check triggers even when the server is quiet. Explicitly stop pymumble
in the signal handler to accelerate Mumble teardown. Together these
eliminate the 10s podman grace-period SIGKILL.
2026-02-22 20:31:49 +01:00
user
0ffddb8e41 fix: write cProfile to data/ volume for host access
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 20:02:14 +01:00
user
62b01c76f7 fix: reduce cProfile dump interval to 10s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 20:00:06 +01:00
user
e0db0ad567 fix: periodic cProfile dump every 60s (survives hard kills)
Replace cProfile.runctx with Profile() + daemon thread that dumps
stats to disk every 60 seconds. Final dump on clean exit. Profile
data no longer lost on container stop, OOM, or SIGKILL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:56:21 +01:00
user
c41035ceca test: add test_lastfm.py (50 cases)
Covers API helpers (_get_similar_artists, _get_top_tags,
_get_similar_tracks, _search_track), config resolution, metadata
extraction from track titles, match score formatting, and both
commands (!similar, !tags) including play mode delegation, fallback
from track to artist similarity, and current-track integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:52:16 +01:00
user
cd4124e07a fix: route alert YouTube/SearXNG through pooled urlopen
- YouTube InnerTube search: urllib.request.urlopen -> _urlopen (gets
  connection pooling + SOCKS5 proxy)
- SearXNG search: urllib.request.urlopen -> _urlopen(proxy=False)
  (local service, skip proxy, get pooling)
- Update 5 tests to patch _urlopen instead of urllib.request.urlopen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:39:38 +01:00
user
717bf59a05 feat: playlist save/load, queue durations, whisper bias, greet fix
- Move TTS greeting from mumble._play_greet to voice.on_connected
  (fires once on first connect, gated on _is_audio_ready)
- Add initial_prompt multipart field to Whisper STT for trigger word
  bias (auto-generated from trigger config, overridable)
- Enhanced !queue: elapsed/total on now-playing, per-track durations,
  footer with track count and total time
- New !playlist command: save/load/list/del named playlists via
  bot.state persistence (playlist:<name> keys)
- Fix duck floor test (1% -> 2% to match default change)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:23:03 +01:00
user
5d0e200fbe docs: update TASKS.md for voice/duck sprint
Some checks failed
CI / gitleaks (push) Failing after 3s
CI / lint (push) Successful in 23s
CI / test (3.11) (push) Failing after 2m40s
CI / test (3.13) (push) Failing after 2m43s
CI / test (3.12) (push) Failing after 2m45s
CI / build (push) Has been skipped
2026-02-22 18:55:00 +01:00
user
8d54322ce1 fix: raise duck floor default from 1% to 2%
Some checks failed
CI / gitleaks (push) Failing after 3s
CI / lint (push) Successful in 23s
CI / test (3.11) (push) Has started running
CI / test (3.12) (push) Has started running
CI / build (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
Keep music audible during voice ducking instead of near-silent.
2026-02-22 18:53:42 +01:00
user
e920ec5f10 fix: duck on audio packets only, remove unmute-based ducking
Instant ducking is purely packet-based now -- _instant_duck() fires
on _on_sound_received, not on user state changes. Removes the
USERUPDATED callback that preemptively ducked on unmute.
2026-02-22 18:51:38 +01:00
user
c522d30c36 feat: ack tone, duck-before-TTS, instant ducking on voice/unmute
- Add ascending two-tone chime (880Hz/1320Hz) before TTS playback
  as audible acknowledgment that the voice trigger was recognized
- Signal music ducking 1.5s before TTS starts so music is already
  lowered when audio begins playing
- Snap duck volume to floor instantly on voice packet or user unmute
  via pymumble callback, eliminating the 1s poll delay
- Register USERUPDATED callback to preemptively duck when a user
  unmutes (they're about to speak)
- Strip leading punctuation from trigger remainder (Whisper artifacts)
2026-02-22 18:46:33 +01:00
user
068734d931 fix: kept file protection, skip/autoplay, TTS routing, video ID expansion
- _cleanup_track: never delete files from kept directory (data/music/)
  even when track.keep=False -- fixes kept files vanishing on replay
- !kept rm: skip to next track if removing the currently playing one
- !skip: silent (no reply), restarts play loop for autoplay on empty queue
- TTS plays through merlin's own connection instead of derp's, preventing
  choppy audio when music and TTS compete for the same output buffer
- !play recognizes bare YouTube video IDs (11-char alphanumeric)
- !kept rm <id> subcommand for removing individual kept tracks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:00:23 +01:00
user
36da191b45 fix: download track on !keep when local file is missing
When the initial download failed during playback and the track streamed
directly from URL, !keep would refuse with "No local file". Now it
downloads the track on the spot before keeping it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:01:44 +01:00
user
6083de13f9 feat: playlist shuffle, lazy resolution, TTS ducking, kept repair
Some checks failed
CI / gitleaks (push) Failing after 3s
CI / lint (push) Successful in 22s
CI / test (3.11) (push) Failing after 2m47s
CI / test (3.13) (push) Failing after 2m52s
CI / test (3.12) (push) Failing after 2m54s
CI / build (push) Has been skipped
Music:
- #random URL fragment shuffles playlist tracks before enqueuing
- Lazy playlist resolution: first 10 tracks resolve immediately,
  remaining are fetched in a background task
- !kept repair re-downloads kept tracks with missing local files
- !kept shows [MISSING] marker for tracks without local files
- TTS ducking: music ducks when merlin speaks via voice peer,
  smooth restore after TTS finishes

Performance (from profiling):
- Connection pool: preload_content=True for SOCKS connection reuse
- Pool tuning: 30 pools / 8 connections (up from 20/4)
- _PooledResponse wrapper for stdlib-compatible read interface
- Iterative _extract_videos (replace 51K-deep recursion with stack)
- proxy=False for local SearXNG

Voice + multi-bot:
- Per-bot voice config lookup ([<username>.voice] in TOML)
- Mute detection: skip duck silence when all users muted
- Autoplay shuffle deck (no repeats until full cycle)
- Seek clamp to track duration (prevent seek-past-end stall)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:21:47 +01:00
user
6d6b957557 docs: update roadmap, tasks, and backlog for v2.3.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:10:28 +01:00
user
f72f55148b fix: ignore bot audio in sound callback, self-mute support
- _on_sound_received skips audio from our own bots entirely,
  preventing self-ducking and STT transcribing bot TTS/music
- New self_mute config: mute on connect, unmute before stream_audio,
  re-mute 3s after audio finishes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:09:30 +01:00
user
e9d17e8b00 feat: voice profiles, rubberband FX, per-bot plugin filtering
- Add rubberband package to container for pitch-shifting FX
- Split FX chain: rubberband CLI for pitch, ffmpeg for filters
- Configurable voice profile (voice, fx, piper params) in [voice]
- Extra bots inherit voice config (minus trigger) for own TTS
- Greeting is voice-only, spoken directly by the greeting bot
- Per-bot only_plugins/except_plugins filtering on Mumble
- Alias plugin, core plugin tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:41:00 +01:00
user
3afeace6e7 feat: container management tools in tools/
Shell scripts for build, start, stop, restart, nuke, logs, status.
Shared helpers in _common.sh (colours, compose detection, project root).
Updated CHEATSHEET.md with new tool references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:40:53 +01:00
user
b88a459142 feat: music library management, smooth fades, clickable URLs
- Audio-only downloads (-x), resume (-c), skip existing (--no-overwrites)
- Title-based filenames (e.g. never-gonna-give-you-up.opus)
- Separate cache (data/music/cache/) from kept tracks (data/music/)
- Kept track IDs: !keep assigns #id, !play #id, !kept shows IDs
- Linear fade-in (5s) and fade-out (3s) with volume-proportional step
- Fix ramp click: threshold-based convergence instead of float equality
- Clean up cache files for skipped/stopped tracks
- Auto-linkify URLs in Mumble text chat (clickable <a> tags)
- FlaskPaste links use /raw endpoint for direct content access
- Metadata fetch uses --no-playlist for reliable results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 08:11:29 +01:00
user
ad1de1653e fix: gitleaks clone depth and opuslib discovery on musl
Some checks failed
CI / gitleaks (push) Failing after 3s
CI / lint (push) Successful in 27s
CI / test (3.11) (push) Successful in 2m13s
CI / test (3.13) (push) Successful in 2m18s
CI / test (3.12) (push) Successful in 2m20s
CI / build (push) Has been skipped
Remove invalid --depth=0 from gitleaks checkout (needs full history).
Add opus symlink and pymumble/opuslib patch step to test jobs so
ctypes.find_library works on Alpine/musl.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:11:50 +01:00
user
8f1df167b9 feat: fade-out on skip/stop/prev, song metadata on keep
Some checks failed
CI / gitleaks (push) Failing after 4s
CI / lint (push) Successful in 24s
CI / test (3.11) (push) Failing after 30s
CI / test (3.13) (push) Failing after 34s
CI / test (3.12) (push) Failing after 36s
CI / build (push) Has been skipped
- Add fade_step parameter to stream_audio for fast volume ramps
- _fade_and_cancel helper: smooth ~0.8s fade before track switch
- !skip, !stop, !seek now fade out instead of cutting instantly
- !prev command: go back to previous track (10-track history stack)
- !keep fetches title/artist/duration via yt-dlp, stores in bot.state
- !kept displays metadata (title, artist, duration, file size)
- !kept clear also removes stored metadata
- 29 new tests for fade, prev, history, keep metadata

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:38:25 +01:00
user
de2d1fdf15 fix: replace actions/checkout with git clone in container jobs
Some checks failed
CI / gitleaks (push) Failing after 4s
CI / lint (push) Successful in 25s
CI / test (3.11) (push) Failing after 32s
CI / test (3.13) (push) Failing after 35s
CI / test (3.12) (push) Failing after 39s
CI / build (push) Has been skipped
actions/checkout@v4 requires node, which isn't available in
alpine or gitleaks images. Use plain git clone instead for
containerized jobs; keep actions/checkout for the host build job.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:23:49 +01:00
user
82f5984631 ci: use 'linux' runner label
Some checks failed
CI / gitleaks (push) Failing after 5s
CI / lint (push) Failing after 9s
CI / test (3.11) (push) Has been skipped
CI / test (3.12) (push) Has been skipped
CI / test (3.13) (push) Has been skipped
CI / build (push) Has been skipped
2026-02-22 06:20:48 +01:00
user
1744e7087f ci: re-trigger pipeline
Some checks failed
CI / gitleaks (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
CI / build (push) Has been cancelled
2026-02-22 06:18:27 +01:00
user
0c0adef90d feat: run CI jobs in podman containers, add requirements-dev.txt
Some checks failed
CI / gitleaks (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
CI / build (push) Has been cancelled
- Use container: directive for gitleaks, lint, and test jobs
- Build job stays on host (needs podman for image build/push)
- Add requirements-dev.txt for unified dev/test dependency install

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:15:42 +01:00
user
3dada3fc06 fix: allowlist tests/ in gitleaks, add libopus for CI test job
Some checks failed
CI / gitleaks (push) Successful in 12s
CI / lint (push) Successful in 18s
CI / test (3.11) (push) Successful in 2m5s
CI / test (3.12) (push) Successful in 2m5s
CI / test (3.13) (push) Successful in 2m3s
CI / build (push) Failing after 12s
- gitleaks flagged fake api_key in test fixtures as a secret leak;
  allowlist tests/ directory since it contains only mock data
- Install libopus0 in test runner for pymumble/opuslib import chain

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:09:28 +01:00
user
6e40daa8a9 fix: resolve CI gitleaks download and missing pymumble dep
Some checks failed
CI / gitleaks (push) Failing after 12s
CI / lint (push) Successful in 17s
CI / test (3.11) (push) Failing after 25s
CI / test (3.12) (push) Failing after 28s
CI / test (3.13) (push) Failing after 27s
CI / build (push) Has been skipped
- Dynamically resolve latest gitleaks version from GitHub releases
  instead of hardcoded tarball URL that 404'd
- Add pymumble to test job install (needed by derp.mumble import
  chain, not in pyproject.toml base deps)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:06:47 +01:00
user
ba1af461de fix: use gitleaks CLI instead of licensed action, fix lint errors
Some checks failed
CI / gitleaks (push) Failing after 10s
CI / lint (push) Successful in 18s
CI / test (3.11) (push) Failing after 19s
CI / test (3.12) (push) Failing after 20s
CI / test (3.13) (push) Failing after 20s
CI / build (push) Has been skipped
- Replace gitleaks-action (requires paid license) with direct CLI
  invocation -- same engine, no license needed
- Fix ruff I001 import sorting in voice.py and test_llm.py
- Remove unused imports: _chat_request (test_llm), Path (test_music)
- Remove unused assignment: original_spawn (test_voice)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:58:10 +01:00
user
004656a64f feat: add Harbor image build+push to CI pipeline
Some checks failed
CI / gitleaks (push) Failing after 10s
CI / lint (push) Failing after 17s
CI / test (3.11) (push) Has been skipped
CI / test (3.12) (push) Has been skipped
CI / test (3.13) (push) Has been skipped
CI / build (push) Has been skipped
Build and push to harbor.mymx.me/library/derp after gitleaks
and test jobs pass. Only runs on push to master. Tags with
short SHA and latest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:55:34 +01:00
user
192ea717a7 feat: split CI into gitleaks, lint, and test jobs
Some checks failed
CI / gitleaks (push) Failing after 15s
CI / lint (push) Failing after 17s
CI / test (3.11) (push) Has been skipped
CI / test (3.12) (push) Has been skipped
CI / test (3.13) (push) Has been skipped
- Add gitleaks secret scanning (full history)
- Separate lint (ruff, Python 3.13 only) from test matrix
- Test job gates on lint; gitleaks runs in parallel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:51:53 +01:00
user
7a4aa65882 fix: align cmd_stop else branch with _play_loop finally cleanup
The else branch (no active task) only cleared current, task, and
duck_vol. Now resets all play state fields to match _play_loop's
finally block: done_event, duck_task, progress, and cur_seek.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:49:53 +01:00
user
2cd1d5efb1 fix: race condition in skip/seek/stop losing track state
task.cancel() triggers _play_loop's finally block asynchronously.
When cmd_skip or cmd_seek called _ensure_loop before the finally
block ran, the old task's cleanup would overwrite the new task's
state -- causing !np to report "Nothing playing" while audio
was still streaming.

Now await the cancelled task before restarting the loop, ensuring
the finally block completes first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:45:00 +01:00
user
95981275b5 feat: add OpenRouter LLM chat plugin (!ask, !chat)
Single-shot (!ask) and conversational (!chat) LLM commands backed by
OpenRouter's API. Per-user history (20 msg cap), 5s cooldown, reasoning
model fallback, and model switching via subcommands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:39:11 +01:00
user
66116d2caf docs: update Piper TTS endpoint and document available voices
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:01:57 +01:00
user
eded764f6a fix: update Piper TTS endpoint and request format
Piper is on 192.168.129.9:5100, not :5000. It expects POST with
JSON body {"text": "..."}, not GET with query params. Also update
Whisper default to 192.168.129.9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 04:56:24 +01:00
user
9783365b1e feat: add extra Mumble bot instances and TTS greeting
Support [[mumble.extra]] config for additional Mumble identities that
inherit connection settings from the main [mumble] section. Extra bots
get their own state DB and do not run the voice trigger by default.

Add TTS greeting on first connect via mumble.greet config option.
Merlin joins as a second identity with his own client certificate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 04:34:10 +01:00
user
165938a801 fix: mumble disconnect loop from stale socket and dead parent thread
Two issues causing ~2min reconnect cycles:

1. pymumble captures threading.current_thread() as parent_thread at
   init. Since we construct it in a run_in_executor thread, the parent
   dies after _connect_sync returns, triggering pymumble's loop exit.
   Fix: point parent_thread at threading.main_thread().

2. pymumble's init_connection() drops the control_socket reference
   without closing it. The lingering TCP connection makes Murmur kick
   the new session with "connected from another device". Fix: patch
   init_connection to close the old socket before reconnecting.
2026-02-22 04:24:23 +01:00
user
221cb1f06b fix: voice trigger not receiving audio from pymumble
pymumble passes a User object, not a dict. The isinstance(user, dict)
check returned False, setting name to None and silently discarding
every voice packet. Use try/except for dict-like access instead.
2026-02-22 04:02:23 +01:00
user
c4908f2a63 docs: document seek command and volume persistence 2026-02-22 03:31:39 +01:00