diff --git a/extendedaudio/extendedaudio.py b/extendedaudio/extendedaudio.py index 40cd1bc..b73eba3 100644 --- a/extendedaudio/extendedaudio.py +++ b/extendedaudio/extendedaudio.py @@ -5,10 +5,17 @@ from typing import Optional import discord from redbot.core import Config, commands from redbot.core.bot import Red -from redbot.core.utils.chat_formatting import box +from redbot.core.utils.chat_formatting import box, humanize_list +from redbot.core.utils.predicates import MessagePredicate log = logging.getLogger("red.valerie.extendedaudio") +SUPPORTED_VARIABLES = { + "title": "Song title", + "requester": "User who requested the song", + "duration": "Duration of the song" +} + class ExtendedAudio(commands.Cog): """Extends Red's core Audio cog with additional features""" @@ -18,6 +25,7 @@ class ExtendedAudio(commands.Cog): default_guild = { "channel_status": True, # Whether to update voice channel name with current song "status_format": "🎵 {title}", # Format for the channel name + "original_name": None, # Store the original channel name } self.config.register_guild(**default_guild) self.task = self.bot.loop.create_task(self.initialize()) @@ -37,13 +45,47 @@ class ExtendedAudio(commands.Cog): """Cleanup when cog unloads""" if self.task: self.task.cancel() + # Reset all modified channel names + asyncio.create_task(self._cleanup_on_unload()) + + async def _cleanup_on_unload(self): + """Reset all modified channel names when cog unloads""" + for guild in self.bot.guilds: + try: + original_name = await self.config.guild(guild).original_name() + if original_name and guild.voice_client and guild.voice_client.channel: + await guild.voice_client.channel.edit(name=original_name) + await self.config.guild(guild).original_name.set(None) + except: + continue + + async def cog_check(self, ctx: commands.Context): + """Check if Audio cog is loaded""" + if not self.audio: + self.audio = self.bot.get_cog("Audio") + if not self.audio: + await ctx.send("The Audio cog is not loaded. Please load it first with `[p]load audio`") + return False + return True @commands.group(aliases=["eaudioset"]) @commands.guild_only() @commands.admin_or_permissions(manage_guild=True) async def extendedaudioset(self, ctx: commands.Context): """Configure ExtendedAudio settings""" - pass + if not ctx.invoked_subcommand: + guild = ctx.guild + conf = await self.config.guild(guild).all() + enabled = conf["channel_status"] + format_str = conf["status_format"] + msg = ( + f"**Current Settings**\n" + f"Channel Status: {'Enabled' if enabled else 'Disabled'}\n" + f"Status Format: {format_str}\n\n" + f"Available Variables: {humanize_list([f'{{' + k + '}}' for k in SUPPORTED_VARIABLES.keys()])}\n" + f"Use `{ctx.prefix}extendedaudioset statusformat` to change the format." + ) + await ctx.send(box(msg)) @extendedaudioset.command(name="channelstatus") async def set_channel_status(self, ctx: commands.Context, enabled: bool): @@ -53,21 +95,73 @@ class ExtendedAudio(commands.Cog): Example: [p]extendedaudioset channelstatus true """ - await self.config.guild(ctx.guild).channel_status.set(enabled) + guild = ctx.guild + + # Check if bot has required permissions + if not guild.me.guild_permissions.manage_channels: + await ctx.send("I need the `Manage Channels` permission to update channel names.") + return + + # Store original channel name if enabling and not stored + if enabled: + voice_client = guild.voice_client + if voice_client and voice_client.channel: + current_name = voice_client.channel.name + if not await self.config.guild(guild).original_name(): + await self.config.guild(guild).original_name.set(current_name) + + await self.config.guild(guild).channel_status.set(enabled) state = "enabled" if enabled else "disabled" await ctx.send(f"Voice channel status updates {state}.") + # Reset channel name if disabling + if not enabled: + try: + voice_client = guild.voice_client + if voice_client and voice_client.channel: + original_name = await self.config.guild(guild).original_name() + if original_name: + await voice_client.channel.edit(name=original_name) + await self.config.guild(guild).original_name.set(None) + except discord.Forbidden: + await ctx.send("I don't have permission to edit the channel name.") + except Exception as e: + log.error(f"Error resetting channel name: {e}") + @extendedaudioset.command(name="statusformat") async def set_status_format(self, ctx: commands.Context, *, format_str: str): """Set the format for voice channel status updates Available variables: {title} - Song title + {requester} - User who requested the song + {duration} - Duration of the song Example: [p]extendedaudioset statusformat 🎵 {title} """ + # Validate format string + try: + # Test with sample data + test_data = { + "title": "Test Song", + "requester": "Test User", + "duration": "3:45" + } + format_str.format(**test_data) + except KeyError as e: + invalid_var = str(e).strip("'") + supported = humanize_list([f"{{" + k + "}}" for k in SUPPORTED_VARIABLES.keys()]) + await ctx.send( + f"Invalid variable `{invalid_var}` in format string.\n" + f"Supported variables are: {supported}" + ) + return + except Exception as e: + await ctx.send(f"Invalid format string: {e}") + return + await self.config.guild(ctx.guild).status_format.set(format_str) - example = format_str.format(title="Never Gonna Give You Up") + example = format_str.format(title="Never Gonna Give You Up", requester="Rick Astley", duration="3:32") await ctx.send( f"Status format set! Example:\n" f"{box(example)}" @@ -91,12 +185,31 @@ class ExtendedAudio(commands.Cog): channel = voice_client.channel + # Store original name if not stored + if not await self.config.guild(guild).original_name(): + await self.config.guild(guild).original_name.set(channel.name) + # Get the format and create the new name format_str = await self.config.guild(guild).status_format() - new_name = format_str.format(title=track.get("title", "Unknown")) - # Update channel name if different - if channel.name != new_name: + # Prepare variables + duration = track.get("length", 0) + minutes = duration // 60 + seconds = duration % 60 + duration_str = f"{minutes}:{seconds:02d}" + + new_name = format_str.format( + title=track.get("title", "Unknown"), + requester=requester.display_name, + duration=duration_str + ) + + # Ensure name length is valid (Discord limit is 100 characters) + if len(new_name) > 100: + new_name = new_name[:97] + "..." + + # Update channel name if different and we have permissions + if channel.name != new_name and guild.me.guild_permissions.manage_channels: await channel.edit(name=new_name) except discord.Forbidden: @@ -122,12 +235,52 @@ class ExtendedAudio(commands.Cog): channel = voice_client.channel - # Reset channel name if it was modified (contains 🎵) - if "🎵" in channel.name: - original_name = channel.name.split("🎵")[-1].strip() + # Reset to original name if stored and we have permissions + original_name = await self.config.guild(guild).original_name() + if original_name and guild.me.guild_permissions.manage_channels: await channel.edit(name=original_name) except discord.Forbidden: log.warning(f"Missing permissions to edit channel name in {guild.name}") except Exception as e: - log.error(f"Error resetting channel status in {guild.name}: {e}") \ No newline at end of file + log.error(f"Error resetting channel status in {guild.name}: {e}") + + @commands.Cog.listener() + async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState): + """Reset channel name when bot leaves voice channel""" + if not member.guild: + return + + # Check if this is the bot disconnecting + if member.id == self.bot.user.id and before.channel and not after.channel: + try: + # Reset channel name if we have the original stored and permissions + guild = member.guild + if not guild.me.guild_permissions.manage_channels: + return + + original_name = await self.config.guild(guild).original_name() + if original_name and before.channel: + await before.channel.edit(name=original_name) + await self.config.guild(guild).original_name.set(None) + except Exception as e: + log.error(f"Error resetting channel name on disconnect: {e}") + + @commands.Cog.listener() + async def on_red_audio_player_auto_disconnect(self, guild: discord.Guild): + """Handle auto-disconnect cleanup""" + try: + if guild.me.guild_permissions.manage_channels: + original_name = await self.config.guild(guild).original_name() + if original_name: + voice_client = guild.voice_client + if voice_client and voice_client.channel: + await voice_client.channel.edit(name=original_name) + await self.config.guild(guild).original_name.set(None) + except Exception as e: + log.error(f"Error handling auto-disconnect cleanup: {e}") + + @commands.Cog.listener() + async def on_red_audio_player_auto_pause(self, guild: discord.Guild): + """Handle auto-pause""" + # We don't change the channel name on pause to maintain consistency \ No newline at end of file