From 4c69cf7ab99c37a80ffd207efb8dbc7d9ae83976 Mon Sep 17 00:00:00 2001 From: Valerie Date: Fri, 13 Jun 2025 19:22:53 -0400 Subject: [PATCH] Add waiting room functionality to AutoRoom cog This update introduces a waiting room system for AutoRoom sources, allowing users to be approved before entering the main AutoRoom. New commands for enabling and disabling waiting rooms have been added, along with corresponding settings in the configuration. The README has been updated to include instructions on using the waiting room feature. --- autoroom/README.md | 25 ++++++ autoroom/autoroom.py | 17 ++++ autoroom/c_autoroomset.py | 47 ++++++++++ autoroom/waiting_room.py | 184 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 autoroom/waiting_room.py diff --git a/autoroom/README.md b/autoroom/README.md index c7c3cd5..e5d2a97 100644 --- a/autoroom/README.md +++ b/autoroom/README.md @@ -26,6 +26,31 @@ Start by having a voice channel, and a category (the voice channel does not need This command will guide you through setting up an AutoRoom Source by asking some questions. If you get a warning about missing permissions, take a look at `[p]autoroomset permissions`, grant the missing permissions, and then run the command again. Otherwise, answer the questions, and you'll have a new AutoRoom Source. Give it a try by joining it: if all goes well, you will be moved to a new AutoRoom, where you can do all of the `[p]autoroom` commands. +### Waiting Room System + +The waiting room system allows you to control who can join AutoRooms. When enabled, users will first join a waiting room where they must be approved by the AutoRoom owner before being allowed into the main AutoRoom. + +To enable the waiting room system for an AutoRoom source: + +``` +[p]autoroomset modify waitingroom enable +``` + +To disable it: + +``` +[p]autoroomset modify waitingroom disable +``` + +When a user joins the waiting room: +1. A message will appear in the AutoRoom's text channel with "Allow" and "Deny" buttons +2. The AutoRoom owner can click these buttons to either: + - Allow the user into the AutoRoom + - Deny access and move them back to the waiting room +3. If denied, the user will be added to the denied list and won't be able to join the AutoRoom again + +Note: The waiting room system requires a text channel to be enabled for the AutoRoom source. + There are some additional configuration options for AutoRoom Sources that you can set by using `[p]autoroomset modify`. You can also check out `[p]autoroomset access`, which controls whether admins (default yes) or moderators (default no) can see and join private AutoRooms. For an overview of all of your settings, use `[p]autoroomset settings`. #### Member Roles and Hidden Sources diff --git a/autoroom/autoroom.py b/autoroom/autoroom.py index 4d6dea7..e31ee8e 100644 --- a/autoroom/autoroom.py +++ b/autoroom/autoroom.py @@ -15,6 +15,7 @@ from .c_autoroom import AutoRoomCommands from .c_autoroomset import AutoRoomSetCommands, channel_name_template from .pcx_lib import Perms, SettingDisplay from .pcx_template import Template +from .waiting_room import WaitingRoom class CompositeMetaClass(type(commands.Cog), type(ABC)): @@ -57,6 +58,9 @@ class AutoRoom( "channel_name_format": "", "perm_owner_manage_channels": True, "perm_send_messages": True, + "waiting_room_enabled": False, + "waiting_room_channel_id": None, + "waiting_room_message_id": None, } default_channel_settings: ClassVar[dict[str, int | list[int] | None]] = { "source_channel": None, @@ -112,6 +116,7 @@ class AutoRoom( ) self.config.register_channel(**self.default_channel_settings) self.template = Template() + self.waiting_room = WaitingRoom(self) self.bucket_autoroom_create = commands.CooldownMapping.from_cooldown( 2, 60, lambda member: member ) @@ -357,6 +362,18 @@ class AutoRoom( bucket.update_rate_limit() if isinstance(joining.channel, discord.VoiceChannel): + # Check if user joined a waiting room + config = await self.config.custom( + "AUTOROOM_SOURCE", + str(joining.channel.guild.id) + ).all() + + for source_id, source_config in config.items(): + if source_config.get("waiting_room_enabled") and str(joining.channel.id) == str(source_config.get("waiting_room_channel_id")): + # User joined a waiting room, handle it + await self.waiting_room.handle_waiting_user(member, joining.channel) + return + # If user entered an AutoRoom Source channel, create new AutoRoom asc = await self.get_autoroom_source_config(joining.channel) if asc: diff --git a/autoroom/c_autoroomset.py b/autoroom/c_autoroomset.py index 655ce4a..66c8226 100644 --- a/autoroom/c_autoroomset.py +++ b/autoroom/c_autoroomset.py @@ -973,6 +973,53 @@ class AutoRoomSetCommands(MixinMeta, ABC): ) ) + @modify.group(name="waitingroom") + async def modify_waitingroom( + self, + ctx: commands.Context, + ) -> None: + """Modify waiting room settings.""" + pass + + @modify_waitingroom.command(name="enable") + async def modify_waitingroom_enable( + self, + ctx: commands.Context, + autoroom_source: discord.VoiceChannel, + ) -> None: + """Enable waiting room for an AutoRoom source.""" + if not await self.get_autoroom_source_config(autoroom_source): + await ctx.send("That is not an AutoRoom source.") + return + + # Get the text channel + text_channel = await self.get_autoroom_legacy_text_channel(autoroom_source) + if not text_channel: + await ctx.send("This AutoRoom source needs a text channel enabled first.") + return + + # Set up waiting room + success = await self.waiting_room.setup_waiting_room(autoroom_source, text_channel) + if success: + await ctx.send("Waiting room has been enabled for this AutoRoom source.") + else: + await ctx.send("Failed to set up waiting room. Please check my permissions.") + + @modify_waitingroom.command(name="disable") + async def modify_waitingroom_disable( + self, + ctx: commands.Context, + autoroom_source: discord.VoiceChannel, + ) -> None: + """Disable waiting room for an AutoRoom source.""" + if not await self.get_autoroom_source_config(autoroom_source): + await ctx.send("That is not an AutoRoom source.") + return + + # Clean up waiting room + await self.waiting_room.cleanup_waiting_room(autoroom_source) + await ctx.send("Waiting room has been disabled for this AutoRoom source.") + async def _check_all_perms( self, guild: discord.Guild, *, detailed: bool = False ) -> tuple[bool, bool, list[str]]: diff --git a/autoroom/waiting_room.py b/autoroom/waiting_room.py new file mode 100644 index 0000000..fbacf77 --- /dev/null +++ b/autoroom/waiting_room.py @@ -0,0 +1,184 @@ +"""Waiting room functionality for AutoRoom cog.""" + +from typing import Any, Optional + +import discord +from discord import ui +from redbot.core import Config +from redbot.core.bot import Red + +class WaitingRoomView(ui.View): + """View for waiting room control buttons.""" + + def __init__(self, cog: Any): + super().__init__(timeout=None) + self.cog = cog + + @discord.ui.button(label="Allow", style=discord.ButtonStyle.green, custom_id="waiting_room_allow") + async def allow_button(self, interaction: discord.Interaction, button: discord.ui.Button): + """Allow a user into the AutoRoom.""" + if not interaction.message: + return + + # Get the waiting user from the message + waiting_user_id = int(interaction.message.content.split("User: <@")[1].split(">")[0]) + waiting_user = interaction.guild.get_member(waiting_user_id) + + if not waiting_user: + await interaction.response.send_message("User no longer in the server.", ephemeral=True) + return + + # Get the AutoRoom info + autoroom_info = await self.cog.get_autoroom_info(interaction.channel) + if not autoroom_info: + await interaction.response.send_message("This is not an AutoRoom.", ephemeral=True) + return + + # Check if the interaction user is the owner + if interaction.user.id != autoroom_info["owner"]: + await interaction.response.send_message("Only the AutoRoom owner can use these buttons.", ephemeral=True) + return + + # Move the user to the AutoRoom + try: + await waiting_user.move_to(interaction.channel) + await interaction.message.delete() + await interaction.response.send_message(f"Allowed {waiting_user.mention} into the AutoRoom.", ephemeral=True) + except discord.HTTPException: + await interaction.response.send_message("Failed to move user to the AutoRoom.", ephemeral=True) + + @discord.ui.button(label="Deny", style=discord.ButtonStyle.red, custom_id="waiting_room_deny") + async def deny_button(self, interaction: discord.Interaction, button: discord.ui.Button): + """Deny a user access to the AutoRoom.""" + if not interaction.message: + return + + # Get the waiting user from the message + waiting_user_id = int(interaction.message.content.split("User: <@")[1].split(">")[0]) + waiting_user = interaction.guild.get_member(waiting_user_id) + + if not waiting_user: + await interaction.response.send_message("User no longer in the server.", ephemeral=True) + return + + # Get the AutoRoom info + autoroom_info = await self.cog.get_autoroom_info(interaction.channel) + if not autoroom_info: + await interaction.response.send_message("This is not an AutoRoom.", ephemeral=True) + return + + # Check if the interaction user is the owner + if interaction.user.id != autoroom_info["owner"]: + await interaction.response.send_message("Only the AutoRoom owner can use these buttons.", ephemeral=True) + return + + # Add user to denied list + denied = autoroom_info.get("denied", []) + if waiting_user.id not in denied: + denied.append(waiting_user.id) + await self.cog.config.channel(interaction.channel).denied.set(denied) + + # Move user back to waiting room + waiting_room = interaction.guild.get_channel(autoroom_info["waiting_room_channel_id"]) + if waiting_room: + try: + await waiting_user.move_to(waiting_room) + except discord.HTTPException: + pass + + await interaction.message.delete() + await interaction.response.send_message(f"Denied {waiting_user.mention} access to the AutoRoom.", ephemeral=True) + +class WaitingRoom: + """Handles waiting room functionality.""" + + def __init__(self, cog: Any): + self.cog = cog + self.bot: Red = cog.bot + self.config: Config = cog.config + + async def setup_waiting_room(self, autoroom_source: discord.VoiceChannel, text_channel: discord.TextChannel) -> bool: + """Set up the waiting room for an AutoRoom source.""" + try: + # Create waiting room channel + waiting_room = await autoroom_source.guild.create_voice_channel( + name="Waiting Room", + category=autoroom_source.category, + reason="AutoRoom: Setting up waiting room." + ) + + # Set up permissions + perms = discord.PermissionOverwrite( + view_channel=True, + connect=True, + speak=True, + stream=True, + use_voice_activation=True + ) + await waiting_room.set_permissions(autoroom_source.guild.default_role, overwrite=perms) + + # Save waiting room channel ID + await self.config.custom( + "AUTOROOM_SOURCE", + str(autoroom_source.guild.id), + str(autoroom_source.id) + ).waiting_room_channel_id.set(waiting_room.id) + + # Enable waiting room + await self.config.custom( + "AUTOROOM_SOURCE", + str(autoroom_source.guild.id), + str(autoroom_source.id) + ).waiting_room_enabled.set(True) + + return True + except discord.HTTPException: + return False + + async def handle_waiting_user(self, member: discord.Member, autoroom: discord.VoiceChannel) -> None: + """Handle a user joining the waiting room.""" + autoroom_info = await self.cog.get_autoroom_info(autoroom) + if not autoroom_info: + return + + # Get the text channel + text_channel = await self.cog.get_autoroom_legacy_text_channel(autoroom) + if not text_channel: + return + + # Create waiting message with buttons + view = WaitingRoomView(self.cog) + message = await text_channel.send( + f"User: {member.mention} is waiting to join the AutoRoom.", + view=view + ) + + # Save message ID + await self.config.channel(autoroom).waiting_room_message_id.set(message.id) + + async def cleanup_waiting_room(self, autoroom_source: discord.VoiceChannel) -> None: + """Clean up the waiting room when disabling it.""" + config = await self.config.custom( + "AUTOROOM_SOURCE", + str(autoroom_source.guild.id), + str(autoroom_source.id) + ).all() + + if config["waiting_room_channel_id"]: + waiting_room = autoroom_source.guild.get_channel(config["waiting_room_channel_id"]) + if waiting_room: + try: + await waiting_room.delete(reason="AutoRoom: Disabling waiting room.") + except discord.HTTPException: + pass + + await self.config.custom( + "AUTOROOM_SOURCE", + str(autoroom_source.guild.id), + str(autoroom_source.id) + ).waiting_room_enabled.set(False) + await self.config.custom( + "AUTOROOM_SOURCE", + str(autoroom_source.guild.id), + str(autoroom_source.id) + ).waiting_room_channel_id.set(None) \ No newline at end of file