diff --git a/bot.py b/bot.py index 28ac30a..4104293 100644 --- a/bot.py +++ b/bot.py @@ -13,7 +13,7 @@ from dotenv import load_dotenv from lib.config import config, config_meta, config_load, config_save, config_get, config_set, config_get_descriptions, \ config_set_raw -from lib.utils import async_filter, find_category +from lib.utils import async_filter, find_category, find_role_case_insensitive load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') @@ -49,6 +49,7 @@ OCH_LOEH_SOUND = "assets/och_loeh.mp3" config_load() intents = discord.Intents.default() intents.members = True +intents.voice_states = True bot = commands.Bot(command_prefix=config.get('prefix'), intents=intents) if 'LIBOPUS' in os.environ and not len(os.environ['LIBOPUS']) == 0: @@ -200,6 +201,21 @@ async def on_message(message: discord.Message): await bot.process_commands(message) +@bot.event +async def on_voice_state_update(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState): + if after.channel is None and before.channel is not None: + channel: discord.VoiceChannel = before.channel + if not channel.voice_states: + if channel.category is not None and channel.name.endswith("_vc"): + if channel.category.name.lower() == config_get("groups-category", channel.guild.id).lower(): + name: str = channel.name[:-3] + role = await find_role_case_insensitive(channel.guild, name, + config_get("groups-role-prefix", channel.guild.id)) + + if role is not None: + await channel.delete(reason="Delete temporary group channel when last person left") + + @bot.command(name='random_message', brief='Select a random message from a channel') async def random_message_command(ctx: commands.Context, channel: Optional[discord.TextChannel] = None, max_cnt: Optional[int] = 100, reaction_filter: Optional[str] = None): @@ -354,13 +370,14 @@ async def group_command(ctx: commands.Context, subcommand: Optional[str], arg: O await ctx.send("Access denied!") return if subcommand is None: - await ctx.send("Available commands: `list`, `create `, `archive `, `delete `") + await ctx.send("Available commands: `list`, `create `, " + "`archive `, `unarchive `, `delete `, `clear_vcs`") return guild: discord.Guild = ctx.guild role_prefix = config_get("groups-role-prefix", guild.id) - def collect_group_channels(cat: discord.CategoryChannel) -> dict[str, discord.TextChannel]: + def collect_group_channels(cat: discord.CategoryChannel) -> dict[str, discord.abc.GuildChannel]: return {channel.name: channel for channel in cat.text_channels} async def collect_group_roles() -> list[discord.Role]: @@ -369,9 +386,10 @@ async def group_command(ctx: commands.Context, subcommand: Optional[str], arg: O async def fail_category(type: str, expected: str): await ctx.send("Unable to find channel category \"" + expected + "\" for " + type + ". Change in configs.") - def link_channel(channel: discord.TextChannel, italic: bool = False) -> str: + def link_channel(channel: discord.abc.GuildChannel, italic: bool = False) -> str: if italic: - return '[*' + channel.name + '*](https://discord.com/channels/' + str(guild.id) + '/' + str(channel.id) + ')' + return '[*' + channel.name + '*](https://discord.com/channels/' + str(guild.id) + '/' + str( + channel.id) + ')' return '[' + channel.name + '](https://discord.com/channels/' + str(guild.id) + '/' + str(channel.id) + ')' groups_cat = find_category(guild, config_get("groups-category", guild.id)) @@ -414,8 +432,29 @@ async def group_command(ctx: commands.Context, subcommand: Optional[str], arg: O await fail_category("archive", config_get("groups-archive-category", guild.id)) return await groups[channel_name].edit(reason="Archive group " + arg, category=archive_cat) + if channel_name + "_vc" in groups: + await groups[channel_name + "_vc"].delete(reason="Archive group" + arg) + await ctx.send("Group " + arg + " archived.") + else: + await ctx.send("Can't find that group!") + elif subcommand == 'unarchive': + if arg is None: + await ctx.send("Group name required!") + return + + channel_name = arg.lower() + archive_cat = find_category(guild, config_get("groups-archive-category", guild.id)) + if archive_cat is None: + await fail_category("archive", config_get("groups-archive-category", guild.id)) + return + archived_groups = collect_group_channels(archive_cat) + + if channel_name in archived_groups: + await archived_groups[channel_name].edit(reason="Archive group " + arg, category=groups_cat) + await ctx.send("Group " + arg + " unarchived.") + else: await ctx.send("Can't find that group!") elif subcommand == 'delete': @@ -428,18 +467,18 @@ async def group_command(ctx: commands.Context, subcommand: Optional[str], arg: O await fail_category("archive", config_get("groups-archive-category", guild.id)) return - active_groups = collect_group_channels(groups_cat) - archive_groups = collect_group_channels(archive_cat) - - role = guild.get_role(config_get("groups-role-prefix", guild.id) + arg) + role = await find_role_case_insensitive(guild, arg, role_prefix) if role is not None: await role.delete(reason="Delete group " + arg) + groups = collect_group_channels(groups_cat) + groups.update(collect_group_channels(archive_cat)) + channel_name = arg.lower() - if channel_name in active_groups: - await active_groups[channel_name].delete(reason="Delete group " + arg) - if channel_name in archive_groups: - await archive_groups[channel_name].delete(reason="Delete group " + arg) + if channel_name in groups: + await groups[channel_name].delete(reason="Delete group " + arg) + if channel_name + "_vc" in groups: + await groups[channel_name + "_vc"].delete(reason="Delete group " + arg) await ctx.send("Group " + arg + " deleted.") elif subcommand == 'create': @@ -463,6 +502,40 @@ async def group_command(ctx: commands.Context, subcommand: Optional[str], arg: O await asyncio.sleep(5) await channel.send("Hi, " + role.mention) await ctx.send("Group " + arg + " created.") + elif subcommand == 'clear_vcs': + cors: list[coroutine] = [] + for vc in groups_cat.voice_channels: + cors.append(vc.delete(reason="Clear temporary vcs, as requested by " + ctx.author.mention)) + for cor in cors: + await cor + + +@bot.command(name='groupvc', brief='Creates a temporary vc for the current group text channel') +async def groupvc_command(ctx: commands.Context): + if ctx.channel.category is not None: + guild: discord.Guild = ctx.guild + channel: discord.TextChannel = ctx.channel + if channel.category.name.lower() == config_get("groups-category", guild.id): + role = await find_role_case_insensitive(guild, channel.name, config_get("groups-role-prefix", guild.id)) + if role is None: + await ctx.send("Couldn't resolve group role!") + return + + category: discord.CategoryChannel = channel.category + for vc in category.voice_channels: + if vc.name.lower() == channel.name.lower() + "_vc": + await ctx.send("Temporary group channel already exists.") + return + + vc = await category.create_voice_channel(channel.name + "_vc", + overwrites={ + role: discord.PermissionOverwrite(view_channel=True) + }, + reason="Create temporary vc for group " + channel.name) + await ctx.send("Created temporary vc for this group") + return + + await ctx.send("Not an active group channel!") def _is_message_valid_for_selection(message: discord.Message, reaction_filter: Optional[str] = None) -> bool: diff --git a/lib/utils.py b/lib/utils.py index 1c7f654..1f18fa5 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -15,3 +15,10 @@ def find_category(guild: discord.Guild, group: str) -> Optional[discord.Category if cat.name.lower() == group: return cat return None + + +async def find_role_case_insensitive(guild: discord.Guild, name: str, prefix: str = '') -> Optional[discord.Role]: + for role in await guild.fetch_roles(): + if name.strip().lower() == role.name[len(prefix):].lower(): + return role + return None