From 4663f260b55de0975661fda1c00f60d2ca6e4506 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 20 May 2026 15:40:18 +0200 Subject: Clean up consumers: replace polling with VoiceManager.play_source() - cron.py: replace while-is_playing polling with VoiceManager.play_source() - vclogs.py: replace while-is_playing polling with VoiceManager.play_source() - stop.py: use VoiceManager.disconnect() instead of raw voice_client - join.py: use VoiceManager.connect() instead of raw channel.connect() - voice.py: add back join_by_channel_id() convenience method - Tests: update for new VoiceManager method names and signatures --- tests/cogs/test_music_audio.py | 4 ++-- tests/cogs/test_music_join.py | 25 ++++++++++++++----------- tests/cogs/test_voice.py | 4 ++-- 3 files changed, 18 insertions(+), 15 deletions(-) (limited to 'tests') diff --git a/tests/cogs/test_music_audio.py b/tests/cogs/test_music_audio.py index 705f1b3..150ad5d 100644 --- a/tests/cogs/test_music_audio.py +++ b/tests/cogs/test_music_audio.py @@ -59,7 +59,7 @@ async def test_prepare_file_source_absolute_path(): async def test_prepare_source_url(): from cogs.music.audio_playback import AudioPlaybackManager - source, _, is_url = await AudioPlaybackManager.prepare_source(MagicMock(), "https://example.com/stream") + source, _, is_url = await AudioPlaybackManager.prepare_source("https://example.com/stream") assert is_url == True assert source == "https://example.com/stream" @@ -70,7 +70,7 @@ async def test_prepare_source_local_file(): from cogs.music.audio_playback import AudioPlaybackManager with patch('os.path.exists', return_value=True): - source, display_name, is_url = await AudioPlaybackManager.prepare_source(MagicMock(), "/tmp/test.mp3") + source, display_name, is_url = await AudioPlaybackManager.prepare_source("/tmp/test.mp3") assert is_url == False assert display_name == "test" diff --git a/tests/cogs/test_music_join.py b/tests/cogs/test_music_join.py index 0176759..b40d93a 100644 --- a/tests/cogs/test_music_join.py +++ b/tests/cogs/test_music_join.py @@ -24,16 +24,18 @@ async def test_join_command_success(join_cog, mock_channel, mock_guild, bot, moc ctx.author = mock_user mock_voice_channel = MagicMock() - mock_voice = MagicMock() - mock_voice_channel.connect = AsyncMock(return_value=mock_voice) - ctx.author.voice = MagicMock() ctx.author.voice.channel = mock_voice_channel - with patch.object(mock_voice_channel, 'connect', new_callable=AsyncMock, return_value=mock_voice): + with patch('cogs.music.join.get_voice_manager') as mock_get_vm: + mock_vm = MagicMock() + mock_vm.connect = AsyncMock() + mock_get_vm.return_value = mock_vm + with patch.object(ctx, 'respond', new_callable=AsyncMock): with patch.object(ctx.interaction, 'delete_original_response', new_callable=AsyncMock): await join_cog.join.callback(join_cog, ctx) + mock_vm.connect.assert_called_once_with(mock_voice_channel) @pytest.mark.asyncio @@ -42,12 +44,13 @@ async def test_stop_command_success(stop_cog, mock_channel, mock_guild, bot, moc ctx.user = mock_user ctx.author = mock_user - mock_voice_client = MagicMock() - mock_voice_client.disconnect = AsyncMock() - ctx.voice_client = mock_voice_client + with patch('cogs.music.stop.get_voice_manager') as mock_get_vm: + mock_vm = MagicMock() + mock_vm.disconnect = AsyncMock() + mock_get_vm.return_value = mock_vm - with patch.object(ctx, 'respond', new_callable=AsyncMock): - with patch.object(ctx.interaction, 'delete_original_response', new_callable=AsyncMock): - await stop_cog.stop.callback(stop_cog, ctx) + with patch.object(ctx, 'respond', new_callable=AsyncMock): + with patch.object(ctx.interaction, 'delete_original_response', new_callable=AsyncMock): + await stop_cog.stop.callback(stop_cog, ctx) - mock_voice_client.disconnect.assert_called_once() + mock_vm.disconnect.assert_called_once_with(mock_guild) diff --git a/tests/cogs/test_voice.py b/tests/cogs/test_voice.py index 104eea6..6eb226b 100644 --- a/tests/cogs/test_voice.py +++ b/tests/cogs/test_voice.py @@ -53,7 +53,7 @@ async def test_voice_manager_get_voice_status_not_connected(): mock_bot = MagicMock() mock_guild = MagicMock() - with patch('cogs.voice.VoiceManager.get_voice_client', return_value=None): + with patch('cogs.voice.VoiceManager.get_client', return_value=None): manager = VoiceManager(mock_bot) voice_client, is_connected, is_playing = await manager.get_voice_status(mock_guild) @@ -71,7 +71,7 @@ async def test_voice_manager_stop(): mock_voice_client = MagicMock() mock_voice_client.disconnect = AsyncMock() - with patch('cogs.voice.VoiceManager.get_voice_client', return_value=mock_voice_client): + with patch('cogs.voice.VoiceManager.get_client', return_value=mock_voice_client): manager = VoiceManager(mock_bot) await manager.stop(mock_guild) -- cgit v1.2.3 From bdf93cbc700f6baa55a283300d5bd3d2e958bc69 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 20 May 2026 16:40:28 +0200 Subject: Redirect play_commands imports to cogs.voice, remove audio_playback shim - Update play_commands.py to import get_voice_manager from cogs.voice instead of going through the audio_playback.py re-export shim - Delete the audio_playback.py shim file entirely - Update test_music_audio.py imports to use cogs.voice.VoiceManager - Delete PLAN_VOICE_REFACTOR.md (plan is complete) --- cogs/music/audio_playback.py | 10 ---------- cogs/music/play_commands.py | 11 ++++++----- tests/cogs/test_music_audio.py | 16 ++++++++-------- 3 files changed, 14 insertions(+), 23 deletions(-) delete mode 100644 cogs/music/audio_playback.py (limited to 'tests') diff --git a/cogs/music/audio_playback.py b/cogs/music/audio_playback.py deleted file mode 100644 index 5206a96..0000000 --- a/cogs/music/audio_playback.py +++ /dev/null @@ -1,10 +0,0 @@ -# This file is part of Senator Sperma -# Licensed under the GPL-3.0 License -# Copyright (C) 2026 Salmonik @ salmonik@tuta.io -# https://codeberg.org/salmonik/senator-sperma - -# Re-export shim — all voice utilities now live in cogs.voice.VoiceManager. -# Kept so existing imports in play_commands.py and ai.py keep working. -# TODO: update importers and remove this file. - -from cogs.voice import VoiceManager as AudioPlaybackManager # noqa: F401 diff --git a/cogs/music/play_commands.py b/cogs/music/play_commands.py index bdb021d..a90f901 100644 --- a/cogs/music/play_commands.py +++ b/cogs/music/play_commands.py @@ -16,10 +16,11 @@ import discord from discord.commands import OptionChoice from discord.ext import commands -from cogs.music import audio_playback, media_processing +from cogs.music import media_processing from cogs.utils.file_search import UniversalSearcher from cogs.utils.settings import get_uploadurl from cogs.utils.url_helper import clean_url_for_ytdlp +from cogs.voice import get_voice_manager logger = logging.getLogger(__name__) @@ -162,7 +163,7 @@ class PlayCommands(commands.Cog): if not compressed_path: return - await audio_playback.AudioPlaybackManager.play( + await get_voice_manager(self.bot).play( ctx, compressed_path, delete_after_play=True ) except Exception as e: @@ -209,7 +210,7 @@ class PlayCommands(commands.Cog): upload_task = asyncio.create_task(self._upload_to_copyparty_async(local_path)) - await audio_playback.AudioPlaybackManager.play( + await get_voice_manager(self.bot).play( ctx, local_path, delete_after_play=True ) @@ -274,7 +275,7 @@ class PlayCommands(commands.Cog): if compressed: await self._handle_compressed_file_play(ctx, url) else: - await audio_playback.AudioPlaybackManager.play(ctx, url) + await get_voice_manager(self.bot).play(ctx, url) @discord.slash_command(name="grajcuj", description="grajcujmy wszyscy!") async def play( @@ -309,7 +310,7 @@ class PlayCommands(commands.Cog): if file: file_path: PathLike[str] | str = os.path.join(self.temp_dir, file.filename) await file.save(file_path) - await audio_playback.AudioPlaybackManager.play(ctx, file_path) + await get_voice_manager(self.bot).play(ctx, file_path) else: try: if EXTERNAL_URL_PATTERN.match(melodia.lower()): diff --git a/tests/cogs/test_music_audio.py b/tests/cogs/test_music_audio.py index 150ad5d..98c6f6b 100644 --- a/tests/cogs/test_music_audio.py +++ b/tests/cogs/test_music_audio.py @@ -6,7 +6,7 @@ from conftest import MockApplicationContext @pytest.mark.asyncio async def test_validate_voice_channel_not_in_voice(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager mock_ctx = MagicMock() mock_ctx.author.voice = None @@ -20,7 +20,7 @@ async def test_validate_voice_channel_not_in_voice(): @pytest.mark.asyncio async def test_validate_voice_channel_in_voice(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager mock_channel = MagicMock() mock_ctx = MagicMock() @@ -35,7 +35,7 @@ async def test_validate_voice_channel_in_voice(): @pytest.mark.asyncio async def test_prepare_file_source_file_not_found(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager display_name, absolute_path = await AudioPlaybackManager.prepare_file_source("/nonexistent/file.mp3") @@ -45,7 +45,7 @@ async def test_prepare_file_source_file_not_found(): @pytest.mark.asyncio async def test_prepare_file_source_absolute_path(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager with patch('os.path.exists', return_value=True): with patch('os.path.isabs', return_value=True): @@ -57,7 +57,7 @@ async def test_prepare_file_source_absolute_path(): @pytest.mark.asyncio async def test_prepare_source_url(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager source, _, is_url = await AudioPlaybackManager.prepare_source("https://example.com/stream") @@ -67,7 +67,7 @@ async def test_prepare_source_url(): @pytest.mark.asyncio async def test_prepare_source_local_file(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager with patch('os.path.exists', return_value=True): source, display_name, is_url = await AudioPlaybackManager.prepare_source("/tmp/test.mp3") @@ -78,7 +78,7 @@ async def test_prepare_source_local_file(): @pytest.mark.asyncio async def test_cleanup_temp_file(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager with patch('os.path.exists', return_value=True): with patch('os.remove'): @@ -87,7 +87,7 @@ async def test_cleanup_temp_file(): @pytest.mark.asyncio async def test_cleanup_temp_file_not_exists(): - from cogs.music.audio_playback import AudioPlaybackManager + from cogs.voice import VoiceManager as AudioPlaybackManager with patch('os.path.exists', return_value=False): await AudioPlaybackManager.cleanup_temp_file("/nonexistent/file.mp3") -- cgit v1.2.3 From 95e43df1be6a0d7f4f33d729cfa65a6fb8cbc3a5 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 21 May 2026 16:15:21 +0200 Subject: reqs and test fix Signed-off-by: salmonik --- requirements-dev.txt | 2 +- requirements.txt | 2 +- tests/cogs/test_music_integration.py | 26 ++++++++++++++------------ 3 files changed, 16 insertions(+), 14 deletions(-) (limited to 'tests') diff --git a/requirements-dev.txt b/requirements-dev.txt index abba086..6a6279c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -36,7 +36,7 @@ numpy==2.4.4 packaging==26.1 pillow==12.2.0 propcache==0.4.1 -py-cord @ file:///home/salmon/Documents/botcode/senator-sperma/pycord +py-cord==2.8.0 pycparser==3.0 pycryptodomex==3.23.0 PyNaCl==1.6.2 diff --git a/requirements.txt b/requirements.txt index 81f00ef..d43181f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,7 +33,7 @@ numpy==2.4.4 packaging==26.1 pillow==12.2.0 propcache==0.4.1 -py-cord @ file:///home/salmon/Documents/botcode/senator-sperma/pycord +py-cord==2.8.0 pycparser==3.0 pycryptodomex==3.23.0 PyNaCl==1.6.2 diff --git a/tests/cogs/test_music_integration.py b/tests/cogs/test_music_integration.py index 1c0260a..692e1c1 100644 --- a/tests/cogs/test_music_integration.py +++ b/tests/cogs/test_music_integration.py @@ -27,18 +27,20 @@ async def test_music_join_stop(music_cog, bot): ctx.author.voice.channel = MagicMock() ctx.guild = MagicMock() - voice_client = AsyncMock() - ctx.voice_client = voice_client - # Test join - ctx.author.voice.channel.connect = AsyncMock(return_value=voice_client) - with patch.object(ctx, 'respond', new_callable=AsyncMock): - with patch.object(ctx.interaction, 'delete_original_response', new_callable=AsyncMock): - await join_cog.join.callback(join_cog, ctx) - ctx.author.voice.channel.connect.assert_called_once() + mock_vm = MagicMock() + mock_vm.connect = AsyncMock() + with patch('cogs.music.join.get_voice_manager', return_value=mock_vm): + with patch.object(ctx, 'respond', new_callable=AsyncMock): + with patch.object(ctx.interaction, 'delete_original_response', new_callable=AsyncMock): + await join_cog.join.callback(join_cog, ctx) + mock_vm.connect.assert_called_once_with(ctx.author.voice.channel) # Test stop - with patch.object(ctx, 'respond', new_callable=AsyncMock): - with patch.object(ctx.interaction, 'delete_original_response', new_callable=AsyncMock): - await stop_cog.stop.callback(stop_cog, ctx) - voice_client.disconnect.assert_called_once() + mock_vm_stop = MagicMock() + mock_vm_stop.disconnect = AsyncMock() + with patch('cogs.music.stop.get_voice_manager', return_value=mock_vm_stop): + with patch.object(ctx, 'respond', new_callable=AsyncMock): + with patch.object(ctx.interaction, 'delete_original_response', new_callable=AsyncMock): + await stop_cog.stop.callback(stop_cog, ctx) + mock_vm_stop.disconnect.assert_called_once_with(ctx.guild) -- cgit v1.2.3