From 2ae4379cd2c78ae1bd09a1c4a1de54bd4bd7fd4e Mon Sep 17 00:00:00 2001 From: Conatum Date: Sat, 14 Oct 2023 16:09:35 +0300 Subject: [PATCH] feat(ranks): Implement event logging. --- src/core/lion_guild.py | 21 ++++- src/modules/ranks/cog.py | 187 ++++++++++++++++++++++++++++++--------- 2 files changed, 165 insertions(+), 43 deletions(-) diff --git a/src/core/lion_guild.py b/src/core/lion_guild.py index f68618b8..1912c24f 100644 --- a/src/core/lion_guild.py +++ b/src/core/lion_guild.py @@ -254,6 +254,7 @@ class LionGuild(Timezoned): *, embed: Optional[discord.Embed] = None, fields: dict[str, tuple[str, bool]]={}, + errors: list[str]=[], **kwargs: str | int): """ Synchronously log an event to the guild event log. @@ -273,6 +274,9 @@ class LionGuild(Timezoned): May be used to completely customise log message. fields: dict[str, tuple[str, bool]] Optional embed fields to add. + errors: list[str] + Optional list of errors to add. + Errors will always be added last. kwargs: str | int Optional embed fields to add to the embed. These differ from `fields` in that the kwargs keys will be automatically matched and localised @@ -282,7 +286,12 @@ class LionGuild(Timezoned): t = self.bot.translator.t # Build embed - base = embed if embed is not None else discord.Embed(colour=discord.Colour.dark_orange()) + if embed is not None: + base = embed + else: + base = discord.Embed( + colour=(discord.Colour.brand_red() if errors else discord.Colour.dark_orange()) + ) if description is not None: base.description = description if title is not None: @@ -317,6 +326,16 @@ class LionGuild(Timezoned): inline=inline, ) + if errors: + error_name = t(_p( + 'eventlog|field:errors|name', + "Errors" + )) + error_value = '\n'.join(f"- {line}" for line in errors) + base.add_field( + name=error_name, value=error_value, inline=False + ) + # Send embed task = asyncio.create_task(self._log_event(embed=base), name='event-log') self._tasks.add(task) diff --git a/src/modules/ranks/cog.py b/src/modules/ranks/cog.py index 4940d249..7d1da652 100644 --- a/src/modules/ranks/cog.py +++ b/src/modules/ranks/cog.py @@ -319,10 +319,15 @@ class RankCog(LionCog): if roleid in rank_roleids and roleid != current_roleid ] + t = self.bot.translator.t + log_errors: list[str] = [] + log_added = None + log_removed = None + # Now update roles new_last_roleid = last_roleid - # TODO: Event log here, including errors + # TODO: Factor out role updates to_rm = [role for role in to_rm if role.is_assignable()] if to_rm: try: @@ -336,32 +341,68 @@ class RankCog(LionCog): f"Removed old rank roles from in : {roleids}" ) new_last_roleid = None - except discord.HTTPException: + except discord.HTTPException as e: logger.warning( f"Unexpected error removing old rank roles from in : {to_rm}", exc_info=True ) + log_errors.append(t(_p( + 'eventlog|event:rank_check|error:remove_failed', + "Failed to remove old rank roles: `{error}`" + )).format(error=str(e))) + log_removed = '\n'.join(role.mention for role in to_rm) - if to_add and to_add.is_assignable(): - try: - await member.add_roles( - to_add, - reason="Rewarding Activity Rank", - atomic=True - ) - logger.info( - f"Rewarded rank role to in ." - ) - new_last_roleid = to_add.id - except discord.HTTPException: - logger.warning( - f"Unexpected error giving in their rank role ", - exc_info=True - ) + if to_add: + if to_add.is_assignable(): + try: + await member.add_roles( + to_add, + reason="Rewarding Activity Rank", + atomic=True + ) + logger.info( + f"Rewarded rank role to in ." + ) + last_roleid=to_add.id + except discord.HTTPException as e: + logger.warning( + f"Unexpected error giving in " + f"their rank role ", + exc_info=True + ) + log_errors.append(t(_p( + 'eventlog|event:rank_check|error:add_failed', + "Failed to add new rank role: `{error}`" + )).format(error=str(e))) + else: + log_errors.append(t(_p( + 'eventlog|event:rank_check|error:add_impossible', + "Could not assign new activity rank role. Lacking permissions or invalid role." + ))) + log_added = to_add.mention + else: + log_errors.append(t(_p( + 'eventlog|event:rank_check|error:permissions', + "Could not update activity rank roles, I lack the 'Manage Roles' permission." + ))) if new_last_roleid != last_roleid: await session_rank.rankrow.update(last_roleid=new_last_roleid) + if to_add or to_rm: + # Log rank role update + lguild = await self.bot.core.lions.fetch_guild(guildid) + lguild.log_event( + t(_p( + 'eventlog|event:rank_check|name', + "Member Activity Rank Roles Updated" + )), + memberid=member.id, + roles_given=log_added, + roles_taken=log_removed, + errors=log_errors, + ) + @log_wrap(action="Update Rank") async def update_rank(self, session_rank): # Identify target rank @@ -390,6 +431,11 @@ class RankCog(LionCog): if member is None: return + t = self.bot.translator.t + log_errors: list[str] = [] + log_added = None + log_removed = None + last_roleid = session_rank.rankrow.last_roleid # Update ranks @@ -409,7 +455,6 @@ class RankCog(LionCog): ] # Now update roles - # TODO: Event log here, including errors to_rm = [role for role in to_rm if role.is_assignable()] if to_rm: try: @@ -423,28 +468,50 @@ class RankCog(LionCog): f"Removed old rank roles from in : {roleids}" ) last_roleid = None - except discord.HTTPException: + except discord.HTTPException as e: logger.warning( f"Unexpected error removing old rank roles from in : {to_rm}", exc_info=True ) + log_errors.append(t(_p( + 'eventlog|event:new_rank|error:remove_failed', + "Failed to remove old rank roles: `{error}`" + )).format(error=str(e))) + log_removed = '\n'.join(role.mention for role in to_rm) - if to_add and to_add.is_assignable(): - try: - await member.add_roles( - to_add, - reason="Rewarding Activity Rank", - atomic=True - ) - logger.info( - f"Rewarded rank role to in ." - ) - last_roleid=to_add.id - except discord.HTTPException: - logger.warning( - f"Unexpected error giving in their rank role ", - exc_info=True - ) + if to_add: + if to_add.is_assignable(): + try: + await member.add_roles( + to_add, + reason="Rewarding Activity Rank", + atomic=True + ) + logger.info( + f"Rewarded rank role to in ." + ) + last_roleid=to_add.id + except discord.HTTPException as e: + logger.warning( + f"Unexpected error giving in " + f"their rank role ", + exc_info=True + ) + log_errors.append(t(_p( + 'eventlog|event:new_rank|error:add_failed', + "Failed to add new rank role: `{error}`" + )).format(error=str(e))) + else: + log_errors.append(t(_p( + 'eventlog|event:new_rank|error:add_impossible', + "Could not assign new activity rank role. Lacking permissions or invalid role." + ))) + log_added = to_add.mention + else: + log_errors.append(t(_p( + 'eventlog|event:new_rank|error:permissions', + "Could not update activity rank roles, I lack the 'Manage Roles' permission." + ))) # Update MemberRank row column = { @@ -473,7 +540,29 @@ class RankCog(LionCog): ) # Send notification - await self._notify_rank_update(guildid, userid, new_rank) + try: + await self._notify_rank_update(guildid, userid, new_rank) + except discord.HTTPException: + log_errors.append(t(_p( + 'eventlog|event:new_rank|error:notify_failed', + "Could not notify member." + ))) + + # Log rank achieved + lguild.log_event( + t(_p( + 'eventlog|event:new_rank|name', + "Member Achieved Activity rank" + )), + t(_p( + 'eventlog|event:new_rank|desc', + "{member} earned the new activity rank {rank}" + )).format(member=member.mention, rank=f"<@&{new_rank.roleid}>"), + roles_given=log_added, + roles_taken=log_removed, + coins_earned=new_rank.reward, + errors=log_errors, + ) async def _notify_rank_update(self, guildid, userid, new_rank): """ @@ -516,11 +605,7 @@ class RankCog(LionCog): text = member.mention # Post! - try: - await destination.send(embed=embed, content=text) - except discord.HTTPException: - # TODO: Logging, guild logging, invalidate channel if permissions are wrong - pass + await destination.send(embed=embed, content=text) def get_message_map(self, rank_type: RankType, @@ -777,6 +862,24 @@ class RankCog(LionCog): self.flush_guild_ranks(guild.id) await ui.set_done() + # Event log + lguild.log_event( + t(_p( + 'eventlog|event:rank_refresh|name', + "Activity Ranks Refreshed" + )), + t(_p( + 'eventlog|event:rank_refresh|desc', + "{actor} refresh member activity ranks.\n" + "**`{removed}`** invalid rank roles removed.\n" + "**`{added}`** new rank roles added." + )).format( + actor=interaction.user.mention, + removed=ui.removed, + added=ui.added, + ) + ) + # ---------- Commands ---------- @cmds.hybrid_command(name=_p('cmd:ranks', "ranks")) async def ranks_cmd(self, ctx: LionContext):