From f899241d730447aa25e08c836513ca067ff7aab3 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 22 Feb 2026 00:18:43 +0100 Subject: [PATCH] feat: support relative volume adjustment (+N/-N) !volume +10 increases by 10, !volume -5 decreases by 5. Out-of-range results (below 0 or above 100) are rejected. Co-Authored-By: Claude Opus 4.6 --- plugins/music.py | 13 ++++++++++--- tests/test_music.py | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/plugins/music.py b/plugins/music.py index b39a894..1b555de 100644 --- a/plugins/music.py +++ b/plugins/music.py @@ -405,13 +405,14 @@ async def cmd_testtone(bot, message): await bot.reply(message, "Test tone complete") -@command("volume", help="Music: !volume [0-100]") +@command("volume", help="Music: !volume [0-100|+N|-N]") async def cmd_volume(bot, message): """Get or set playback volume. Usage: !volume Show current volume !volume <0-100> Set volume (takes effect immediately) + !volume +N/-N Adjust volume relatively """ if not _is_mumble(bot): return @@ -422,12 +423,18 @@ async def cmd_volume(bot, message): await bot.reply(message, f"Volume: {ps['volume']}%") return + arg = parts[1].strip() + relative = arg.startswith("+") or (arg.startswith("-") and arg != "-") + try: - val = int(parts[1]) + val = int(arg) except ValueError: - await bot.reply(message, "Usage: !volume <0-100>") + await bot.reply(message, "Usage: !volume <0-100|+N|-N>") return + if relative: + val = ps["volume"] + val + if val < 0 or val > 100: await bot.reply(message, "Volume must be 0-100") return diff --git a/tests/test_music.py b/tests/test_music.py index 3eaaf0d..c5e6f9f 100644 --- a/tests/test_music.py +++ b/tests/test_music.py @@ -319,8 +319,41 @@ class TestVolumeCommand: asyncio.run(_mod.cmd_volume(bot, msg)) assert any("0-100" in r for r in bot.replied) - def test_volume_negative(self): + def test_volume_negative_absolute(self): + """Bare negative that underflows clamps at 0-100 error.""" bot = _FakeBot() + _mod._ps(bot)["volume"] = 5 + msg = _Msg(text="!volume -10") + asyncio.run(_mod.cmd_volume(bot, msg)) + assert any("0-100" in r for r in bot.replied) + + def test_volume_relative_up(self): + bot = _FakeBot() + msg = _Msg(text="!volume +15") + asyncio.run(_mod.cmd_volume(bot, msg)) + ps = _mod._ps(bot) + assert ps["volume"] == 65 + assert any("65%" in r for r in bot.replied) + + def test_volume_relative_down(self): + bot = _FakeBot() + _mod._ps(bot)["volume"] = 80 + msg = _Msg(text="!volume -20") + asyncio.run(_mod.cmd_volume(bot, msg)) + ps = _mod._ps(bot) + assert ps["volume"] == 60 + assert any("60%" in r for r in bot.replied) + + def test_volume_relative_clamp_over(self): + bot = _FakeBot() + _mod._ps(bot)["volume"] = 95 + msg = _Msg(text="!volume +10") + asyncio.run(_mod.cmd_volume(bot, msg)) + assert any("0-100" in r for r in bot.replied) + + def test_volume_relative_clamp_under(self): + bot = _FakeBot() + _mod._ps(bot)["volume"] = 5 msg = _Msg(text="!volume -10") asyncio.run(_mod.cmd_volume(bot, msg)) assert any("0-100" in r for r in bot.replied)