From 36da191b45636b3ac7ae74d8f391af46caf5c1d0 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 22 Feb 2026 17:01:44 +0100 Subject: [PATCH] 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 --- plugins/music.py | 16 ++++++++++++++-- tests/test_music.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/plugins/music.py b/plugins/music.py index aca3649..69f9c02 100644 --- a/plugins/music.py +++ b/plugins/music.py @@ -1405,8 +1405,20 @@ async def cmd_keep(bot, message): await bot.reply(message, "Nothing playing") return if track.local_path is None: - await bot.reply(message, "No local file for current track") - return + if not track.url: + await bot.reply(message, "No local file for current track") + return + # Download on the spot -- track was streaming without a local file + loop = asyncio.get_running_loop() + tid = hashlib.md5(track.url.encode()).hexdigest()[:12] + dl_path = await loop.run_in_executor( + None, _download_track, track.url, tid, track.title, + ) + if dl_path: + track.local_path = dl_path + else: + await bot.reply(message, "Download failed, cannot keep track") + return track.keep = True # Check if this track is already kept (by normalized URL) diff --git a/tests/test_music.py b/tests/test_music.py index 48ac34b..e8da24e 100644 --- a/tests/test_music.py +++ b/tests/test_music.py @@ -1520,14 +1520,44 @@ class TestKeepCommand: asyncio.run(_mod.cmd_keep(bot, msg)) assert any("Nothing playing" in r for r in bot.replied) - def test_keep_no_local_file(self): + def test_keep_no_local_file_no_url(self): bot = _FakeBot() ps = _mod._ps(bot) - ps["current"] = _mod._Track(url="x", title="t", requester="a") + ps["current"] = _mod._Track(url="", title="t", requester="a") msg = _Msg(text="!keep") asyncio.run(_mod.cmd_keep(bot, msg)) assert any("No local file" in r for r in bot.replied) + def test_keep_downloads_when_no_local_file(self, tmp_path): + bot = _FakeBot() + ps = _mod._ps(bot) + track = _mod._Track(url="https://example.com/v", title="t", + requester="a") + ps["current"] = track + dl_file = tmp_path / "abc.opus" + dl_file.write_bytes(b"audio") + music_dir = tmp_path / "kept" + music_dir.mkdir() + meta = {"title": "t", "artist": "", "duration": 0} + msg = _Msg(text="!keep") + with patch.object(_mod, "_download_track", return_value=dl_file), \ + patch.object(_mod, "_MUSIC_DIR", music_dir), \ + patch.object(_mod, "_fetch_metadata", return_value=meta): + asyncio.run(_mod.cmd_keep(bot, msg)) + assert track.keep is True + assert track.local_path is not None + assert any("Keeping" in r for r in bot.replied) + + def test_keep_download_failure(self): + bot = _FakeBot() + ps = _mod._ps(bot) + ps["current"] = _mod._Track(url="https://example.com/v", title="t", + requester="a") + msg = _Msg(text="!keep") + with patch.object(_mod, "_download_track", return_value=None): + asyncio.run(_mod.cmd_keep(bot, msg)) + assert any("Download failed" in r for r in bot.replied) + def test_keep_marks_track(self, tmp_path): bot = _FakeBot() ps = _mod._ps(bot)