From 07e81b36a54f7e4c110146c1b80851e62a133840 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:56:18 +0000 Subject: [PATCH 1/5] Add User in Guild check for inductions --- cogs/induct.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cogs/induct.py b/cogs/induct.py index dd52a9380..ca39aa05e 100644 --- a/cogs/induct.py +++ b/cogs/induct.py @@ -366,6 +366,17 @@ async def non_silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role. """ + if not self.bot.main_guild.get_member(member.id): + await ctx.respond( + ( + ":information_source: No changes made. User cannot be inducted " + "because they have left the server " + ":information_source:" + ), + ephemeral=True, + ) + return + await self._perform_induction(ctx, member, silent=False) @discord.user_command(name="Silently Induct User") @@ -382,6 +393,17 @@ async def silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role, only without broadcasting a welcome message. """ + if not self.bot.main_guild.get_member(member.id): + await ctx.respond( + ( + ":information_source: No changes made. User cannot be inducted " + "because they have left the server " + ":information_source:" + ), + ephemeral=True, + ) + return + await self._perform_induction(ctx, member, silent=True) @discord.message_command(name="Induct Message Author") From 6c81e496a94fcdcd443f59ff53ace90c5a41728b Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:33:43 +0000 Subject: [PATCH 2/5] Fix type hinting --- cogs/induct.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cogs/induct.py b/cogs/induct.py index ca39aa05e..834f11a85 100644 --- a/cogs/induct.py +++ b/cogs/induct.py @@ -356,7 +356,7 @@ class InductContextCommandsCog(BaseInductCog): @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild async def non_silent_user_induct( - self, ctx: "TeXBotApplicationContext", member: discord.Member + self, ctx: "TeXBotApplicationContext", member: discord.Member | discord.User ) -> None: """ Definition & callback response of the "non_silent_induct" user-context-command. @@ -366,7 +366,8 @@ async def non_silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role. """ - if not self.bot.main_guild.get_member(member.id): + main_guild_member: discord.Member | None = self.bot.main_guild.get_member(member.id) + if not main_guild_member: await ctx.respond( ( ":information_source: No changes made. User cannot be inducted " @@ -377,13 +378,13 @@ async def non_silent_user_induct( ) return - await self._perform_induction(ctx, member, silent=False) + await self._perform_induction(ctx, main_guild_member, silent=False) @discord.user_command(name="Silently Induct User") @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild async def silent_user_induct( - self, ctx: "TeXBotApplicationContext", member: discord.Member + self, ctx: "TeXBotApplicationContext", member: discord.Member | discord.User ) -> None: """ Definition & callback response of the "silent_induct" user-context-command. @@ -393,7 +394,8 @@ async def silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role, only without broadcasting a welcome message. """ - if not self.bot.main_guild.get_member(member.id): + main_guild_member: discord.Member | None = self.bot.main_guild.get_member(member.id) + if not main_guild_member: await ctx.respond( ( ":information_source: No changes made. User cannot be inducted " @@ -404,7 +406,7 @@ async def silent_user_induct( ) return - await self._perform_induction(ctx, member, silent=True) + await self._perform_induction(ctx, main_guild_member, silent=True) @discord.message_command(name="Induct Message Author") @CommandChecks.check_interaction_user_has_committee_role From 02ec97ab1f6cd6b2148cd91ed7ef7f8b975b20aa Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:42:05 +0000 Subject: [PATCH 3/5] Refactor --- cogs/induct.py | 62 ++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/cogs/induct.py b/cogs/induct.py index 834f11a85..72c9d61f1 100644 --- a/cogs/induct.py +++ b/cogs/induct.py @@ -179,7 +179,7 @@ async def get_random_welcome_message( async def _perform_induction( self, ctx: "TeXBotApplicationContext", - induction_member: discord.Member, + induction_member: discord.Member | discord.User, *, silent: bool, ) -> None: @@ -188,9 +188,21 @@ async def _perform_induction( main_guild: discord.Guild = self.bot.main_guild guest_role: discord.Role = await self.bot.guest_role + main_guild_member: discord.Member | None = main_guild.get_member(induction_member.id) + if not main_guild_member: + await ctx.respond( + ( + ":information_source: No changes made. User cannot be inducted " + "because they have left the server " + ":information_source:" + ), + ephemeral=True, + ) + return + await ctx.defer(ephemeral=True) async with ctx.typing(): - logger.debug("Inducting member %s, silent=%s", induction_member, silent) + logger.debug("Inducting member %s, silent=%s", main_guild_member, silent) INDUCT_AUDIT_MESSAGE: Final[str] = ( f'{ctx.user} used TeX Bot slash-command: "/induct"' @@ -200,13 +212,13 @@ async def _perform_induction( main_guild.text_channels, name="introductions" ) - if induction_member.bot: + if main_guild_member.bot: await self.command_send_error( ctx, message="Member cannot be inducted because they are a bot." ) return - if guest_role in induction_member.roles: + if guest_role in main_guild_member.roles: await ctx.respond( content=( ":information_source: No changes made. " @@ -218,25 +230,25 @@ async def _perform_induction( if not silent: await (await self.bot.general_channel).send( - f"{await self.get_random_welcome_message(induction_member)} :tada:\n" + f"{await self.get_random_welcome_message(main_guild_member)} :tada:\n" f"Remember to grab your roles in { await self.bot.get_mention_string(self.bot.roles_channel) } and say hello to everyone here! :wave:" ) - await induction_member.add_roles(guest_role, reason=INDUCT_AUDIT_MESSAGE) + await main_guild_member.add_roles(guest_role, reason=INDUCT_AUDIT_MESSAGE) news_role: discord.Role | None = discord.utils.get(main_guild.roles, name="News") - if news_role and news_role not in induction_member.roles: - await induction_member.add_roles(news_role, reason=INDUCT_AUDIT_MESSAGE) + if news_role and news_role not in main_guild_member.roles: + await main_guild_member.add_roles(news_role, reason=INDUCT_AUDIT_MESSAGE) try: applicant_role: discord.Role = await ctx.bot.applicant_role except ApplicantRoleDoesNotExistError: pass else: - if applicant_role in induction_member.roles: - await induction_member.remove_roles( + if applicant_role in main_guild_member.roles: + await main_guild_member.remove_roles( applicant_role, reason=INDUCT_AUDIT_MESSAGE ) @@ -247,7 +259,7 @@ async def _perform_induction( if intro_channel: recent_message: discord.Message for recent_message in await intro_channel.history(limit=30).flatten(): - if recent_message.author.id == induction_member.id: + if recent_message.author.id == main_guild_member.id: try: if tex_emoji: await recent_message.add_reaction(tex_emoji) @@ -366,19 +378,7 @@ async def non_silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role. """ - main_guild_member: discord.Member | None = self.bot.main_guild.get_member(member.id) - if not main_guild_member: - await ctx.respond( - ( - ":information_source: No changes made. User cannot be inducted " - "because they have left the server " - ":information_source:" - ), - ephemeral=True, - ) - return - - await self._perform_induction(ctx, main_guild_member, silent=False) + await self._perform_induction(ctx, member, silent=False) @discord.user_command(name="Silently Induct User") @CommandChecks.check_interaction_user_has_committee_role @@ -394,19 +394,7 @@ async def silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role, only without broadcasting a welcome message. """ - main_guild_member: discord.Member | None = self.bot.main_guild.get_member(member.id) - if not main_guild_member: - await ctx.respond( - ( - ":information_source: No changes made. User cannot be inducted " - "because they have left the server " - ":information_source:" - ), - ephemeral=True, - ) - return - - await self._perform_induction(ctx, main_guild_member, silent=True) + await self._perform_induction(ctx, member, silent=True) @discord.message_command(name="Induct Message Author") @CommandChecks.check_interaction_user_has_committee_role From a81baad2fdc6323b96d48e696ee51856b494bc80 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:34:30 +0000 Subject: [PATCH 4/5] Refactor --- cogs/induct.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cogs/induct.py b/cogs/induct.py index 72c9d61f1..e718fd7d9 100644 --- a/cogs/induct.py +++ b/cogs/induct.py @@ -179,7 +179,7 @@ async def get_random_welcome_message( async def _perform_induction( self, ctx: "TeXBotApplicationContext", - induction_member: discord.Member | discord.User, + induction_member_id: int, *, silent: bool, ) -> None: @@ -188,8 +188,8 @@ async def _perform_induction( main_guild: discord.Guild = self.bot.main_guild guest_role: discord.Role = await self.bot.guest_role - main_guild_member: discord.Member | None = main_guild.get_member(induction_member.id) - if not main_guild_member: + induction_member: discord.Member | None = main_guild.get_member(induction_member_id) + if not induction_member: await ctx.respond( ( ":information_source: No changes made. User cannot be inducted " @@ -202,7 +202,7 @@ async def _perform_induction( await ctx.defer(ephemeral=True) async with ctx.typing(): - logger.debug("Inducting member %s, silent=%s", main_guild_member, silent) + logger.debug("Inducting member %s, silent=%s", induction_member, silent) INDUCT_AUDIT_MESSAGE: Final[str] = ( f'{ctx.user} used TeX Bot slash-command: "/induct"' @@ -212,13 +212,13 @@ async def _perform_induction( main_guild.text_channels, name="introductions" ) - if main_guild_member.bot: + if induction_member.bot: await self.command_send_error( ctx, message="Member cannot be inducted because they are a bot." ) return - if guest_role in main_guild_member.roles: + if guest_role in induction_member.roles: await ctx.respond( content=( ":information_source: No changes made. " @@ -230,25 +230,25 @@ async def _perform_induction( if not silent: await (await self.bot.general_channel).send( - f"{await self.get_random_welcome_message(main_guild_member)} :tada:\n" + f"{await self.get_random_welcome_message(induction_member)} :tada:\n" f"Remember to grab your roles in { await self.bot.get_mention_string(self.bot.roles_channel) } and say hello to everyone here! :wave:" ) - await main_guild_member.add_roles(guest_role, reason=INDUCT_AUDIT_MESSAGE) + await induction_member.add_roles(guest_role, reason=INDUCT_AUDIT_MESSAGE) news_role: discord.Role | None = discord.utils.get(main_guild.roles, name="News") - if news_role and news_role not in main_guild_member.roles: - await main_guild_member.add_roles(news_role, reason=INDUCT_AUDIT_MESSAGE) + if news_role and news_role not in induction_member.roles: + await induction_member.add_roles(news_role, reason=INDUCT_AUDIT_MESSAGE) try: applicant_role: discord.Role = await ctx.bot.applicant_role except ApplicantRoleDoesNotExistError: pass else: - if applicant_role in main_guild_member.roles: - await main_guild_member.remove_roles( + if applicant_role in induction_member.roles: + await induction_member.remove_roles( applicant_role, reason=INDUCT_AUDIT_MESSAGE ) @@ -259,7 +259,7 @@ async def _perform_induction( if intro_channel: recent_message: discord.Message for recent_message in await intro_channel.history(limit=30).flatten(): - if recent_message.author.id == main_guild_member.id: + if recent_message.author.id == induction_member.id: try: if tex_emoji: await recent_message.add_reaction(tex_emoji) @@ -358,7 +358,7 @@ async def induct( await self.command_send_error(ctx, message=member_id_not_integer_error.args[0]) return - await self._perform_induction(ctx, induct_member, silent=silent) + await self._perform_induction(ctx, induct_member.id, silent=silent) class InductContextCommandsCog(BaseInductCog): @@ -378,7 +378,7 @@ async def non_silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role. """ - await self._perform_induction(ctx, member, silent=False) + await self._perform_induction(ctx, member.id, silent=False) @discord.user_command(name="Silently Induct User") @CommandChecks.check_interaction_user_has_committee_role @@ -394,7 +394,7 @@ async def silent_user_induct( Therefore, it will induct a given member into your group's Discord guild by giving them the "Guest" role, only without broadcasting a welcome message. """ - await self._perform_induction(ctx, member, silent=True) + await self._perform_induction(ctx, member.id, silent=True) @discord.message_command(name="Induct Message Author") @CommandChecks.check_interaction_user_has_committee_role @@ -425,7 +425,7 @@ async def non_silent_message_induct( ) return - await self._perform_induction(ctx, member, silent=False) + await self._perform_induction(ctx, member.id, silent=False) class EnsureMembersInductedCommandCog(TeXBotBaseCog): From 4f6406e765d0008eef6358c3586ea3404d4fc3c0 Mon Sep 17 00:00:00 2001 From: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:09:01 +0000 Subject: [PATCH 5/5] Fix user_command type hints and refactor --- cogs/make_applicant.py | 38 ++++++++++++++++++-------------------- cogs/strike.py | 4 ++-- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/cogs/make_applicant.py b/cogs/make_applicant.py index 619d77568..27e4e1681 100644 --- a/cogs/make_applicant.py +++ b/cogs/make_applicant.py @@ -34,7 +34,7 @@ class BaseMakeApplicantCog(TeXBotBaseCog): """ async def _perform_make_applicant( - self, ctx: "TeXBotApplicationContext", applicant_member: discord.Member + self, ctx: "TeXBotApplicationContext", applicant_member_id: int ) -> None: """Perform the actual process of making the user into a group-applicant.""" # NOTE: Shortcut accessors are placed at the top of the function so that the exceptions they raise are displayed before any further errors may be sent @@ -42,6 +42,19 @@ async def _perform_make_applicant( applicant_role: discord.Role = await ctx.bot.applicant_role guest_role: discord.Role = await ctx.bot.guest_role + applicant_member: discord.Member | None = main_guild.get_member(applicant_member_id) + + if not applicant_member: + await ctx.respond( + content=( + ":information_source: " + "No changes made. User cannot be made into an applicant " + "because they have left the server :information_source:" + ), + ephemeral=True, + ) + return + if applicant_role in applicant_member.roles: await ctx.respond("User is already an applicant! Command aborted.") return @@ -196,7 +209,7 @@ async def make_applicant( await self.command_send_error(ctx, message=member_id_not_integer_error.args[0]) return - await self._perform_make_applicant(ctx, applicant_member) + await self._perform_make_applicant(ctx, applicant_member.id) class MakeApplicantContextCommandsCog(BaseMakeApplicantCog): @@ -206,7 +219,7 @@ class MakeApplicantContextCommandsCog(BaseMakeApplicantCog): @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild async def user_make_applicant( - self, ctx: "TeXBotApplicationContext", member: discord.Member + self, ctx: "TeXBotApplicationContext", member: discord.Member | discord.User ) -> None: """ Definition and callback response of the "make_applicant" user-context-command. @@ -215,7 +228,7 @@ async def user_make_applicant( the "make_applicant" slash-command and thus gives the specified user the "Applicant" role and removes the "Guest" role if they have it. """ - await self._perform_make_applicant(ctx, member) + await self._perform_make_applicant(ctx, member.id) @discord.message_command(name="Make Message Author Applicant") @CommandChecks.check_interaction_user_has_committee_role @@ -230,19 +243,4 @@ async def message_make_applicant( the "make_applicant" slash-command and thus gives the specified user the "Applicant" role and removes the "Guest" role if they have it. """ - try: - member: discord.Member = await self.bot.get_member_from_str_id( - str(message.author.id) - ) - except ValueError: - await ctx.respond( - content=( - ":information_source: " - "No changes made. User cannot be made into an applicant " - "because they have left the server :information_source:" - ), - ephemeral=True, - ) - return - - await self._perform_make_applicant(ctx, member) + await self._perform_make_applicant(ctx, message.author.id) diff --git a/cogs/strike.py b/cogs/strike.py index 70bfcbe8d..4cf0e08ba 100644 --- a/cogs/strike.py +++ b/cogs/strike.py @@ -393,7 +393,7 @@ async def _confirm_increase_strike( ) async def _command_perform_strike( - self, ctx: "TeXBotApplicationContext", strike_member: discord.Member + self, ctx: "TeXBotApplicationContext", strike_member: discord.Member | discord.User ) -> None: """ Perform the actual process of giving a member an additional strike. @@ -1075,7 +1075,7 @@ async def _send_message_to_committee( @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild async def user_strike( - self, ctx: "TeXBotApplicationContext", member: discord.Member + self, ctx: "TeXBotApplicationContext", member: discord.Member | discord.User ) -> None: """Call the _strike command, providing the required command arguments.""" await self._command_perform_strike(ctx, strike_member=member)