From aef775375153d8a4bcd620adf4e7b536559e126e Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Fri, 23 Oct 2026 02:44:44 +0200 Subject: [PATCH] resolve roles with at most one fetch_roles call per update Previously every role missing from the cache triggered its own fetch_roles call, for every user in the membership check. Co-Authored-By: Claude Opus 5.5 --- bot/src/cogs/galtinn.py | 15 +++++---------- bot/src/cogs/utils/discord_utils.py | 25 +++++++++++++------------ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/bot/src/cogs/galtinn.py b/bot/src/cogs/galtinn.py index 479ea4e..1cc3a16 100644 --- a/bot/src/cogs/galtinn.py +++ b/bot/src/cogs/galtinn.py @@ -465,16 +465,11 @@ class Galtinn(commands.Cog): requested_roles_to_add = roles_to_add requested_roles_to_remove = roles_to_remove - roles_to_add = { - role - for role_id in requested_roles_to_add - if (role := await discord_utils.get_guild_role(self.bot, guild, role_id)) - } - roles_to_remove = { - role - for role_id in requested_roles_to_remove - if (role := await discord_utils.get_guild_role(self.bot, guild, role_id)) - } + guild_roles = await discord_utils.get_guild_roles( + self.bot, guild, requested_roles_to_add | requested_roles_to_remove + ) + roles_to_add = {guild_roles[role_id] for role_id in requested_roles_to_add if role_id in guild_roles} + roles_to_remove = {guild_roles[role_id] for role_id in requested_roles_to_remove if role_id in guild_roles} # Problematic roles are skipped rather than aborting, so that one misconfigured role # doesn't stop every other role from being updated diff --git a/bot/src/cogs/utils/discord_utils.py b/bot/src/cogs/utils/discord_utils.py index 867890c..b27ef50 100644 --- a/bot/src/cogs/utils/discord_utils.py +++ b/bot/src/cogs/utils/discord_utils.py @@ -93,32 +93,33 @@ async def get_guild_member(bot: commands.Bot, guild: discord.Guild, user_id: int return user -async def get_guild_role(bot: commands.Bot, guild: discord.Guild, role_id: int) -> discord.Role | None: +async def get_guild_roles(bot: commands.Bot, guild: discord.Guild, role_ids: set[int]) -> dict[int, discord.Role]: """ - Helper function that tries fetching a discord object from the cache. If not found, fetches from the API + Helper function that tries fetching discord roles from the cache. + If any are not found, all roles are fetched from the API in a single call Parameters ---------- bot (commands.Bot): Bot instance - guild (discord.Guild): The discord guild to fetch the role from - role_id (int): ID of the role + guild (discord.Guild): The discord guild to fetch the roles from + role_ids (set[int]): IDs of the roles Returns ---------- - (discord.Role | None): Discord object. None if not found or failed to fetch from API + (dict[int, discord.Role]): Discord objects by ID. Roles that were not found or failed to fetch are left out """ - role = guild.get_role(role_id) - if not role: - bot.logger.info(f"failed to fetch role {role_id} from cache. Making API call...") + roles = {role_id: role for role_id in role_ids if (role := guild.get_role(role_id))} + if missing_role_ids := role_ids - roles.keys(): + bot.logger.info(f"failed to fetch roles {missing_role_ids} from cache. Making API call...") try: - roles = await guild.fetch_roles() + fetched_roles = await guild.fetch_roles() except discord.errors.HTTPException: - bot.logger.warning(f"Failed to fetch role with ID {role_id}. HTTPException") + bot.logger.warning(f"Failed to fetch roles with IDs {missing_role_ids}. HTTPException") else: - role = discord.utils.get(roles, id=role_id) + roles.update({role.id: role for role in fetched_roles if role.id in missing_role_ids}) - return role + return roles class ScrollerButton(discord.ui.Button):