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)