From 7429e42892434623a8e710c010a4fa3186d5e3f7 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:34:40 +0100 Subject: [PATCH 1/7] refactor(onboarding_role): abstract guild onboarding to own method --- onboarding_role/onboarding_role.py | 55 +++++++++++++++++++----------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/onboarding_role/onboarding_role.py b/onboarding_role/onboarding_role.py index 3f92e79d..0338072e 100644 --- a/onboarding_role/onboarding_role.py +++ b/onboarding_role/onboarding_role.py @@ -54,30 +54,47 @@ async def on_member_update(self, before: discord.Member, after: discord.Member): async def on_ready(self): """ Listen for on_ready event. - When event fires, add onboarded role to members in all guilds who meet the following criteria: - - Not in `onboarded_users` list. - - Has completed onboarding. - - Does not have the onboarded role. + When event fires, process onboarding for all eligible members. """ # Wait until Red is fully ready and cache is populated await self.bot.wait_until_red_ready() for guild in self.bot.guilds: - onboarded_role_id = await self.config.guild(guild).role() - if not onboarded_role_id: - # No role configured for this guild - continue - - onboarded_role = guild.get_role(onboarded_role_id) - if not onboarded_role: - # Role not found - log.warning(f"Role ID {onboarded_role_id} not found in guild {guild.name} (ID {guild.id}).") - continue - - onboarded_users = await self.config.guild(guild).onboarded_users() - for member in guild.members: - if member not in onboarded_users and member.flags.completed_onboarding and onboarded_role not in member.roles: - await self.handle_onboarding(member) + await self.process_onboarding_for_guild(guild) + + async def process_onboarding_for_guild(self, guild: discord.Guild) -> int: + """ + Process onboarding for members in a specific guild who meet the following criteria: + - Not in `onboarded_users` list. + - Has completed onboarding. + - Does not have the onboarded role. + + Args: + guild: The Discord guild to process onboarding for. + + Returns: + int: Number of members processed. + """ + onboarded_role_id = await self.config.guild(guild).role() + if not onboarded_role_id: + # No role configured for this guild + return 0 + + onboarded_role = guild.get_role(onboarded_role_id) + if not onboarded_role: + # Role not found + log.warning(f"Role ID {onboarded_role_id} not found in guild {guild.name} (ID {guild.id}).") + return 0 + + onboarded_users = await self.config.guild(guild).onboarded_users() + processed_count = 0 + + for member in guild.members: + if member not in onboarded_users and member.flags.completed_onboarding and onboarded_role not in member.roles: + await self.handle_onboarding(member) + processed_count += 1 + + return processed_count # Commands From fc6c714d5fa714e264ee0e36537094081f7e15f3 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:35:16 +0100 Subject: [PATCH 2/7] feat(onboarding_role): add manual onboarding command Also adds message when role is set to inform the user of the behaviour. --- onboarding_role/onboarding_role.py | 94 ++++++++++++++++++------------ 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/onboarding_role/onboarding_role.py b/onboarding_role/onboarding_role.py index 0338072e..2f75684c 100644 --- a/onboarding_role/onboarding_role.py +++ b/onboarding_role/onboarding_role.py @@ -48,7 +48,7 @@ async def on_member_update(self, before: discord.Member, after: discord.Member): # Onboarding state is not changed or onboarding is not complete return - await self.handle_onboarding(after) + await self.process_onboarding_for_member(after) @commands.Cog.listener() async def on_ready(self): @@ -62,40 +62,6 @@ async def on_ready(self): for guild in self.bot.guilds: await self.process_onboarding_for_guild(guild) - async def process_onboarding_for_guild(self, guild: discord.Guild) -> int: - """ - Process onboarding for members in a specific guild who meet the following criteria: - - Not in `onboarded_users` list. - - Has completed onboarding. - - Does not have the onboarded role. - - Args: - guild: The Discord guild to process onboarding for. - - Returns: - int: Number of members processed. - """ - onboarded_role_id = await self.config.guild(guild).role() - if not onboarded_role_id: - # No role configured for this guild - return 0 - - onboarded_role = guild.get_role(onboarded_role_id) - if not onboarded_role: - # Role not found - log.warning(f"Role ID {onboarded_role_id} not found in guild {guild.name} (ID {guild.id}).") - return 0 - - onboarded_users = await self.config.guild(guild).onboarded_users() - processed_count = 0 - - for member in guild.members: - if member not in onboarded_users and member.flags.completed_onboarding and onboarded_role not in member.roles: - await self.handle_onboarding(member) - processed_count += 1 - - return processed_count - # Commands @commands.group() # type: ignore @@ -153,6 +119,10 @@ async def set_role(self, ctx: commands.GuildContext, role: discord.Role): await self.config.guild(ctx.guild).role.set(role.id) log.debug(f"Onboarded role set to {role.name} (ID {role.id})") await ctx.tick() + await ctx.send( + f"Onboarding role has been set to {role.mention}. All eligible members will be assigned this role " + f"next time the bot starts, or it can be triggered now with `{ctx.prefix}onboarding_role process`." + ) @onboarding_role.command("logchannel") async def set_log_channel(self, ctx: commands.GuildContext, channel: discord.TextChannel): @@ -170,9 +140,61 @@ async def set_log_channel(self, ctx: commands.GuildContext, channel: discord.Tex else: await ctx.send(f"❌ I need the `Send Messages` and `Embed Links` permissions to send logs to {channel.mention}.") + @onboarding_role.command("process") + async def manual_onboarding(self, ctx: commands.GuildContext): + """ + Manually process onboarding for all eligible members in this guild. + + This will check all members in the guild and assign the onboarding role + to those who have completed onboarding but don't have the role yet. + """ + async with ctx.typing(): + processed_count = await self.process_onboarding_for_guild(ctx.guild) + + if processed_count == 0: + await ctx.send("✅ No members needed onboarding role assignment.") + elif processed_count == 1: + await ctx.send("✅ Processed onboarding for 1 member.") + else: + await ctx.send(f"✅ Processed onboarding for {processed_count} members.") + # Helpers - async def handle_onboarding(self, member: discord.Member): + async def process_onboarding_for_guild(self, guild: discord.Guild) -> int: + """ + Process onboarding for members in a specific guild who meet the following criteria: + - Not in `onboarded_users` list. + - Has completed onboarding. + - Does not have the onboarded role. + + Args: + guild: The Discord guild to process onboarding for. + + Returns: + int: Number of members processed. + """ + onboarded_role_id = await self.config.guild(guild).role() + if not onboarded_role_id: + # No role configured for this guild + return 0 + + onboarded_role = guild.get_role(onboarded_role_id) + if not onboarded_role: + # Role not found + log.warning(f"Role ID {onboarded_role_id} not found in guild {guild.name} (ID {guild.id}).") + return 0 + + onboarded_users = await self.config.guild(guild).onboarded_users() + processed_count = 0 + + for member in guild.members: + if member not in onboarded_users and member.flags.completed_onboarding and onboarded_role not in member.roles: + await self.process_onboarding_for_member(member) + processed_count += 1 + + return processed_count + + async def process_onboarding_for_member(self, member: discord.Member): """Handle onboarding completed event""" log.debug(f"User '{member.name}' (ID {member.id}) completed onboarding") guild = member.guild From a6b75346107826e7154c69be0f0bb5aba4740f1d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:57:50 +0100 Subject: [PATCH 3/7] feat(onboarding_role): add dry run option to `process` cmd --- onboarding_role/onboarding_role.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/onboarding_role/onboarding_role.py b/onboarding_role/onboarding_role.py index 2f75684c..f434699b 100644 --- a/onboarding_role/onboarding_role.py +++ b/onboarding_role/onboarding_role.py @@ -141,17 +141,27 @@ async def set_log_channel(self, ctx: commands.GuildContext, channel: discord.Tex await ctx.send(f"❌ I need the `Send Messages` and `Embed Links` permissions to send logs to {channel.mention}.") @onboarding_role.command("process") - async def manual_onboarding(self, ctx: commands.GuildContext): + async def manual_onboarding(self, ctx: commands.GuildContext, dry_run: bool = False): """ Manually process onboarding for all eligible members in this guild. This will check all members in the guild and assign the onboarding role to those who have completed onboarding but don't have the role yet. + + Args: + dry_run: If True, only show what would be done without making changes. """ async with ctx.typing(): - processed_count = await self.process_onboarding_for_guild(ctx.guild) + processed_count = await self.process_onboarding_for_guild(ctx.guild, dry_run=dry_run) - if processed_count == 0: + if dry_run: + if processed_count == 0: + await ctx.send("🔍 **Dry Run**: No members would need onboarding role assignment.") + elif processed_count == 1: + await ctx.send("🔍 **Dry Run**: 1 member would be processed for onboarding role assignment.") + else: + await ctx.send(f"🔍 **Dry Run**: {processed_count} members would be processed for onboarding role assignment.") + elif processed_count == 0: await ctx.send("✅ No members needed onboarding role assignment.") elif processed_count == 1: await ctx.send("✅ Processed onboarding for 1 member.") @@ -160,7 +170,7 @@ async def manual_onboarding(self, ctx: commands.GuildContext): # Helpers - async def process_onboarding_for_guild(self, guild: discord.Guild) -> int: + async def process_onboarding_for_guild(self, guild: discord.Guild, dry_run: bool = False) -> int: """ Process onboarding for members in a specific guild who meet the following criteria: - Not in `onboarded_users` list. @@ -169,9 +179,10 @@ async def process_onboarding_for_guild(self, guild: discord.Guild) -> int: Args: guild: The Discord guild to process onboarding for. + dry_run: If True, only count eligible members without making changes. Returns: - int: Number of members processed. + int: Number of members processed (or would be processed in dry run). """ onboarded_role_id = await self.config.guild(guild).role() if not onboarded_role_id: @@ -189,7 +200,8 @@ async def process_onboarding_for_guild(self, guild: discord.Guild) -> int: for member in guild.members: if member not in onboarded_users and member.flags.completed_onboarding and onboarded_role not in member.roles: - await self.process_onboarding_for_member(member) + if not dry_run: + await self.process_onboarding_for_member(member) processed_count += 1 return processed_count From f5c29b3b85d39f6158c04ee809d5cadb886b2c19 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 27 Aug 2025 01:57:59 +0100 Subject: [PATCH 4/7] docs: add `onboarding_role`'s `process` command --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ab17bea3..fa938df5 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,7 @@ If any such member is found, they will be granted the onboarding role. * `[p]onboarding_role status` - Status of the cog. * `[p]onboarding_role role ` - Set the role to be granted to users once they complete onboarding. * `[p]onboarding_role logchannel ` - Set the channel to which onboarding events should be logged. +* `[p]onboarding_role process [dry_run]` - Manually process onboarding for all eligible members in the current guild. This checks all members and assigns the onboarding role to those who have completed onboarding but don't have the role yet. Use `True` for `dry_run` to see what would happen without making changes. ### Penis From dac615e545de3531f4fcf518f7eb735a5cccfab4 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 27 Aug 2025 02:10:44 +0100 Subject: [PATCH 5/7] fix(onboarding_role): correct member onboarded check --- onboarding_role/onboarding_role.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onboarding_role/onboarding_role.py b/onboarding_role/onboarding_role.py index f434699b..9d830617 100644 --- a/onboarding_role/onboarding_role.py +++ b/onboarding_role/onboarding_role.py @@ -199,7 +199,7 @@ async def process_onboarding_for_guild(self, guild: discord.Guild, dry_run: bool processed_count = 0 for member in guild.members: - if member not in onboarded_users and member.flags.completed_onboarding and onboarded_role not in member.roles: + if member.id not in onboarded_users and member.flags.completed_onboarding and onboarded_role not in member.roles: if not dry_run: await self.process_onboarding_for_member(member) processed_count += 1 From b4ba83f87a245e5c6e87cd27c9e32ad9732013ba Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 27 Aug 2025 02:12:52 +0100 Subject: [PATCH 6/7] fix(onboarding_role): correct typos --- onboarding_role/onboarding_role.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onboarding_role/onboarding_role.py b/onboarding_role/onboarding_role.py index 9d830617..3eb7d662 100644 --- a/onboarding_role/onboarding_role.py +++ b/onboarding_role/onboarding_role.py @@ -98,7 +98,7 @@ async def get_status(self, ctx: commands.GuildContext): embed = ( discord.Embed(colour=(await ctx.embed_colour())) .add_field(name="Onboarded Role", value=onboarded_role) - .add_field(name="Log Channnel", value=log_channel) + .add_field(name="Log Channel", value=log_channel) .add_field(name="Onboarded User Count", value=num_onboarded_users, inline=False) ) @@ -222,7 +222,7 @@ async def process_onboarding_for_member(self, member: discord.Member): # Welcome role is not found log.warning( f"Cannot grant onboarding role to '{member.name}' (ID {member.id}): " - + f"Onboarding role set to ID {role_id} but could not found." + + f"Onboarding role set to ID {role_id} but could not be found." ) return From d20e824072f8575c76172c3b5db1a0aa16bed2a9 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 27 Aug 2025 02:15:23 +0100 Subject: [PATCH 7/7] refactor(onboarding_role): quote object names in log messages --- onboarding_role/onboarding_role.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/onboarding_role/onboarding_role.py b/onboarding_role/onboarding_role.py index 3eb7d662..f545dbec 100644 --- a/onboarding_role/onboarding_role.py +++ b/onboarding_role/onboarding_role.py @@ -117,7 +117,7 @@ async def set_role(self, ctx: commands.GuildContext, role: discord.Role): - `[p]onboarding_role role 1253932390562590999` """ await self.config.guild(ctx.guild).role.set(role.id) - log.debug(f"Onboarded role set to {role.name} (ID {role.id})") + log.debug(f"Onboarded role set to '{role.name}' (ID {role.id})") await ctx.tick() await ctx.send( f"Onboarding role has been set to {role.mention}. All eligible members will be assigned this role " @@ -135,7 +135,7 @@ async def set_log_channel(self, ctx: commands.GuildContext, channel: discord.Tex """ if channel.permissions_for(ctx.me).send_messages and channel.permissions_for(ctx.me).embed_links: await self.config.guild(ctx.guild).log_channel.set(channel.id) - log.debug(f"Log channel set to {channel.name} (ID {channel.id})") + log.debug(f"Log channel set to '{channel.name}' (ID {channel.id})") await ctx.tick() else: await ctx.send(f"❌ I need the `Send Messages` and `Embed Links` permissions to send logs to {channel.mention}.") @@ -192,7 +192,7 @@ async def process_onboarding_for_guild(self, guild: discord.Guild, dry_run: bool onboarded_role = guild.get_role(onboarded_role_id) if not onboarded_role: # Role not found - log.warning(f"Role ID {onboarded_role_id} not found in guild {guild.name} (ID {guild.id}).") + log.warning(f"Role ID {onboarded_role_id} not found in guild '{guild.name}' (ID {guild.id}).") return 0 onboarded_users = await self.config.guild(guild).onboarded_users() @@ -237,7 +237,7 @@ async def process_onboarding_for_member(self, member: discord.Member): await self.send_log_message(member) except discord.Forbidden: - error_msg = f"Adding onboarding role to {member.name} (ID {member.id}) was forbidden." + error_msg = f"Adding onboarding role to '{member.name}' (ID {member.id}) was forbidden." log.warning(error_msg) await self.send_log_message(member, error_msg) @@ -277,7 +277,7 @@ async def send_log_message(self, member: discord.Member, error_msg: str = ""): try: await log_channel.send(embed=embed) except discord.Forbidden: - log.warning(f"Sending onboarding log to {log_channel.name} (ID {log_channel_id}) was forbidden.") + log.warning(f"Sending onboarding log to '{log_channel.name}' (ID {log_channel_id}) was forbidden.") def humanise_timedelta(delta: timedelta) -> str: