From 0b7b84556d7e56621ed047ceee60af7371f26131 Mon Sep 17 00:00:00 2001 From: Conatum Date: Tue, 26 Oct 2021 11:42:25 +0300 Subject: [PATCH 01/10] fix (renting): Fix expiry execution order. --- bot/modules/renting/rooms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/modules/renting/rooms.py b/bot/modules/renting/rooms.py index c83adc29..9e79d5b0 100644 --- a/bot/modules/renting/rooms.py +++ b/bot/modules/renting/rooms.py @@ -178,6 +178,8 @@ class Room: """ Expire the room. """ + guild_settings = GuildSettings(self.data.guildid) + if self.channel: # Delete the discord channel try: @@ -188,7 +190,6 @@ class Room: # Delete the room from data (cascades to member deletion) self.delete() - guild_settings = GuildSettings(self.data.guildid) guild_settings.event_log.log( title="Private study room expired!", description="<@{}>'s private study room expired.".format(self.data.ownerid) From 10f048fabca05a3348ed597bdc72d691b34aaaa1 Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 03:16:56 +0200 Subject: [PATCH 02/10] fix (rooms): Avoid loading rooms in dead guilds. --- bot/modules/accountability/TimeSlot.py | 16 ++++++++++------ bot/modules/accountability/tracker.py | 17 +++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/bot/modules/accountability/TimeSlot.py b/bot/modules/accountability/TimeSlot.py index ac12efea..2a87826a 100644 --- a/bot/modules/accountability/TimeSlot.py +++ b/bot/modules/accountability/TimeSlot.py @@ -218,6 +218,9 @@ class TimeSlot: """ Load data and update applicable caches. """ + if not self.guild: + return self + # Load setting data self.category = GuildSettings(self.guild.id).accountability_category.value self.lobby = GuildSettings(self.guild.id).accountability_lobby.value @@ -389,13 +392,14 @@ class TimeSlot: pass # Reward members appropriately - guild_settings = GuildSettings(self.guild.id) - reward = guild_settings.accountability_reward.value - if all(mem.has_attended for mem in self.members.values()): - reward += guild_settings.accountability_bonus.value + if self.guild: + guild_settings = GuildSettings(self.guild.id) + reward = guild_settings.accountability_reward.value + if all(mem.has_attended for mem in self.members.values()): + reward += guild_settings.accountability_bonus.value - for memid in self.members: - Lion.fetch(self.guild.id, memid).addCoins(reward) + for memid in self.members: + Lion.fetch(self.guild.id, memid).addCoins(reward) async def cancel(self): """ diff --git a/bot/modules/accountability/tracker.py b/bot/modules/accountability/tracker.py index 51713230..24e1dc94 100644 --- a/bot/modules/accountability/tracker.py +++ b/bot/modules/accountability/tracker.py @@ -374,14 +374,15 @@ async def _accountability_system_resume(): None, mow.slotid, mow.userid) for mow in slot_members[row.slotid] if mow.last_joined_at ) - slot = TimeSlot(client.get_guild(row.guildid), row.start_at, data=row).load( - memberids=[mow.userid for mow in slot_members[row.slotid]] - ) + if client.get_guild(row.guildid): + slot = TimeSlot(client.get_guild(row.guildid), row.start_at, data=row).load( + memberids=[mow.userid for mow in slot_members[row.slotid]] + ) + try: + await slot.close() + except discord.HTTPException: + pass row.closed_at = now - try: - await slot.close() - except discord.HTTPException: - pass # Load the in-progress room data if current_room_data: @@ -451,7 +452,7 @@ async def launch_accountability_system(client): guilds = tables.guild_config.fetch_rows_where( accountability_category=NOTNULL ) - [AccountabilityGuild(guild.guildid) for guild in guilds] + [AccountabilityGuild(guild.guildid) for guild in guilds if client.get_guild(guild.guildid)] await _accountability_system_resume() asyncio.create_task(_accountability_loop()) From 0e62ebdb2b54e7f14feede7e63e6225db01e70e4 Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 14:58:57 +0200 Subject: [PATCH 03/10] fix (rroles): Increase maximum role price. --- bot/modules/guild_admin/reaction_roles/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/modules/guild_admin/reaction_roles/settings.py b/bot/modules/guild_admin/reaction_roles/settings.py index b678b804..dbb4b8cf 100644 --- a/bot/modules/guild_admin/reaction_roles/settings.py +++ b/bot/modules/guild_admin/reaction_roles/settings.py @@ -199,6 +199,7 @@ class price(setting_types.Integer, ReactionSetting): ) accepts = "An integer number of coins. Use `0` to make the role free, or `None` to use the message default." + _max = 2 ** 20 @property def default(self): From 4c21160b31eb669df2b420fd861f9740ac3c26b8 Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 15:00:04 +0200 Subject: [PATCH 04/10] fix (rroles): Repair setting error reply. Modified `error_reply` utility so `kwargs` are passed to `Embed`. Added `send_args` kwarg to `error_reply`. Fixed an rroles issue where `UserInputError` handling would fail. --- bot/modules/guild_admin/reaction_roles/command.py | 6 +++--- bot/utils/ctx_addons.py | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bot/modules/guild_admin/reaction_roles/command.py b/bot/modules/guild_admin/reaction_roles/command.py index 61e4a1ed..7f460262 100644 --- a/bot/modules/guild_admin/reaction_roles/command.py +++ b/bot/modules/guild_admin/reaction_roles/command.py @@ -821,8 +821,8 @@ async def cmd_reactionroles(ctx, flags): setting = await setting_class.parse(target.messageid, ctx, flags[flag]) except UserInputError as e: return await ctx.error_reply( - title="Couldn't save settings!", - description="{} {}\nNo settings were modified.".format(cross, e.msg) + "{} {}\nNo settings were modified.".format(cross, e.msg), + title="Couldn't save settings!" ) else: update_lines.append( @@ -861,8 +861,8 @@ async def cmd_reactionroles(ctx, flags): setting = await setting_class.parse(reaction.reactionid, ctx, flags[flag]) except UserInputError as e: return await ctx.error_reply( + "{} {}\nNo reaction roles were modified.".format(cross, e.msg), title="Couldn't save reaction role settings!", - description="{} {}\nNo reaction roles were modified.".format(cross, e.msg) ) else: update_lines.append( diff --git a/bot/utils/ctx_addons.py b/bot/utils/ctx_addons.py index 931422d9..9697eeec 100644 --- a/bot/utils/ctx_addons.py +++ b/bot/utils/ctx_addons.py @@ -26,21 +26,22 @@ async def embed_reply(ctx, desc, colour=discord.Colour.orange(), **kwargs): @Context.util -async def error_reply(ctx, error_str, **kwargs): +async def error_reply(ctx, error_str, send_args={}, **kwargs): """ Notify the user of a user level error. Typically, this will occur in a red embed, posted in the command channel. """ embed = discord.Embed( colour=discord.Colour.red(), - description=error_str + description=error_str, + **kwargs ) message = None try: message = await ctx.ch.send( embed=embed, reference=ctx.msg.to_reference(fail_if_not_exists=False), - **kwargs + **send_args ) ctx.sent_messages.append(message) return message From db6cc078db4d36ba7af2b25208cc1bdab215819a Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 15:05:05 +0200 Subject: [PATCH 05/10] fix (rroles): Off by one error in `maximum`. --- bot/modules/guild_admin/reaction_roles/tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/modules/guild_admin/reaction_roles/tracker.py b/bot/modules/guild_admin/reaction_roles/tracker.py index 6d130263..f18e3c34 100644 --- a/bot/modules/guild_admin/reaction_roles/tracker.py +++ b/bot/modules/guild_admin/reaction_roles/tracker.py @@ -272,7 +272,7 @@ class ReactionRoleMessage: # Fetch the number of applicable roles the user has roleids = set(reaction.data.roleid for reaction in self.reactions) member_roleids = set(role.id for role in member.roles) - if len(roleids.intersection(member_roleids)) > maximum: + if len(roleids.intersection(member_roleids)) >= maximum: # Notify the user embed = discord.Embed( title="Maximum group roles reached!", From e797b67c3b12375d519791197fcb82ff40aa3680 Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 15:09:06 +0200 Subject: [PATCH 06/10] fix (rooms): Remove `speak` permission from room. --- bot/modules/accountability/TimeSlot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/modules/accountability/TimeSlot.py b/bot/modules/accountability/TimeSlot.py index 2a87826a..9464e4e5 100644 --- a/bot/modules/accountability/TimeSlot.py +++ b/bot/modules/accountability/TimeSlot.py @@ -69,7 +69,8 @@ class TimeSlot: _everyone_overwrite = discord.PermissionOverwrite( view_channel=False, - connect=False + connect=False, + speak=False ) happy_lion = "https://media.discordapp.net/stickers/898266283559227422.png" From 0fbf7c8903fac61edfe9a3febb5a3635bff2424e Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 15:23:14 +0200 Subject: [PATCH 07/10] fix (seekers): Consider news channels as text. Non-canonical hack to `find_channel` to include `news` types in `text`. --- bot/modules/guild_admin/reaction_roles/command.py | 2 +- bot/utils/seekers.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bot/modules/guild_admin/reaction_roles/command.py b/bot/modules/guild_admin/reaction_roles/command.py index 7f460262..0f915ffa 100644 --- a/bot/modules/guild_admin/reaction_roles/command.py +++ b/bot/modules/guild_admin/reaction_roles/command.py @@ -485,7 +485,7 @@ async def cmd_reactionroles(ctx, flags): await ctx.error_reply( "The provided channel no longer exists!" ) - elif channel.type != discord.ChannelType.text: + elif not isinstance(channel, discord.TextChannel): await ctx.error_reply( "The provided channel is not a text channel!" ) diff --git a/bot/utils/seekers.py b/bot/utils/seekers.py index 69bb1f46..7f3d49d3 100644 --- a/bot/utils/seekers.py +++ b/bot/utils/seekers.py @@ -182,7 +182,11 @@ async def find_channel(ctx, userstr, interactive=False, collection=None, chan_ty # Create the collection to search from args or guild channels collection = collection if collection else ctx.guild.channels if chan_type is not None: - collection = [chan for chan in collection if chan.type == chan_type] + if chan_type == discord.ChannelType.text: + # Hack to support news channels as text channels + collection = [chan for chan in collection if isinstance(chan, discord.TextChannel)] + else: + collection = [chan for chan in collection if chan.type == chan_type] # If the user input was a number or possible channel mention, extract it chanid = userstr.strip('<#@&!>') @@ -413,7 +417,7 @@ async def find_message(ctx, msgid, chlist=None, ignore=[]): async def _search_in_channel(channel: discord.TextChannel, msgid: int): - if channel.type != discord.ChannelType.text: + if not isinstance(channel, discord.TextChannel): return try: message = await channel.fetch_message(msgid) From 0b5be79b6954f6615fca7e06ce52d76164b2bfda Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 16:12:35 +0200 Subject: [PATCH 08/10] (LionModule): Improve startup handling. Update `cmdClient` pointer for module launch updates. Implement module launch wait logic in `pre_command`. Add details to `SafeCancellation` calls in `pre_command`. --- bot/LionModule.py | 25 +++++++++++++++++++------ bot/cmdClient | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/bot/LionModule.py b/bot/LionModule.py index 2d6fb854..c1fd7256 100644 --- a/bot/LionModule.py +++ b/bot/LionModule.py @@ -13,7 +13,7 @@ class LionCommand(Command): """ Subclass to allow easy attachment of custom hooks and structure to commands. """ - ... + allow_before_ready = False class LionModule(Module): @@ -72,25 +72,38 @@ class LionModule(Module): """ Lion pre-command hook. """ + if not self.ready and not ctx.cmd.allow_before_ready: + try: + await ctx.embed_reply( + "I am currently restarting! Please try again in a couple of minutes." + ) + except discord.HTTPException: + pass + raise SafeCancellation(details="Module '{}' is not ready.".format(self.name)) + + # Check that the channel and guild still exists + if not ctx.client.get_guild(ctx.guild.id) or not ctx.guild.get_channel(ctx.ch.id): + raise SafeCancellation(details='Command channel is no longer reachable.') + # Check global user blacklist if ctx.author.id in ctx.client.objects['blacklisted_users']: - raise SafeCancellation + raise SafeCancellation(details='User is blacklisted.') if ctx.guild: # Check global guild blacklist if ctx.guild.id in ctx.client.objects['blacklisted_guilds']: - raise SafeCancellation + raise SafeCancellation(details='Guild is blacklisted.') # Check guild's own member blacklist if ctx.author.id in ctx.client.objects['ignored_members'][ctx.guild.id]: - raise SafeCancellation + raise SafeCancellation(details='User is ignored in this guild.') # Check channel permissions are sane if not ctx.ch.permissions_for(ctx.guild.me).send_messages: - raise SafeCancellation + raise SafeCancellation(details='I cannot send messages in this channel.') if not ctx.ch.permissions_for(ctx.guild.me).embed_links: await ctx.reply("I need permission to send embeds in this channel before I can run any commands!") - raise SafeCancellation + raise SafeCancellation(details='I cannot send embeds in this channel.') # Start typing await ctx.ch.trigger_typing() diff --git a/bot/cmdClient b/bot/cmdClient index 75410acc..6eb42690 160000 --- a/bot/cmdClient +++ b/bot/cmdClient @@ -1 +1 @@ -Subproject commit 75410acc120b456ff315ff468357b6fb22a0a406 +Subproject commit 6eb426903423d6be8439621eb0b906aa94957efd From fc3246913fb9c1a6be285af9b2eb906438dac90b Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 16:27:38 +0200 Subject: [PATCH 09/10] fix (core): Update `guild_config` definition. Increase the cache size, and update the columns. --- bot/core/data.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bot/core/data.py b/bot/core/data.py index c33914d7..992e9ab5 100644 --- a/bot/core/data.py +++ b/bot/core/data.py @@ -53,13 +53,19 @@ def add_pending(pending): guild_config = RowTable( 'guild_config', - ('guildid', 'admin_role', 'mod_role', 'event_log_channel', 'alert_channel', + ('guildid', 'admin_role', 'mod_role', 'event_log_channel', 'mod_log_channel', 'alert_channel', + 'studyban_role', 'min_workout_length', 'workout_reward', 'max_tasks', 'task_reward', 'task_reward_limit', 'study_hourly_reward', 'study_hourly_live_bonus', - 'study_ban_role', 'max_study_bans'), + 'renting_price', 'renting_category', 'renting_cap', 'renting_role', 'renting_sync_perms', + 'accountability_category', 'accountability_lobby', 'accountability_bonus', + 'accountability_reward', 'accountability_price', + 'video_studyban', 'video_grace_period', + 'greeting_channel', 'greeting_message', 'returning_message', + 'starting_funds', 'persist_roles'), 'guildid', - cache=TTLCache(1000, ttl=60*5) + cache=TTLCache(2500, ttl=60*5) ) unranked_roles = Table('unranked_roles') @@ -72,6 +78,7 @@ lions = RowTable( ('guildid', 'userid', 'tracked_time', 'coins', 'workout_count', 'last_workout_start', + 'revision_mute_count', 'last_study_badgeid', 'video_warned', '_timestamp' From 2cf66ab6004ae347f9a85dd572e43632eeb5f2d7 Mon Sep 17 00:00:00 2001 From: Conatum Date: Sun, 7 Nov 2021 16:31:55 +0200 Subject: [PATCH 10/10] (stats_cmd): Allow execution before ready. --- bot/modules/study/stats_cmd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/modules/study/stats_cmd.py b/bot/modules/study/stats_cmd.py index 90768202..73604658 100644 --- a/bot/modules/study/stats_cmd.py +++ b/bot/modules/study/stats_cmd.py @@ -12,7 +12,8 @@ from .module import module @module.cmd( "stats", group="Statistics", - desc="View a summary of your study statistics!" + desc="View a summary of your study statistics!", + allow_before_ready=True ) @in_guild() async def cmd_stats(ctx):