From 7afa958c8fe9b6e5854a23cca6f41dfdb9254f27 Mon Sep 17 00:00:00 2001 From: Laura Demkowicz-Duffy Date: Mon, 30 Aug 2021 13:18:30 +0000 Subject: [PATCH] Hotfixes from Testing Session (#183) * Remove reference to deprecated function * Move music channel management commands to their own group to avoid falling into channel checking problems * Fixed an issue caused by moving default_role_id to its own table from Guild_info * Fixed an issue where a VM child would not be created if the Parent VC was joined from another VM Child * Correct musicchannel command in README * Fixed an issue where youtu.be links would not work * Fixed an issue where songs would not play when supplying the URL directly * Added missing doc strings for EventCategoriesCog functions Co-authored-by: Laura Demkowicz-Duffy Co-authored-by: Fluxticks --- README.md | 136 ++++++------- src/esportsbot/cogs/DefaultRoleCog.py | 2 +- src/esportsbot/cogs/EventCategoriesCog.py | 228 +++++++++++----------- src/esportsbot/cogs/MusicCog.py | 10 +- src/esportsbot/cogs/VoicemasterCog.py | 64 +++--- 5 files changed, 215 insertions(+), 225 deletions(-) diff --git a/README.md b/README.md index 73036cfe..d196769a 100644 --- a/README.md +++ b/README.md @@ -67,70 +67,70 @@ $ source secrets.env ```bash $ cd src ``` -5. Install all the requirements for python: -```bash -pip install -r requirements.txt -``` -6. Run the bot: -```bash -python3 main.py -``` - -## Current Functions -The list below describes the different "Cogs" of the bot, their associated commands, and any additional information required to set them up. - -
-Voicemaster - -### Voicemaster - #### !setvmmaster -* Make the given ID a Voicemaster master. - -#### !getvmmasters * Get all the Voicemaster masters in the server. - -#### !removevmmaster -* Remove the given ID as a Voicemaster master. - -#### !removeallmasters * Remove all Voicemaster masters from the server. - -#### !killallslaves * Kill all the Voicemaster slave channels in the server. - -#### !lockvm * Locks the Voicemaster slave you're currently in to the number of current members. - -#### !unlockvm * Unlocks the Voicemaster slave you're currently in. -
- -
-Default Role - -### Default role - #### !setdefaultroles * Sets the roles that the server gives to members when they join the server. - -#### !getdefaultroles * Gets the current default roles set for the server. - -#### !removedefaultroles * Removes the current default roles for the server. -
- -
-Log Channel - -### Log Channel - #### !setlogchannel * Set the log channel to the #'ed channel or given role ID. - -#### !getlogchannel * Gets the current log channel value. - -#### !removelogchannel * Removes the current log channel value. -
- -
-Administrator Tools - -### Administrator Tools - Adds a few commands useful for admin operations. -#### !clear_message * Aliases: `cls, purge, delete` -* Clear the specified number of messages from the current text channel. - -#### !members +5. Install all the requirements for python: +```bash +pip install -r requirements.txt +``` +6. Run the bot: +```bash +python3 main.py +``` + +## Current Functions +The list below describes the different "Cogs" of the bot, their associated commands, and any additional information required to set them up. + +
+Voicemaster + +### Voicemaster + #### !setvmmaster +* Make the given ID a Voicemaster master. + +#### !getvmmasters * Get all the Voicemaster masters in the server. + +#### !removevmmaster +* Remove the given ID as a Voicemaster master. + +#### !removeallmasters * Remove all Voicemaster masters from the server. + +#### !killallslaves * Kill all the Voicemaster slave channels in the server. + +#### !lockvm * Locks the Voicemaster slave you're currently in to the number of current members. + +#### !unlockvm * Unlocks the Voicemaster slave you're currently in. +
+ +
+Default Role + +### Default role + #### !setdefaultroles * Sets the roles that the server gives to members when they join the server. + +#### !getdefaultroles * Gets the current default roles set for the server. + +#### !removedefaultroles * Removes the current default roles for the server. +
+ +
+Log Channel + +### Log Channel + #### !setlogchannel * Set the log channel to the #'ed channel or given role ID. + +#### !getlogchannel * Gets the current log channel value. + +#### !removelogchannel * Removes the current log channel value. +
+ +
+Administrator Tools + +### Administrator Tools + Adds a few commands useful for admin operations. +#### !clear_message * Aliases: `cls, purge, delete` +* Clear the specified number of messages from the current text channel. + +#### !members * List the current number of members in the server. #### !remove-cog \ @@ -439,22 +439,22 @@ For this cog to work, the `GOOGLE_API` env var must also be set, and instruction 1. Click on `Create Credentials` and then `API key`. 1. Copy the key given. For security, it is recommended that you "restrict key" and only enable `YouTube Data API v3`. -#### !music channel set \ [optional: [args]] +#### musicchannel set \ [optional: [args]] * This sets the channel mentioned to be used as the music channel. All messages into this channel will be considered music requests, and any music commands must be sent in this channel. * Optional args: * Using `-c` will clear the entire channel before setting it up as the music channel. * *Requires `administrator` permission in Discord* -#### !music channel get +#### musicchannel get * Sends the currently set music channel for the server. * *Requires `administrator` permission in Discord* -#### !music channel reset +#### musicchannel reset * This clears the current music channel and resets the preview and queue messages. * *Requires `administrator` permission in Discord* -#### !music channel remove +#### musicchannel remove * Unlinks the currently linked music channel from being the music channel. This will not delete the channel or its contents. * *Requires `administrator` permission in Discord* diff --git a/src/esportsbot/cogs/DefaultRoleCog.py b/src/esportsbot/cogs/DefaultRoleCog.py index 58cba6f8..1ed5ac3e 100644 --- a/src/esportsbot/cogs/DefaultRoleCog.py +++ b/src/esportsbot/cogs/DefaultRoleCog.py @@ -2,7 +2,7 @@ from esportsbot.base_functions import role_id_from_mention from esportsbot.db_gateway import DBGatewayActions from esportsbot.models import Guild_info, Default_roles -from esportsbot.base_functions import role_id_from_mention, send_to_log_channel +from esportsbot.base_functions import role_id_from_mention class DefaultRoleCog(commands.Cog): diff --git a/src/esportsbot/cogs/EventCategoriesCog.py b/src/esportsbot/cogs/EventCategoriesCog.py index 0201436c..11d60ce6 100644 --- a/src/esportsbot/cogs/EventCategoriesCog.py +++ b/src/esportsbot/cogs/EventCategoriesCog.py @@ -1,6 +1,7 @@ import asyncio import logging from collections import defaultdict +from enum import IntEnum from discord import Forbidden, PermissionOverwrite, Role from discord.ext import commands @@ -10,7 +11,7 @@ from esportsbot.DiscordReactableMenus.reactable_lib import get_menu from esportsbot.db_gateway import DBGatewayActions from esportsbot.lib.discordUtil import get_attempted_arg -from esportsbot.models import Event_categories, Guild_info +from esportsbot.models import Event_categories, Default_roles denied_perms = PermissionOverwrite(read_messages=False, send_messages=False, connect=False) read_only_perms = PermissionOverwrite(read_messages=True, send_messages=False, connect=False) @@ -24,6 +25,13 @@ VOICE_CHANNEL_SUFFIX = "VC" +class RoleTypeEnum(IntEnum): + DEFAULT = 0 # The Default role + SHARED = 1 # The Shared role users receive when joining the server + EVENT = 2 # The Event role + TOP = 3 # The Top role the bot has + + class EventCategoriesCog(commands.Cog): def __init__(self, bot): self.bot = bot @@ -129,82 +137,80 @@ def delete_event_data(self, guild_id, event_id): db_item = self.db.get(Event_categories, guild_id=guild_id, event_id=event_id) self.db.delete(db_item) - @staticmethod - async def event_closed_perms(general, sign_in, voice_chat, bot_role, event_role, shared_role, reason): - """ - Sets the permissions of the channels to those of a closed event . - :param general: The general text channel in the event . - :param sign_in: The sign-in channel for the event . - :param voice_chat: The voice channel in the event . - :param bot_role: The top role that the bot has . - :param event_role: The role for the event . - :param shared_role: The shared role in the server that all users have . - :param reason: The audit reason . - """ - # The Guilds default role, pretty much always @everyone - default_role = general.guild.default_role - - # Get the current permissions for the general channel and update them - current_general = { - bot_role: writable_perms, - event_role: denied_perms, - shared_role: denied_perms, - default_role: denied_perms - } - await general.edit(overwrites=current_general, reason=reason) - - # Get the current permissions for the voice channel and update them - current_vc = { - bot_role: writable_perms, - event_role: denied_perms, - shared_role: denied_perms, - default_role: denied_perms - } - await voice_chat.edit(overwrites=current_vc, reason=reason) - - # Get the current permissions for the sign in channel and update them - current_sign_in = { - bot_role: writable_perms, - event_role: denied_perms, - shared_role: denied_perms, - default_role: denied_perms - } - await sign_in.edit(overwrites=current_sign_in, reason=reason) + async def set_role_permissions_for_event(self, event_menu, role, role_type, reason): + """ + Sets the permissions for the event channels for the given role based on its role type. + :param event_menu: The event menu of the event to update the roles of. + :param role: The role to update the permissions of. + :param role_type: The type of role the role is. + :param reason: The audit log reason to log with. + """ + if event_menu.enabled: + await self.set_open_perms(event_menu, role, role_type, reason) + else: + await self.set_closed_perms(event_menu, role, role_type, reason) - @staticmethod - async def event_open_perms(general, sign_in, voice_chat, event_role, shared_role, reason): - """ - Sets the permissions of the channels to those of an open event . - :param general: The general text channel in the event . - :param sign_in: The sign-in channel for the event . - :param voice_chat: The voice channel in the event . - :param event_role: The role for the event . - :param shared_role: The shared role in the server that all users have . - :param reason: The audit reason . - """ - # The Guilds default role, pretty much always @everyone - default_role = general.guild.default_role - - # Get the current permissions for the general channel and update them - current_general = general.overwrites - current_general[event_role] = writable_perms - current_general[shared_role] = denied_perms - current_general[default_role] = denied_perms - await general.edit(overwrites=current_general, reason=reason) - - # Get the current permissions for the voice channel and update them - current_vc = voice_chat.overwrites - current_vc[event_role] = writable_perms - current_vc[shared_role] = denied_perms - current_vc[default_role] = denied_perms - await voice_chat.edit(overwrites=current_vc, reason=reason) - - # Get the current permissions for the sign in channel and update them - current_sign_in = sign_in.overwrites - current_sign_in[event_role] = read_only_perms - current_sign_in[shared_role] = read_only_perms - current_sign_in[default_role] = denied_perms - await sign_in.edit(overwrites=current_sign_in, reason=reason) + async def set_open_perms(self, event_menu, role, role_type, reason): + """ + Sets the permissions for the given role and role type for when the event is open. + :param event_menu: The menu of the event. + :param role: The role to update the permissions of. + :param role_type: The type of role the role is. + :param reason: The audit log reason to log with. + """ + general_channel, sign_in_channel, voice_channel = self.get_event_channels(event_menu) + + current_general_perms = general_channel.overwrites + current_vc_perms = voice_channel.overwrites + current_sign_in_perms = sign_in_channel.overwrites + + if role_type == RoleTypeEnum.DEFAULT: + current_sign_in_perms[role] = denied_perms + current_vc_perms[role] = denied_perms + current_general_perms[role] = denied_perms + if role_type == RoleTypeEnum.SHARED: + current_sign_in_perms[role] = read_only_perms + current_vc_perms[role] = denied_perms + current_general_perms[role] = denied_perms + if role_type == RoleTypeEnum.EVENT: + current_sign_in_perms[role] = read_only_perms + current_vc_perms[role] = writable_perms + current_general_perms[role] = writable_perms + if role_type == RoleTypeEnum.TOP: + current_sign_in_perms[role] = writable_perms + current_vc_perms[role] = writable_perms + current_general_perms[role] = writable_perms + + await general_channel.edit(overwrites=current_general_perms, reason=reason) + await sign_in_channel.edit(overwrites=current_sign_in_perms, reason=reason) + await voice_channel.edit(overwrites=current_vc_perms, reason=reason) + + async def set_closed_perms(self, event_menu, role, role_type, reason): + """ + Sets the permissions for the given role and role type for when the event is closed. + :param event_menu: The menu of the event. + :param role: The role to update the permissions of. + :param role_type: The type of role the role is. + :param reason: The audit log reason to log with. + """ + general_channel, sign_in_channel, voice_channel = self.get_event_channels(event_menu) + + current_general_perms = general_channel.overwrites + current_vc_perms = voice_channel.overwrites + current_sign_in_perms = sign_in_channel.overwrites + + if role_type == RoleTypeEnum.TOP: + current_sign_in_perms[role] = writable_perms + current_vc_perms[role] = writable_perms + current_general_perms[role] = writable_perms + else: + current_sign_in_perms[role] = denied_perms + current_vc_perms[role] = denied_perms + current_general_perms[role] = denied_perms + + await general_channel.edit(overwrites=current_general_perms, reason=reason) + await sign_in_channel.edit(overwrites=current_sign_in_perms, reason=reason) + await voice_channel.edit(overwrites=current_vc_perms, reason=reason) @staticmethod def get_event_channels(event_menu): @@ -250,11 +256,11 @@ async def create_event(self, context: commands.Context, event_name: str, shared_ audit_reason = "Done with `create-event` command" if not shared_role: - db_data = self.db.get(Guild_info, guild_id=context.guild.id) - if not db_data or not db_data.default_role_id: + db_data = self.db.get(Default_roles, guild_id=context.guild.id) + if not db_data or not db_data.role_id: shared_role = context.guild.default_role else: - shared_role = context.guild.get_role(db_data.default_role_id) + shared_role = context.guild.get_role(db_data.role_id) if not shared_role: shared_role = context.guild.default_role @@ -292,17 +298,6 @@ async def create_event(self, context: commands.Context, event_name: str, shared_ # Used to ensure that the bot can always see/type in the channel. bot_top_role = context.me.roles[-1] - # Set the permissions for each of the channels created by the command: - await self.event_closed_perms( - event_general_channel, - event_sign_in_channel, - event_voice_channel, - bot_top_role, - event_role, - shared_role, - audit_reason - ) - # Create the sign-in message: event_menu = EventReactMenu( shared_role=shared_role, @@ -316,6 +311,16 @@ async def create_event(self, context: commands.Context, event_name: str, shared_ await event_menu.finalise_and_send(self.bot, event_sign_in_channel) + await self.set_role_permissions_for_event(event_menu, bot_top_role, RoleTypeEnum.TOP, reason=audit_reason) + await self.set_role_permissions_for_event(event_menu, shared_role, RoleTypeEnum.SHARED, reason=audit_reason) + await self.set_role_permissions_for_event(event_menu, event_role, RoleTypeEnum.EVENT, reason=audit_reason) + await self.set_role_permissions_for_event( + event_menu, + context.guild.default_role, + RoleTypeEnum.DEFAULT, + reason=audit_reason + ) + db_item = Event_categories( guild_id=context.guild.id, event_id=event_menu.id, @@ -362,19 +367,21 @@ async def open_event(self, context: commands.Context, event_name: str): await self.send_current_events(context) return - # Set the permissions for the generic event channels: - general_channel, sign_in_channel, voice_channel = self.get_event_channels(event_menu) - await self.event_open_perms( - general_channel, - sign_in_channel, - voice_channel, - event_menu.event_role, - event_menu.shared_role, - reason=audit_reason - ) + bot_top_role = context.me.roles[-1] await event_menu.enable_menu(self.bot) self.update_event(context.guild.id, event_menu) + + await self.set_role_permissions_for_event(event_menu, bot_top_role, RoleTypeEnum.TOP, reason=audit_reason) + await self.set_role_permissions_for_event(event_menu, event_menu.shared_role, RoleTypeEnum.SHARED, reason=audit_reason) + await self.set_role_permissions_for_event(event_menu, event_menu.event_role, RoleTypeEnum.EVENT, reason=audit_reason) + await self.set_role_permissions_for_event( + event_menu, + context.guild.default_role, + RoleTypeEnum.DEFAULT, + reason=audit_reason + ) + self.logger.info(f"Successfully opened an event with the name {event_name} in {context.guild.name}") await context.reply( self.user_strings["success_channel"].format( @@ -411,20 +418,19 @@ async def close_event(self, context: commands.Context, event_name: str): bot_top_role = context.me.roles[-1] - # Set the permissions for the generic event channels: - general_channel, sign_in_channel, voice_channel = self.get_event_channels(event_menu) - await self.event_closed_perms( - general_channel, - sign_in_channel, - voice_channel, - bot_top_role, - event_menu.event_role, - event_menu.shared_role, + await event_menu.disable_menu(self.bot) + self.update_event(context.guild.id, event_menu) + + await self.set_role_permissions_for_event(event_menu, bot_top_role, RoleTypeEnum.TOP, reason=audit_reason) + await self.set_role_permissions_for_event(event_menu, event_menu.shared_role, RoleTypeEnum.SHARED, reason=audit_reason) + await self.set_role_permissions_for_event(event_menu, event_menu.event_role, RoleTypeEnum.EVENT, reason=audit_reason) + await self.set_role_permissions_for_event( + event_menu, + context.guild.default_role, + RoleTypeEnum.DEFAULT, reason=audit_reason ) - await event_menu.disable_menu(self.bot) - self.update_event(context.guild.id, event_menu) self.logger.info(f"Successfully closed an event with the name {event_name} in {context.guild.name}") await context.reply(self.user_strings["success_event_closed"]) return diff --git a/src/esportsbot/cogs/MusicCog.py b/src/esportsbot/cogs/MusicCog.py index 5bc0bab9..1857392d 100644 --- a/src/esportsbot/cogs/MusicCog.py +++ b/src/esportsbot/cogs/MusicCog.py @@ -1,4 +1,3 @@ -import asyncio import datetime import functools import logging @@ -463,7 +462,10 @@ def get_youtube_request(request, request_type): key = "v" if request_type == MessageTypeEnum.youtube_url else "list" query = parse_qs(urlparse(request).query, keep_blank_values=True) - youtube_id = query[key][0] + if not query: + youtube_id = request.split("/")[-1] + else: + youtube_id = query[key][0] api_args = {"part": "snippet", "maxResults": 1 if request_type == MessageTypeEnum.youtube_url else 50} @@ -525,7 +527,7 @@ def url_from_response(response): video_id = response.get("id") else: video_id = response.get("resourceId").get("videoId") - return "https://youtube.com/watch?=v{}".format(video_id) + return "https://youtube.com/watch?v={}".format(video_id) @staticmethod def query_request(request): @@ -1277,7 +1279,7 @@ async def guild_bot_reset_command(self, context: commands.Context): await self.disconnect_from_guild(context.guild) await self.reset_music_channel(context) - @command_group.group(name="channel", help="Manage music channel") + @commands.group(name="musicchannel", help="Manage music channel") async def music_channel_group(self, context: commands.Context): """ The command group for the music channel management. diff --git a/src/esportsbot/cogs/VoicemasterCog.py b/src/esportsbot/cogs/VoicemasterCog.py index 840a1393..8638c0f4 100644 --- a/src/esportsbot/cogs/VoicemasterCog.py +++ b/src/esportsbot/cogs/VoicemasterCog.py @@ -11,8 +11,6 @@ def __init__(self, bot): @commands.Cog.listener() async def on_voice_state_update(self, member, before, after): - before_channel_id = before.channel.id if before.channel else False - after_channel_id = after.channel.id if after.channel else False if not member.guild.me.guild_permissions.move_members: await self.bot.adminLog( @@ -22,45 +20,29 @@ async def on_voice_state_update(self, member, before, after): ) return - if before_channel_id and get_whether_in_vm_slave(member.guild.id, before_channel_id): - vm_slave = DBGatewayActions().get(Voicemaster_slave, guild_id=member.guild.id, channel_id=before_channel_id) - # If you were in a slave VM VC - if not before.channel.members: - # Nobody else in VC - await before.channel.delete() - DBGatewayActions().delete(vm_slave) - await self.bot.adminLog( - None, - { - "Cog": str(type(self)), - "Message": f"{member.mention} has deleted a VM slave" - }, - guildID=member.guild.id - ) - else: - # Still others in VC - await before.channel.edit(name=f"{before.channel.members[0].display_name}'s VC") - vm_slave.owner_id = before.channel.members[0].id - DBGatewayActions().update(vm_slave) - elif after_channel_id and get_whether_in_vm_master(member.guild.id, after_channel_id): - # Moved into a master VM VC - slave_channel_name = f"{member.display_name}'s VC" - new_slave_channel = await member.guild.create_voice_channel(slave_channel_name, category=after.channel.category) - DBGatewayActions().create( - Voicemaster_slave(guild_id=member.guild.id, - channel_id=new_slave_channel.id, - owner_id=member.id, - locked=False) - ) - await member.move_to(new_slave_channel) - await self.bot.adminLog( - None, - { - "Cog": str(type(self)), - "Message": f"{member.mention} has created a VM slave" - }, - guildID=member.guild.id - ) + if not before.channel and not after.channel: + return + + if before.channel: + # The user has either disconnected or moved voice channels. + if get_whether_in_vm_slave(before.channel.guild.id, before.channel.id): + # If the user was in a VM slave. + vm_slave = DBGatewayActions().get(Voicemaster_slave, guild_id=member.guild.id, channel_id=before.channel.id) + if not before.channel.members: + # The VM is empty, delete it. + await before.channel.delete() + DBGatewayActions().delete(vm_slave) + elif vm_slave.owner_id == member.id: + # It was the owner of the channel that left, transfer ownership. + await before.channel.edit(name=f"{before.channel.members[0].display_name}'s VC") + vm_slave.owner_id = before.channel.members[0].id + DBGatewayActions().update(vm_slave) + + if after.channel and get_whether_in_vm_master(after.channel.guild.id, after.channel.id): + slave_channel = await member.guild.create_voice_channel(f"{member.display_name}'s VC", category=after.channel.category) + slave_db_entry = Voicemaster_slave(guild_id=member.guild.id, channel_id=slave_channel.id, owner_id=member.id, locked=False) + DBGatewayActions().create(slave_db_entry) + await member.move_to(slave_channel) @commands.command( name="setvmmaster",