From b6f8ffbc36ece5014959d6da8ff22fddeb21e891 Mon Sep 17 00:00:00 2001 From: zv Date: Wed, 6 May 2026 18:37:32 +0200 Subject: [PATCH] fix(tempvoice): validate dashboard owner access and privacy toggle --- tempvoice.py | 85 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/tempvoice.py b/tempvoice.py index c356e8e..c295f9b 100644 --- a/tempvoice.py +++ b/tempvoice.py @@ -98,6 +98,9 @@ class TempVoiceDashboardView(discord.ui.View): ephemeral=True, ) return + allowed, _ = await self.cog._validate_interaction_access(interaction, channel) + if not allowed: + return await interaction.response.send_modal(TempVoiceInputModal(self.cog, action, channel.id)) @discord.ui.button( @@ -318,7 +321,7 @@ class TempVoice(commands.Cog): def _can_control_tempvoice_channel(self, member: discord.Member, owner_id: Optional[int]) -> bool: if owner_id is None: return False - return member.id == owner_id or member.guild_permissions.manage_guild + return member.id == owner_id async def _find_message_target( self, guild: discord.Guild, preferred_voice: Optional[discord.VoiceChannel] = None @@ -344,10 +347,18 @@ class TempVoice(commands.Cog): return ch return None - def _build_dashboard_embed(self, guild: discord.Guild, channel: discord.VoiceChannel, owner_id: int) -> discord.Embed: + def _build_dashboard_embed( + self, + guild: discord.Guild, + channel: discord.VoiceChannel, + owner_id: int, + *, + is_private: Optional[bool] = None, + ) -> discord.Embed: everyone = guild.default_role overwrite = channel.overwrites_for(everyone) - is_private = overwrite.connect is False or overwrite.view_channel is False + if is_private is None: + is_private = overwrite.connect is False or overwrite.view_channel is False owner_mention = f"<@{owner_id}>" member_mentions = ", ".join(m.mention for m in channel.members) if channel.members else _("No members") @@ -579,22 +590,28 @@ class TempVoice(commands.Cog): return False, None if not self._can_control_tempvoice_channel(interaction.user, owner_id): await interaction.response.send_message( - _("Only the channel owner (or server manager) can use this panel."), + _("Only the current channel owner can use this panel."), ephemeral=True, ) return False, owner_id return True, owner_id + async def _send_interaction_notice(self, interaction: discord.Interaction, message: str) -> None: + if interaction.response.is_done(): + await interaction.followup.send(message, ephemeral=True) + else: + await interaction.response.send_message(message, ephemeral=True) + async def handle_panel_button( self, interaction: discord.Interaction, action: str, channel: Optional[discord.VoiceChannel] ): - allowed, _ = await self._validate_interaction_access(interaction, channel) + allowed, owner_id = await self._validate_interaction_access(interaction, channel) if not allowed or channel is None: return guild = interaction.guild assert guild is not None me = guild.me - if me is None or not guild.me.guild_permissions.manage_channels: + if me is None or not channel.permissions_for(me).manage_channels: await interaction.response.send_message( _("I need Manage Channels permission to modify this channel."), ephemeral=True, @@ -602,35 +619,53 @@ class TempVoice(commands.Cog): return if action == "toggle_private": + await interaction.response.defer(ephemeral=True) everyone = guild.default_role ow = channel.overwrites_for(everyone) currently_private = ow.connect is False or ow.view_channel is False + new_is_private = not currently_private if currently_private: - ow.connect = None - ow.view_channel = None new_state = _("public") else: - ow.connect = False - ow.view_channel = False new_state = _("private") try: await channel.set_permissions( - everyone, overwrite=ow, reason=f"TempVoice panel toggle {new_state}" + everyone, + view_channel=not new_is_private, + connect=not new_is_private, + reason=f"TempVoice panel toggle {new_state}", ) - await interaction.response.send_message( + if interaction.message is not None and owner_id is not None: + embed = self._build_dashboard_embed( + guild, + channel, + owner_id, + is_private=new_is_private, + ) + await interaction.message.edit(embed=embed, view=self._dashboard_view) + await self._send_interaction_notice( + interaction, _("Channel visibility changed. New state: {state}.").format(state=new_state), - ephemeral=True, ) - await self._refresh_dashboard_message(interaction, channel) except discord.Forbidden: - await interaction.response.send_message( + await self._send_interaction_notice( + interaction, _("I cannot update channel permissions."), - ephemeral=True, ) except discord.HTTPException: - await interaction.response.send_message( + await self._send_interaction_notice( + interaction, _("Discord rejected the permission update."), - ephemeral=True, + ) + except Exception: + log.exception( + "Unexpected error while toggling TempVoice privacy for channel %s in guild %s.", + channel.id, + guild.id, + ) + await self._send_interaction_notice( + interaction, + _("Unexpected error while updating channel permissions."), ) return @@ -658,6 +693,7 @@ class TempVoice(commands.Cog): if me is None: await interaction.response.send_message(_("Bot state is not ready."), ephemeral=True) return + bot_channel_perms = channel.permissions_for(me) try: if action == "change_limit": @@ -669,7 +705,7 @@ class TempVoice(commands.Cog): if value < 0 or value > 99: await interaction.response.send_message(_("User limit must be between 0 and 99."), ephemeral=True) return - if not me.guild_permissions.manage_channels: + if not bot_channel_perms.manage_channels: await interaction.response.send_message( _("I need Manage Channels permission to change the user limit."), ephemeral=True, @@ -684,7 +720,7 @@ class TempVoice(commands.Cog): return if action == "change_name": - if not me.guild_permissions.manage_channels: + if not bot_channel_perms.manage_channels: await interaction.response.send_message( _("I need Manage Channels permission to change channel name."), ephemeral=True, @@ -709,8 +745,17 @@ class TempVoice(commands.Cog): if target is None: await interaction.response.send_message(_("Could not find that member."), ephemeral=True) return + previous_owner = guild.get_member(owner_id) if owner_id is not None else None await self._set_channel_owner(guild, channel.id, target.id) await self._ensure_owner_permissions(channel, target) + if previous_owner is not None and previous_owner.id != target.id: + previous_overwrite = channel.overwrites_for(previous_owner) + previous_overwrite.manage_channels = None + await channel.set_permissions( + previous_owner, + overwrite=previous_overwrite, + reason="TempVoice owner changed", + ) await interaction.response.send_message( _("Channel owner changed to {member}.").format(member=target.mention), ephemeral=True,