From d5c3d7e9a58582488281cf77341eaf9af53df0c2 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Thu, 21 May 2026 01:34:12 +0530 Subject: [PATCH 01/17] Code cleanup --- bot/cogs/teams.py | 19 +++++++----- bot/cogs/tortoise_dm.py | 69 +++++++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/bot/cogs/teams.py b/bot/cogs/teams.py index 2405b93..7034553 100644 --- a/bot/cogs/teams.py +++ b/bot/cogs/teams.py @@ -10,7 +10,10 @@ from bot.utils.embed_handler import success, failure, warning, info, authored_sm from bot.utils.checks import tortoise_bot_developer_only +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from bot.bot import Bot class CreateTeamModal(discord.ui.Modal, title="Create Team"): @@ -232,7 +235,7 @@ def __init__(self, cog): style=discord.ButtonStyle.blurple, custom_id="team_request_join_start" ) - async def request_join(self, interaction: discord.Interaction, button: discord.ui.Button): + async def request_join(self, interaction: discord.Interaction): existing = await self.cog.team.get_user_team(interaction.guild.id, interaction.user.id) if existing: return await interaction.response.send_message( @@ -246,7 +249,7 @@ async def request_join(self, interaction: discord.Interaction, button: discord.u ) view = TeamSelectionView(self.cog, teams) - await interaction.response.send_message( + return await interaction.response.send_message( "Select a team you wish to join:", view=view, ephemeral=True ) @@ -314,7 +317,7 @@ async def on_submit(self, interaction: discord.Interaction): class TeamCog(commands.Cog): team_group = app_commands.Group(name="team", description="All management commands related to teams.") - def __init__(self, bot): + def __init__(self, bot: Bot): self.bot = bot self.team = bot.team_manager self.log_channel = None @@ -447,7 +450,7 @@ async def _handle_team_setup(self, interaction: discord.Interaction, custom_id: ephemeral=True ) - await interaction.response.send_modal( + return await interaction.response.send_modal( CreateTeamModal(self, guild.id, invite_id) ) @@ -527,7 +530,7 @@ async def send_team_setup(self, interaction, member: discord.Member): await msg.edit(view=view) - await interaction.followup.send( + return await interaction.followup.send( embed=success("Setup invite sent."), ephemeral=True ) @@ -557,7 +560,7 @@ async def delete_team(self, interaction, role: discord.Role): ) await self.update_dashboard(guild) - await interaction.followup.send(embed=success("Team deleted successfully."), ephemeral=True) + return await interaction.followup.send(embed=success("Team deleted successfully."), ephemeral=True) @team_group.command(name="invite") @@ -752,7 +755,7 @@ async def remove_member(self, interaction, member: discord.Member): if not success_flag: return await interaction.followup.send(embed=failure(err)) - await interaction.followup.send( + return await interaction.followup.send( embed=success(f"{member.mention} removed from team.") ) @@ -792,7 +795,7 @@ async def leave_team(self, interaction: discord.Interaction): if not success_flag: return await interaction.followup.send(embed=failure(err)) - await interaction.followup.send( + return await interaction.followup.send( embed=success("You left the team.") ) diff --git a/bot/cogs/tortoise_dm.py b/bot/cogs/tortoise_dm.py index caecf9b..1fd4987 100644 --- a/bot/cogs/tortoise_dm.py +++ b/bot/cogs/tortoise_dm.py @@ -26,6 +26,31 @@ class UnsupportedFileExtension(Exception): class UnsupportedFileEncoding(ValueError): pass + +class ModMailReasonModal(discord.ui.Modal, title="Open Mod Mail"): + reason = discord.ui.TextInput( + label="Reason for contacting staff", + style=discord.TextStyle.long, + placeholder=( + "⚠️NOTE: Mod mail is strictly for reporting issues, scams, bot accounts or any server related help\n" + "Do NOT use this for general programming help.\n\n" + "Please describe your issue here..." + ), + min_length=10, + max_length=1024, + required=True + ) + + def __init__(self, cog: "TortoiseDM"): + super().__init__() + self.cog = cog + + async def on_submit(self, interaction: discord.Interaction): + user = interaction.user + await interaction.response.defer(ephemeral=True) + await self.cog.create_mod_mail(user, reason=self.reason.value, source="dm") + + class DutyScheduleModal(discord.ui.Modal, title="Set Daily Mod Mail Schedule"): start_time = discord.ui.TextInput( label="Start Time (24h format)", @@ -113,12 +138,6 @@ async def callback(self, interaction: discord.Interaction): user = interaction.user cog = interaction.client.get_cog("TortoiseDM") - # Remove buttons immediately - if interaction.message: - view = self.view - view.clear_items() - await interaction.message.edit(view=view) - if cog.is_any_session_active(user.id): await interaction.response.send_message("Session already active.", ephemeral=True) return @@ -127,9 +146,21 @@ async def callback(self, interaction: discord.Interaction): msg = f"You are on cooldown. You can retry after {cog.cool_down.retry_after(user.id)}s" await interaction.response.send_message(embed=failure(msg), ephemeral=True) return - else: - cog.cool_down.add_to_cool_down(user.id) + if self.label == "Contact staff (Mod Mail)": + await interaction.response.send_modal(ModMailReasonModal(cog)) + if interaction.message: + view = self.view + view.clear_items() + await interaction.message.edit(view=view) + return + + if interaction.message: + view = self.view + view.clear_items() + await interaction.message.edit(view=view) + + cog.cool_down.add_to_cool_down(user.id) await interaction.response.defer(ephemeral=True) await self.callback_func(user) @@ -325,7 +356,7 @@ def __init__(self, bot): self.user_suggestions_channel = None self.mod_mail_report_channel = None self.code_submissions_channel = None - self.staff_applications_channel= None + self.staff_applications_channel = None self.staff_channel = None @commands.Cog.listener() @@ -544,9 +575,12 @@ async def update_staff_embed( except Exception: pass - async def create_mod_mail(self, user: discord.User, source: str = "dm"): + async def create_mod_mail(self, user: discord.User, reason: str = "No reason provided.", source: str = "dm"): if user.id in self.pending_mod_mails: - await user.send(embed=failure("You already have a pending mod mail, please be patient.")) + try: + await user.send(embed=failure("You already have a pending mod mail, please be patient.")) + except discord.Forbidden: + pass return source_text = { @@ -554,7 +588,10 @@ async def create_mod_mail(self, user: discord.User, source: str = "dm"): "panel": "created a ban appeal request." }.get(source, source) - submission_embed = authored_sm(f"{user.name} {source_text}", author=user) + submission_embed = authored(f"{user.name} {source_text}", author=user) + submission_embed.add_field(name="Provided Reason", value=reason, inline=False) + submission_embed.color = discord.Color.orange() + view = ModMailAcceptView(self, user.id) msg = await self.staff_channel.send( @@ -564,6 +601,9 @@ async def create_mod_mail(self, user: discord.User, source: str = "dm"): ) self.modmail_messages[user.id] = msg.id self.pending_mod_mails.add(user.id) + + self.cog.cool_down.add_to_cool_down(user.id) + if source == "dm": embed = info("Mail is initialized and the moderators have been contacted.\n" "You'll be notified once someone from the team responds.", @@ -571,7 +611,10 @@ async def create_mod_mail(self, user: discord.User, source: str = "dm"): embed.set_footer( text="NOTE: Response time may vary; No need to wait here." ) - await user.send(embed=embed) + try: + await user.send(embed=embed) + except discord.Forbidden: + pass async def create_event_submission(self, user: discord.User): user_reply = await self._get_user_reply(self.active_event_submissions, user, "Event Submission") From 87c68d8cb6b9a986faec83cacc52787d742a55db Mon Sep 17 00:00:00 2001 From: Ryuga Date: Thu, 21 May 2026 02:18:47 +0530 Subject: [PATCH 02/17] Update mod mail --- bot/cogs/tortoise_dm.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/bot/cogs/tortoise_dm.py b/bot/cogs/tortoise_dm.py index 1fd4987..6fd053e 100644 --- a/bot/cogs/tortoise_dm.py +++ b/bot/cogs/tortoise_dm.py @@ -27,18 +27,19 @@ class UnsupportedFileEncoding(ValueError): pass -class ModMailReasonModal(discord.ui.Modal, title="Open Mod Mail"): - reason = discord.ui.TextInput( - label="Reason for contacting staff", - style=discord.TextStyle.long, - placeholder=( - "⚠️NOTE: Mod mail is strictly for reporting issues, scams, bot accounts or any server related help\n" - "Do NOT use this for general programming help.\n\n" - "Please describe your issue here..." +class ModMailReasonModal(discord.ui.Modal, title="Contact Staff (Mod Mail)"): + reason = discord.ui.Label( + text="Reason for contacting staff", + description=( + "⚠️ Mod mail is strictly for reporting scams, bots or server related issues." ), - min_length=10, - max_length=1024, - required=True + component=discord.ui.TextInput( + style=discord.TextStyle.long, + min_length=10, + max_length=1024, + required=True, + placeholder="Describe the issue here..." + ) ) def __init__(self, cog: "TortoiseDM"): @@ -48,7 +49,7 @@ def __init__(self, cog: "TortoiseDM"): async def on_submit(self, interaction: discord.Interaction): user = interaction.user await interaction.response.defer(ephemeral=True) - await self.cog.create_mod_mail(user, reason=self.reason.value, source="dm") + await self.cog.create_mod_mail(user, reason=self.reason.component.value, source="dm") class DutyScheduleModal(discord.ui.Modal, title="Set Daily Mod Mail Schedule"): @@ -323,7 +324,7 @@ def __init__(self, bot): # bool whether that option is disabled or not. # TODO if callable errors container will not be properly updated so users will not be able to call it again self._options = { - constants.mod_mail_emoji_id: { + 1472366942722723995: { "message": "Contact staff (Mod Mail)", "callable": self.create_mod_mail, "check": lambda: self.bot.tortoise_meta_cache["mod_mail"] From 9cfe03c5c1727e2f89b5fa33d1047b62492e7f6e Mon Sep 17 00:00:00 2001 From: Ryuga Date: Thu, 21 May 2026 02:31:41 +0530 Subject: [PATCH 03/17] Upgrade modmail --- bot/cogs/tortoise_dm.py | 42 +++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/bot/cogs/tortoise_dm.py b/bot/cogs/tortoise_dm.py index 6fd053e..1753447 100644 --- a/bot/cogs/tortoise_dm.py +++ b/bot/cogs/tortoise_dm.py @@ -38,7 +38,7 @@ class ModMailReasonModal(discord.ui.Modal, title="Contact Staff (Mod Mail)"): min_length=10, max_length=1024, required=True, - placeholder="Describe the issue here..." + placeholder="Please describe your issue here..." ) ) @@ -247,7 +247,10 @@ def mod_mail_check(msg): timeout_embed = failure("Mod mail closed due to inactivity.") log.add_embed(timeout_embed) await mod.send(embed=timeout_embed) - await user.send(embed=timeout_embed) + try: + await user.send(embed=timeout_embed) + except discord.HTTPException: + pass del self.cog.active_mod_mails[user_id] logs = await self.cog.mod_mail_report_channel.send( file=discord.File(StringIO(str(log)), filename=log.filename) @@ -277,7 +280,10 @@ def mod_mail_check(msg): close_embed = success(f"Mod mail successfully closed by {mail_msg.author}.") log.add_embed(close_embed) await mod.send(embed=close_embed) - await user.send(embed=close_embed) + try: + await user.send(embed=close_embed) + except discord.HTTPException: + pass del self.cog.active_mod_mails[user_id] logs = await self.cog.mod_mail_report_channel.send( file=discord.File(StringIO(str(log)), filename=log.filename) @@ -294,7 +300,31 @@ def mod_mail_check(msg): if mail_msg.author == user: await mod.send(mail_msg.content) elif mail_msg.author == mod: - await user.send(mail_msg.content) + guild_member = self.cog.tortoise_guild.get_member(user_id) + if guild_member is None: + left_embed = failure("Mod mail closed: The user has left the server.") + log.add_embed(left_embed) + await mod.send(embed=left_embed) + + del self.cog.active_mod_mails[user_id] + logs = await self.cog.mod_mail_report_channel.send( + file=discord.File(StringIO(str(log)), filename=log.filename) + ) + await self.cog.update_staff_embed( + user_id, + description=logs.jump_url, + footer_append="❌ Closed: User left the server.", + color=discord.Color.red() + ) + del self.cog.modmail_messages[user_id] + break + + try: + await user.send(mail_msg.content) + except discord.HTTPException: + dm_closed_embed = failure("Could not deliver message: The user closed their DMs.") + log.add_embed(dm_closed_embed) + await mod.send(embed=dm_closed_embed) class TortoiseDM(commands.Cog): @@ -324,7 +354,7 @@ def __init__(self, bot): # bool whether that option is disabled or not. # TODO if callable errors container will not be properly updated so users will not be able to call it again self._options = { - 1472366942722723995: { + constants.mod_mail_emoji_id: { "message": "Contact staff (Mod Mail)", "callable": self.create_mod_mail, "check": lambda: self.bot.tortoise_meta_cache["mod_mail"] @@ -603,7 +633,7 @@ async def create_mod_mail(self, user: discord.User, reason: str = "No reason pro self.modmail_messages[user.id] = msg.id self.pending_mod_mails.add(user.id) - self.cog.cool_down.add_to_cool_down(user.id) + self.cool_down.add_to_cool_down(user.id) if source == "dm": embed = info("Mail is initialized and the moderators have been contacted.\n" From bf8372ee95f59fc2be7e7299a9a42d8d3ac73a72 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Thu, 21 May 2026 02:36:16 +0530 Subject: [PATCH 04/17] Modmail fixes --- bot/cogs/tortoise_dm.py | 52 ++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/bot/cogs/tortoise_dm.py b/bot/cogs/tortoise_dm.py index 1753447..f63bfa9 100644 --- a/bot/cogs/tortoise_dm.py +++ b/bot/cogs/tortoise_dm.py @@ -190,6 +190,8 @@ async def accept(self, interaction: discord.Interaction, button: discord.ui.Butt await interaction.response.send_message("User not found.", ephemeral=True) return + await interaction.response.defer(ephemeral=True) + self.clear_items() await self.cog.update_staff_embed_from_message( interaction.message, @@ -212,22 +214,25 @@ async def accept(self, interaction: discord.Interaction, button: discord.ui.Butt await interaction.followup.send("Mod mail failed: moderator DMs closed.", ephemeral=True) return - await user.send( - embed=authored( - ( - f"{mod.name} has accepted your mod mail request.\n" - "Reply here in DMs to chat with them.\n" - "This mod mail will be logged, by continuing you agree to that." - ), - author=mod + try: + await user.send( + embed=authored( + ( + f"{mod.name} has accepted your mod mail request.\n" + "Reply here in DMs to chat with them.\n" + "This mod mail will be logged, by continuing you agree to that." + ), + author=mod + ) ) - ) + except discord.HTTPException: + await interaction.followup.send("Failed to notify the user. Their DMs might be closed.", ephemeral=True) + return self.cog.pending_mod_mails.remove(user_id) self.cog.active_mod_mails[user_id] = mod.id embed = success("Mod Mail initialized. Check your DMs") - await interaction.response.send_message(embed=embed, ephemeral=True) - + await interaction.followup.send(embed=embed, ephemeral=True) first_timeout = 21_600 regular_timeout = 1800 @@ -246,7 +251,10 @@ def mod_mail_check(msg): except TimeoutError: timeout_embed = failure("Mod mail closed due to inactivity.") log.add_embed(timeout_embed) - await mod.send(embed=timeout_embed) + try: + await mod.send(embed=timeout_embed) + except discord.HTTPException: + pass try: await user.send(embed=timeout_embed) except discord.HTTPException: @@ -279,7 +287,10 @@ def mod_mail_check(msg): if mail_msg.content.lower() == "close" and mail_msg.author.id == mod.id: close_embed = success(f"Mod mail successfully closed by {mail_msg.author}.") log.add_embed(close_embed) - await mod.send(embed=close_embed) + try: + await mod.send(embed=close_embed) + except discord.HTTPException: + pass try: await user.send(embed=close_embed) except discord.HTTPException: @@ -298,13 +309,19 @@ def mod_mail_check(msg): break if mail_msg.author == user: - await mod.send(mail_msg.content) + try: + await mod.send(mail_msg.content) + except discord.HTTPException: + pass elif mail_msg.author == mod: guild_member = self.cog.tortoise_guild.get_member(user_id) if guild_member is None: left_embed = failure("Mod mail closed: The user has left the server.") log.add_embed(left_embed) - await mod.send(embed=left_embed) + try: + await mod.send(embed=left_embed) + except discord.HTTPException: + pass del self.cog.active_mod_mails[user_id] logs = await self.cog.mod_mail_report_channel.send( @@ -324,7 +341,10 @@ def mod_mail_check(msg): except discord.HTTPException: dm_closed_embed = failure("Could not deliver message: The user closed their DMs.") log.add_embed(dm_closed_embed) - await mod.send(embed=dm_closed_embed) + try: + await mod.send(embed=dm_closed_embed) + except discord.HTTPException: + pass class TortoiseDM(commands.Cog): From a01df0c4636131c975ab460b6da3b756b5899bac Mon Sep 17 00:00:00 2001 From: Ryuga Date: Thu, 21 May 2026 22:27:04 +0530 Subject: [PATCH 05/17] Update security cog to view pdf online --- bot/cogs/security.py | 132 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 12 deletions(-) diff --git a/bot/cogs/security.py b/bot/cogs/security.py index 792eed7..70ba2d8 100644 --- a/bot/cogs/security.py +++ b/bot/cogs/security.py @@ -5,6 +5,7 @@ import aiohttp import asyncio import io +import urllib.parse from discord.ext import commands from discord import Member, Message, app_commands, Guild @@ -15,11 +16,28 @@ from bot.utils.checks import tortoise_bot_developer_only from bot.utils.misc import get_user_avatar from bot.utils.custom_types import FakeInteraction - +from bot.utils.cooldown import CoolDown logger = logging.getLogger(__name__) +class PDFViewerButtonView(discord.ui.View): + + def __init__(self, bot: commands.Bot, original_msg_id: int): + super().__init__(timeout=None) + self.bot = bot + self.original_msg_id = original_msg_id + + self.click_cooldown = CoolDown(seconds=60) + self.bot.loop.create_task(self.click_cooldown.start()) + + self.open_button.custom_id = f"pdf_view_ctx:{original_msg_id}" + + @discord.ui.button(label="Open in Web Viewer", style=discord.ButtonStyle.link) + async def open_button(self, interaction: discord.Interaction, button: discord.ui.Button): + pass + + class Security(commands.Cog): def __init__(self, bot): self.bot = bot @@ -27,6 +45,8 @@ def __init__(self, bot): self._trusted = None self._log_channel = None + self.interaction_cooldowns = {} + @property def guild(self): if self._guild is None: @@ -73,6 +93,7 @@ async def security_check(self, message: Message): return if message.attachments: + await self.process_pdf_attachments(message) deleted = await self.deal_with_attachments(message) if deleted: self.bot.suppressed_deletes.add(message.id) @@ -97,6 +118,93 @@ def is_security_whitelisted(self, message: Message) -> bool: return True return False + async def process_pdf_attachments(self, message: Message): + for attachment in message.attachments: + if attachment.filename.lower().endswith('.pdf'): + + embed = discord.Embed( + title="📄 View This File Online", + description=( + f"You can safely view **{attachment.filename}** within your web browser " + f"without needing to download the file locally.\n\n" + f"💡 *Click the button below to compile a fresh web view link.*" + ), + color=0xffb101 + ) + embed.set_footer(text="We do not recommend downloading docs from discord.") + + view = PDFViewerButtonView(self.bot, message.id) + + try: + await message.channel.send(embed=embed, view=view, reference=message) + except discord.HTTPException: + pass + + @commands.Cog.listener() + async def on_interaction(self, interaction: discord.Interaction): + if interaction.type != discord.InteractionType.component: + return + + custom_id = interaction.data.get("custom_id", "") + if not custom_id.startswith("pdf_view_ctx:"): + return + + user_id = interaction.user.id + now = discord.utils.utcnow().timestamp() + + if user_id not in self.interaction_cooldowns: + self.interaction_cooldowns[user_id] = [] + + self.interaction_cooldowns[user_id] = [t for t in self.interaction_cooldowns[user_id] if now - t < 30] + + if len(self.interaction_cooldowns[user_id]) >= 3: + await interaction.response.send_message( + embed=warning("You are clicking this too fast. Please wait a few seconds before trying again."), + ephemeral=True + ) + return + + self.interaction_cooldowns[user_id].append(now) + + await interaction.response.defer(ephemeral=True) + + try: + original_msg_id = int(custom_id.split(":")[1]) + # Fetch the message context using the channel parameters to grab updated tokens + origin_message = await interaction.channel.fetch_message(original_msg_id) + except (discord.NotFound, discord.Forbidden, ValueError, IndexError): + await interaction.followup.send( + embed=warning( + "Could not open file: The original upload message was deleted or is no longer accessible."), + ephemeral=True + ) + return + + target_attachment = None + for attachment in origin_message.attachments: + if attachment.filename.lower().endswith('.pdf'): + target_attachment = attachment + break + + if not target_attachment: + await interaction.followup.send( + embed=warning( + "Could not open file: The PDF asset could not be found inside that message payload context."), + ephemeral=True + ) + return + + encoded_url = urllib.parse.quote(target_attachment.url, safe='') + viewer_domain = "https://viewer.tortoisecommunity.org" + final_viewer_url = f"{viewer_domain}/{encoded_url}" + success_embed = success( + f"### 🔗 [Click here]({final_viewer_url}) to open **{target_attachment.filename}** in Web Viewer\n\n" + ) + success_embed.set_footer(text="This temporary direct stream pathway link remains valid for 24 hours") + await interaction.followup.send( + embed=success_embed, + ephemeral=True + ) async def archive_and_delete_message( self, @@ -364,21 +472,21 @@ def extract_content(message: discord.Message) -> str: @commands.Cog.listener() async def on_message_edit(self, msg_before, msg_after): - if msg_before.content == msg_after.content: + if msg_before.content == msg_after.content and len(msg_before.attachments) == len(msg_after.attachments): return elif self.is_security_whitelisted(msg_after): return - # Log that the message was edited for security reasons - msg = ( - f"**Channel**\n{msg_before.channel.mention}\n\n" - f"**Before**\n{msg_before.content}\n\n" - f"**After**\n{msg_after.content}\n\n" - f"[jump]({msg_after.jump_url})" - ) - embed = info(msg, msg_before.guild.me, title="Message edited") - embed.set_footer(text=f"Author: {msg_before.author}", icon_url=get_user_avatar(msg_before.author)) - await self.log_channel.send(embed=embed) + if msg_before.content != msg_after.content: + msg = ( + f"**Channel**\n{msg_before.channel.mention}\n\n" + f"**Before**\n{msg_before.content}\n\n" + f"**After**\n{msg_after.content}\n\n" + f"[jump]({msg_after.jump_url})" + ) + embed = info(msg, msg_before.guild.me, title="Message edited") + embed.set_footer(text=f"Author: {msg_before.author}", icon_url=get_user_avatar(msg_before.author)) + await self.log_channel.send(embed=embed) # Check if the new message violates our security await self.security_check(msg_after) From 80dcbd9947d6cd9c9e16e84a64d98b76147647fe Mon Sep 17 00:00:00 2001 From: Ryuga Date: Thu, 21 May 2026 23:13:34 +0530 Subject: [PATCH 06/17] Fix viewer --- bot/cogs/security.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/bot/cogs/security.py b/bot/cogs/security.py index 70ba2d8..9199623 100644 --- a/bot/cogs/security.py +++ b/bot/cogs/security.py @@ -16,7 +16,6 @@ from bot.utils.checks import tortoise_bot_developer_only from bot.utils.misc import get_user_avatar from bot.utils.custom_types import FakeInteraction -from bot.utils.cooldown import CoolDown logger = logging.getLogger(__name__) @@ -27,13 +26,9 @@ def __init__(self, bot: commands.Bot, original_msg_id: int): super().__init__(timeout=None) self.bot = bot self.original_msg_id = original_msg_id - - self.click_cooldown = CoolDown(seconds=60) - self.bot.loop.create_task(self.click_cooldown.start()) - self.open_button.custom_id = f"pdf_view_ctx:{original_msg_id}" - @discord.ui.button(label="Open in Web Viewer", style=discord.ButtonStyle.link) + @discord.ui.button(label="View Online", style=discord.ButtonStyle.secondary, emoji="🔗") async def open_button(self, interaction: discord.Interaction, button: discord.ui.Button): pass @@ -123,15 +118,15 @@ async def process_pdf_attachments(self, message: Message): if attachment.filename.lower().endswith('.pdf'): embed = discord.Embed( - title="📄 View This File Online", + title="Open in Web Viewer", description=( f"You can safely view **{attachment.filename}** within your web browser " f"without needing to download the file locally.\n\n" - f"💡 *Click the button below to compile a fresh web view link.*" + f"-# Click the button below to generate a fresh link." ), color=0xffb101 ) - embed.set_footer(text="We do not recommend downloading docs from discord.") + embed.set_footer(text="We do not recommend downloading documents from discord.") view = PDFViewerButtonView(self.bot, message.id) @@ -170,7 +165,6 @@ async def on_interaction(self, interaction: discord.Interaction): try: original_msg_id = int(custom_id.split(":")[1]) - # Fetch the message context using the channel parameters to grab updated tokens origin_message = await interaction.channel.fetch_message(original_msg_id) except (discord.NotFound, discord.Forbidden, ValueError, IndexError): await interaction.followup.send( @@ -200,7 +194,7 @@ async def on_interaction(self, interaction: discord.Interaction): success_embed = success( f"### 🔗 [Click here]({final_viewer_url}) to open **{target_attachment.filename}** in Web Viewer\n\n" ) - success_embed.set_footer(text="This temporary direct stream pathway link remains valid for 24 hours") + success_embed.set_footer(text="This temporary link remains valid for 24 hours") await interaction.followup.send( embed=success_embed, ephemeral=True From 1f2b6f8cf4d4c758cd2e5a26693be4d5c24e721f Mon Sep 17 00:00:00 2001 From: Ryuga Date: Thu, 21 May 2026 23:16:20 +0530 Subject: [PATCH 07/17] Fix viewer --- bot/cogs/security.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/security.py b/bot/cogs/security.py index 9199623..7d4b5b8 100644 --- a/bot/cogs/security.py +++ b/bot/cogs/security.py @@ -192,7 +192,7 @@ async def on_interaction(self, interaction: discord.Interaction): viewer_domain = "https://viewer.tortoisecommunity.org" final_viewer_url = f"{viewer_domain}/{encoded_url}" success_embed = success( - f"### 🔗 [Click here]({final_viewer_url}) to open **{target_attachment.filename}** in Web Viewer\n\n" + f"[Click here]({final_viewer_url}) to open **{target_attachment.filename}** in Web Viewer\n\n" ) success_embed.set_footer(text="This temporary link remains valid for 24 hours") await interaction.followup.send( From 85e60d8d3c7a643c0741ec091123ed0eeb1e6402 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 22 May 2026 02:17:04 +0530 Subject: [PATCH 08/17] Fix security cog --- bot/cogs/afk.py | 6 +++--- bot/cogs/security.py | 14 ++++++++------ bot/cogs/teams.py | 2 +- bot/constants.py | 3 ++- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bot/cogs/afk.py b/bot/cogs/afk.py index e9e233a..ee7edc7 100644 --- a/bot/cogs/afk.py +++ b/bot/cogs/afk.py @@ -14,7 +14,7 @@ from bot.bot import Bot class AFK(commands.Cog): - def __init__(self, bot: Bot) -> None: + def __init__(self, bot: "Bot") -> None: self.bot = bot self.manager = bot.afk_manager self.cleanup_expired.start() @@ -22,8 +22,8 @@ def __init__(self, bot: Bot) -> None: def cog_unload(self): self.cleanup_expired.cancel() - @app_commands.command(name="setafk", description="Set AFK status.") - async def setafk( + @app_commands.command(name="set_afk", description="Set AFK status.") + async def set_afk( self, interaction: discord.Interaction, hours: Optional[int] = None, diff --git a/bot/cogs/security.py b/bot/cogs/security.py index 7d4b5b8..36c4694 100644 --- a/bot/cogs/security.py +++ b/bot/cogs/security.py @@ -12,7 +12,7 @@ from bot import constants from bot.utils.embed_handler import info, moderation_log_embed, warning, success, infraction_embed from bot.utils.message_handler import RemovableMessage -from bot.constants import allowed_file_extensions +from bot.constants import allowed_file_extensions, online_viewer_url from bot.utils.checks import tortoise_bot_developer_only from bot.utils.misc import get_user_avatar from bot.utils.custom_types import FakeInteraction @@ -189,10 +189,11 @@ async def on_interaction(self, interaction: discord.Interaction): return encoded_url = urllib.parse.quote(target_attachment.url, safe='') - viewer_domain = "https://viewer.tortoisecommunity.org" - final_viewer_url = f"{viewer_domain}/{encoded_url}" - success_embed = success( - f"[Click here]({final_viewer_url}) to open **{target_attachment.filename}** in Web Viewer\n\n" + final_viewer_url = f"{online_viewer_url}/{encoded_url}" + success_embed = info( + f"[Click here]({final_viewer_url}) to open **{target_attachment.filename}** in Web Viewer\n\n", + self.bot.user, + "" ) success_embed.set_footer(text="This temporary link remains valid for 24 hours") await interaction.followup.send( @@ -324,7 +325,7 @@ async def take_action_ban(self, member, reason, content=None): infraction_type=constants.Infraction.ban, reason=reason, is_dm=True, - can_appeal=True + permanent=False ) embed.set_footer(text="⚠️ This was an automated action. If you'd like to appeal, join the appeal server.") @@ -504,5 +505,6 @@ async def enable_advanced_protection(self, interaction: discord.Interaction): self.bot.advanced_protection = True await interaction.followup.send(embed=success(f"Advanced Protection™ Enabled."), ephemeral=False) + async def setup(bot): await bot.add_cog(Security(bot)) diff --git a/bot/cogs/teams.py b/bot/cogs/teams.py index 7034553..d10e2c7 100644 --- a/bot/cogs/teams.py +++ b/bot/cogs/teams.py @@ -317,7 +317,7 @@ async def on_submit(self, interaction: discord.Interaction): class TeamCog(commands.Cog): team_group = app_commands.Group(name="team", description="All management commands related to teams.") - def __init__(self, bot: Bot): + def __init__(self, bot: "Bot"): self.bot = bot self.team = bot.team_manager self.log_channel = None diff --git a/bot/constants.py b/bot/constants.py index c6740c3..5fb9838 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -21,7 +21,8 @@ appeal_server_link = "https://discord.com/invite/YxEzEqMNY8" server_link = "https://discord.com/invite/Ex8xeWD" online_compiler_link = "https://execute.tortoisecommunity.org" -runtime_bot_link = "https://runtime-bot.tortoisecommunity.org/" +runtime_bot_link = "https://runtime-bot.tortoisecommunity.org" +online_viewer_url = "https://viewer.tortoisecommunity.org" # Channel IDs welcome_channel_id = 738731842538176522 From 8d424f0c52d889030461b28b8118f61fd3e3ae4c Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 22 May 2026 02:18:26 +0530 Subject: [PATCH 09/17] Fix security cog --- bot/bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index 74e0a6f..6d6f3a2 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -125,9 +125,9 @@ async def send_restart_message(self: commands.Bot): embed = simple_embed(message=f"Build version: [{commit_hash}]({github_repo_link}/commit/{commit_hash})", title="") embed.set_footer(text=commit_message) - await self.sys_log_channel.send( - embed=embed, - ) + # await self.sys_log_channel.send( + # embed=embed, + # ) except discord.Forbidden: pass From 81481f09dfae2633e0d7a8906282eac20dd0f2c6 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Mon, 25 May 2026 20:19:43 +0530 Subject: [PATCH 10/17] Fix teams onboarding --- bot/cogs/teams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/teams.py b/bot/cogs/teams.py index d10e2c7..4d31637 100644 --- a/bot/cogs/teams.py +++ b/bot/cogs/teams.py @@ -235,7 +235,7 @@ def __init__(self, cog): style=discord.ButtonStyle.blurple, custom_id="team_request_join_start" ) - async def request_join(self, interaction: discord.Interaction): + async def request_join(self, interaction: discord.Interaction, button: discord.ui.Button): # noqa existing = await self.cog.team.get_user_team(interaction.guild.id, interaction.user.id) if existing: return await interaction.response.send_message( From 3316546cc156bcb82a0b694fb8a386f169cd4b77 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Mon, 25 May 2026 20:42:04 +0530 Subject: [PATCH 11/17] Ban appeal bugfix --- bot/cogs/tortoise_dm.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bot/cogs/tortoise_dm.py b/bot/cogs/tortoise_dm.py index f63bfa9..e4a1c8d 100644 --- a/bot/cogs/tortoise_dm.py +++ b/bot/cogs/tortoise_dm.py @@ -314,7 +314,8 @@ def mod_mail_check(msg): except discord.HTTPException: pass elif mail_msg.author == mod: - guild_member = self.cog.tortoise_guild.get_member(user_id) + guild_member = (self.cog.tortoise_guild.get_member(user_id) + or self.cog.ban_appeal_guild.get_member(user_id)) if guild_member is None: left_embed = failure("Mod mail closed: The user has left the server.") log.add_embed(left_embed) @@ -353,6 +354,7 @@ class TortoiseDM(commands.Cog): def __init__(self, bot): self.bot = bot self._tortoise_guild = None + self._ban_appeal_guild = None self._admin_role = None self._moderator_role = None self._mod_mail_ping_role = None @@ -429,6 +431,12 @@ def tortoise_guild(self): self._tortoise_guild = self.bot.get_guild(constants.tortoise_guild_id) return self._tortoise_guild + @property + def ban_appeal_guild(self): + if self._ban_appeal_guild is None: + self._ban_appeal_guild = self.bot.get_guild(constants.ban_appeal_server_id) + return self._ban_appeal_guild + @property def admin_role(self): if self._admin_role is None: From 8f4e604afd1fcea376981ca8456b22869da24640 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 29 May 2026 01:42:03 +0530 Subject: [PATCH 12/17] Better mod mail system for auto unban --- bot/cogs/anti_raid.py | 5 ++ bot/cogs/button_utility.py | 147 +++++++++++++++++++++++++++++++------ bot/cogs/github.py | 30 ++++---- bot/cogs/moderation.py | 4 +- bot/cogs/tortoise_dm.py | 4 +- bot/manager.py | 27 +++++++ 6 files changed, 175 insertions(+), 42 deletions(-) diff --git a/bot/cogs/anti_raid.py b/bot/cogs/anti_raid.py index c872d10..e1af54c 100644 --- a/bot/cogs/anti_raid.py +++ b/bot/cogs/anti_raid.py @@ -145,6 +145,11 @@ async def handle_bot_trap_raid(self, member: discord.Member): reason=self.BOT_TRAP_BAN_REASON, delete_message_days=1, ) + await self.bot.progression_manager.set_ban_status( + user_id=member.id, + guild_id=guild.id, + status=True + ) except discord.Forbidden: return diff --git a/bot/cogs/button_utility.py b/bot/cogs/button_utility.py index b33de8d..cb10e1b 100644 --- a/bot/cogs/button_utility.py +++ b/bot/cogs/button_utility.py @@ -6,12 +6,132 @@ from bot.constants import ( challenger_role_id, accepting_team_invites_role_id, tortoise_guild_id, - join_a_team_channel_id, teams_dashboard_message_id + join_a_team_channel_id, teams_dashboard_message_id, server_link ) from bot.utils.checks import tortoise_bot_developer_only from bot.utils.embed_handler import info, failure +class TicketReasonSelect(discord.ui.Select): + """Dropdown menu for selecting the ticket/ban appeal reason.""" + + def __init__(self, cog: "TortoiseDM"): + options = [ + discord.SelectOption( + label="Accidentally Selected 'I am Bot' Option", + value="accidental_trap_victim", + description="I accidentally selected 'I am Bot' option while joining.", + emoji="🤖" + ), + discord.SelectOption(label="Unfair Ban", value="unfair_ban", description="I feel my ban was unjust.", + emoji="⚖️"), + discord.SelectOption(label="Apology / Second Chance", value="apology", + description="I admit my mistake and want to apologize.", emoji="🙏"), + discord.SelectOption(label="Compromised Account", value="compromised", + description="My account was hacked when the violation occurred.", emoji="🛡️"), + discord.SelectOption(label="Other Reason", value="other", description="Any other reason not listed above.", + emoji="📝"), + ] + super().__init__( + placeholder="Choose the reason for your appeal...", + min_values=1, + max_values=1, + options=options + ) + self.cog = cog + + async def callback(self, interaction: discord.Interaction): + user = interaction.user + reason = self.values[0] + + reason_mappings = { + "accidental_trap_victim": "Accidentally Selected 'I am Bot' Option", + "unfair_ban": "Unfair Ban Appeal", + "apology": "Apology / Second Chance Request", + "compromised": "Compromised Account Appeal", + "other": "Other / Unspecified Reason" + } + chosen_reason = reason_mappings.get(reason, "Unspecified Reason") + + self.disabled = True + + await interaction.response.defer(ephemeral=True, thinking=True) + + if reason == "accidental_trap_victim": + is_banned = await self.cog.bot.progression_manager.is_auto_banned(user_id=user.id, + guild_id=tortoise_guild_id) + + if is_banned: + guild = self.cog.bot.get_guild(tortoise_guild_id) + + try: + ban_entry = await guild.fetch_ban(discord.Object(id=user.id)) + target_user = ban_entry.user + + await guild.unban(target_user, reason="Auto unbanned via Honeypot Trap Appeal panel.") + await self.cog.bot.safe_send( + target_user, + content=server_link, + embed=info( + "You have been unbanned in Tortoise Community\n" + "Please use the invite link to rejoin the server\n", + self.cog.bot.user, + "Ban Lifted!", + "Welcome back to Tortoise Programming Community!", + ) + ) + + await self.cog.bot.progression_manager.set_ban_status(user_id=user.id, + guild_id=tortoise_guild_id, + status=False) + + await interaction.followup.send(f"✅ Successfully unbanned. You may rejoin!", ephemeral=True) + + except discord.NotFound: + await interaction.followup.send("You are not currently recorded on the server ban list.", + ephemeral=True) + except discord.HTTPException: + await interaction.followup.send( + "❌ Something went wrong while attempting to unban. Try again later.", ephemeral=True) + else: + await interaction.followup.send( + embed=failure( + "Our records indicate you weren't banned by the automated bot trap.\nPlease select a different appeal reason."), + ephemeral=True + ) + + else: + await interaction.edit_original_response(content="⏳ Processing your request and opening a ticket...", + view=None) + + try: + embed = info( + f"Your ban appeal request is logged.\n" + f"**Reason:** {chosen_reason}\n" + "Please wait for a moderator to respond.\n\n", + self.cog.bot.user, + "Ticket Created!" + ) + embed.set_footer(text="NOTE: Please remain in this server until this ticket is closed.") + await user.send(embed=embed) + except discord.HTTPException: + await interaction.followup.send( + "❌ I couldn't send you a Direct Message. Please enable DMs from server members and try again.", + ephemeral=True + ) + return + + await self.cog.create_mod_mail(user, reason=chosen_reason, source="panel", ping=False) + + +class TicketReasonView(discord.ui.View): + """Temporary ephemeral view containing the reason dropdown.""" + + def __init__(self, cog: "TortoiseDM"): + super().__init__(timeout=60) + self.add_item(TicketReasonSelect(cog)) + + class ModMailStartView(discord.ui.View): """Persistent button view for Mod mail ticket creation.""" @@ -45,30 +165,11 @@ async def start_modmail(self, interaction: discord.Interaction, button: discord. cog.cool_down.add_to_cool_down(user.id) await interaction.response.send_message( - "📩 Opening mod mail in your DMs...", - ephemeral=True, - delete_after=5, + "📩 Please select the reason for your ban appeal below:", + view=TicketReasonView(cog), + ephemeral=True ) - try: - embed = info( - "Your ban appeal request is logged.\n" - "Please wait for a moderator to respond.\n\n", - user, - "Ticket Created!" - ) - embed.set_footer(text="NOTE: Please remain in this server until this ticket is closed.") - await user.send(embed=embed) - except discord.HTTPException: - await interaction.followup.send( - "I couldn't DM you. Please enable DMs.", - ephemeral=True - ) - return - - await cog.create_mod_mail(user, source="panel") - - class NotifyButton(discord.ui.View): """Persistent button view for challenge notifications.""" diff --git a/bot/cogs/github.py b/bot/cogs/github.py index d968335..cd07ff5 100644 --- a/bot/cogs/github.py +++ b/bot/cogs/github.py @@ -13,7 +13,7 @@ STATIC_PROJECTS_DATA = [ { - "name": "Tortoise-Bot", + "name": "tortoise-bot", "html_url": "https://github.com/Tortoise-Community/Tortoise-Bot", "web_link": "https://github.com/Tortoise-Community/Tortoise-Bot", "forks_count": 21, @@ -24,7 +24,7 @@ "short_desc": "Fully functional Bot for Discord coded in Discord.py", }, { - "name": "Runtime-Bot", + "name": "runtime-bot", "html_url": "https://github.com/Tortoise-Community/Runtime-Bot", "web_link": "https://github.com/Tortoise-Community/Runtime-Bot", "forks_count": 1, @@ -35,7 +35,7 @@ "short_desc": "Discord bot for executing code directly in chat using the Hermes sandbox engine.", }, { - "name": "Snappy-Bot", + "name": "snappy-bot", "html_url": "https://github.com/Tortoise-Community/Snappy-Bot", "web_link": "https://github.com/Tortoise-Community/Snappy-Bot", "forks_count": 1, @@ -46,20 +46,20 @@ "short_desc": "Snappy is a lightweight Discord bot built using discord.py v2+", }, { - "name": "Backend", - "html_url": "https://github.com/Tortoise-Community/Backend", - "web_link": "https://github.com/Tortoise-Community/Backend", + "name": "site-backend", + "html_url": "https://github.com/Tortoise-Community/Site-Backend", + "web_link": "https://github.com/Tortoise-Community/Site-Backend", "forks_count": 1, "commit_count": 573, "stargazers_count": 8, "contributors_count": 0, - "language": "Python", + "language": "Django", "short_desc": "Website build with django for the Tortoise Community discord server", }, { - "name": "Frontend", - "html_url": "https://github.com/Tortoise-Community/Frontend", - "web_link": "https://github.com/Tortoise-Community/Frontend", + "name": "site-frontend", + "html_url": "https://github.com/Tortoise-Community/Site-Frontend", + "web_link": "https://github.com/Tortoise-Community/Site-Frontend", "forks_count": 1, "commit_count": 119, "stargazers_count": 1, @@ -68,15 +68,15 @@ "short_desc": "Web frontend built with React for Tortoise Community discord server", }, { - "name": "BladeList", - "html_url": "https://github.com/Bladelist/Bladelist", - "web_link": "https://github.com/Bladelist/Bladelist", + "name": "code-studio", + "html_url": "https://github.com/Tortoise-Community/Code-Studio", + "web_link": "https://github.com/Tortoise-Community/Code-Studio", "forks_count": 7, "commit_count": 290, "stargazers_count": 9, "contributors_count": 0, - "language": "Django", - "short_desc": "An open-source Discord Bot and Server Listing site built with Django.", + "language": "React", + "short_desc": "An open-source platform for practising DSA with community-driven resources", }, ] diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index 527a85d..e2e13db 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -334,10 +334,10 @@ async def unban(self, interaction: discord.Interaction, user_id: str, reason: st await interaction.guild.unban(user=user, reason=reason) try: await user.send( + content=constants.server_link, embed=info( "You have been unbanned in Tortoise Community\n" - "Please use the below link to rejoin the server\n" - f"👉 [Invite Link]({constants.server_link}) ", + "Please use the invite link to rejoin the server\n", self.bot.user, "Ban Lifted!", "Welcome back to Tortoise Programming Community!", diff --git a/bot/cogs/tortoise_dm.py b/bot/cogs/tortoise_dm.py index e4a1c8d..1cc131f 100644 --- a/bot/cogs/tortoise_dm.py +++ b/bot/cogs/tortoise_dm.py @@ -634,7 +634,7 @@ async def update_staff_embed( except Exception: pass - async def create_mod_mail(self, user: discord.User, reason: str = "No reason provided.", source: str = "dm"): + async def create_mod_mail(self, user: discord.User, reason: str = "No reason provided.", source: str = "dm", ping=True): if user.id in self.pending_mod_mails: try: await user.send(embed=failure("You already have a pending mod mail, please be patient.")) @@ -654,7 +654,7 @@ async def create_mod_mail(self, user: discord.User, reason: str = "No reason pro view = ModMailAcceptView(self, user.id) msg = await self.staff_channel.send( - self.mod_mail_ping_role.mention, + self.mod_mail_ping_role.mention if ping else None, embed=submission_embed, view=view ) diff --git a/bot/manager.py b/bot/manager.py index d4e89f1..c0275ff 100644 --- a/bot/manager.py +++ b/bot/manager.py @@ -31,6 +31,7 @@ async def setup(self): messages INTEGER NOT NULL DEFAULT 0, active BOOLEAN NOT NULL DEFAULT FALSE, active_plus BOOLEAN NOT NULL DEFAULT FALSE, + auto_banned BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (guild_id, user_id) ) """ @@ -50,6 +51,32 @@ async def setup(self): """ ) + async def set_ban_status(self, guild_id: int, user_id: int, status: bool): + + await self.db.pool.execute( + """ + INSERT INTO activity (guild_id, user_id, auto_banned) + VALUES ($1, $2, $3) + ON CONFLICT (guild_id, user_id) + DO UPDATE + SET auto_banned = EXCLUDED.auto_banned + """, + guild_id, + user_id, + status, + ) + + async def is_auto_banned(self, guild_id: int, user_id: int) -> bool: + + return await self.db.pool.fetchval( + """ + SELECT auto_banned + FROM activity + WHERE guild_id = $1 AND user_id = $2 + """, + guild_id, + user_id + ) or False async def add_messages_bulk(self, guild_id: int, cache: dict[int, int]): From 1765ece323e8dc8eddced4ebbe16d3672eca592a Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 29 May 2026 01:57:26 +0530 Subject: [PATCH 13/17] Disable option selection --- bot/cogs/button_utility.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/cogs/button_utility.py b/bot/cogs/button_utility.py index cb10e1b..5594e85 100644 --- a/bot/cogs/button_utility.py +++ b/bot/cogs/button_utility.py @@ -55,6 +55,8 @@ async def callback(self, interaction: discord.Interaction): self.disabled = True + await interaction.edit_original_response(view=self.view) + await interaction.response.defer(ephemeral=True, thinking=True) if reason == "accidental_trap_victim": From c7681258d9d17d5ed0461bdaf94ed856e55feb11 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 29 May 2026 02:04:07 +0530 Subject: [PATCH 14/17] Disable option selection --- bot/cogs/button_utility.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/cogs/button_utility.py b/bot/cogs/button_utility.py index 5594e85..fa874e1 100644 --- a/bot/cogs/button_utility.py +++ b/bot/cogs/button_utility.py @@ -55,11 +55,9 @@ async def callback(self, interaction: discord.Interaction): self.disabled = True - await interaction.edit_original_response(view=self.view) - - await interaction.response.defer(ephemeral=True, thinking=True) - if reason == "accidental_trap_victim": + await interaction.response.edit_message(view=self.view) + is_banned = await self.cog.bot.progression_manager.is_auto_banned(user_id=user.id, guild_id=tortoise_guild_id) @@ -103,8 +101,10 @@ async def callback(self, interaction: discord.Interaction): ) else: - await interaction.edit_original_response(content="⏳ Processing your request and opening a ticket...", - view=None) + await interaction.response.edit_message( + content="⏳ Processing your request and opening a ticket...", + view=self.view + ) try: embed = info( From 274c1f5bed02a26f18d0ae5fb6e60b2e5eabbea1 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 29 May 2026 02:23:57 +0530 Subject: [PATCH 15/17] Update --- bot/cogs/button_utility.py | 17 ++++++++++++----- bot/constants.py | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/bot/cogs/button_utility.py b/bot/cogs/button_utility.py index fa874e1..96eb423 100644 --- a/bot/cogs/button_utility.py +++ b/bot/cogs/button_utility.py @@ -6,10 +6,10 @@ from bot.constants import ( challenger_role_id, accepting_team_invites_role_id, tortoise_guild_id, - join_a_team_channel_id, teams_dashboard_message_id, server_link + join_a_team_channel_id, teams_dashboard_message_id, server_link, bot_avatar_url ) from bot.utils.checks import tortoise_bot_developer_only -from bot.utils.embed_handler import info, failure +from bot.utils.embed_handler import info, failure, success class TicketReasonSelect(discord.ui.Select): @@ -310,7 +310,11 @@ async def post_challenge_notification( description="Post the mod mail contact panel." ) @app_commands.check(tortoise_bot_developer_only) - async def post_panel(self, interaction: discord.Interaction): + async def post_panel(self, interaction: discord.Interaction, channel_id: int): + + channel = interaction.guild.get_channel(channel_id) + + await channel.purge(limit=1) embed = discord.Embed( title="Ban appeal", @@ -318,12 +322,15 @@ async def post_panel(self, interaction: discord.Interaction): color=discord.Color.dark_green() ) - embed.set_footer(text="Tortoise Programming Community", icon_url=self.bot.user.avatar.url) + embed.set_footer(text="Tortoise Programming Community", icon_url=bot_avatar_url) - await interaction.response.send_message( + await channel.send( embed=embed, view=ModMailStartView() ) + await interaction.response.send_message( + embed=success("Done") + ) @app_commands.command( name="post_team_invites_notification", diff --git a/bot/constants.py b/bot/constants.py index 5fb9838..e0b78b8 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -23,6 +23,7 @@ online_compiler_link = "https://execute.tortoisecommunity.org" runtime_bot_link = "https://runtime-bot.tortoisecommunity.org" online_viewer_url = "https://viewer.tortoisecommunity.org" +bot_avatar_url = "https://lairesit.sirv.com/Tortoise/tortoise.png" # Channel IDs welcome_channel_id = 738731842538176522 From ddc52dacbf8d6e752f47bda55cb6ac6119e6a63d Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 29 May 2026 02:29:14 +0530 Subject: [PATCH 16/17] Update fix --- bot/cogs/button_utility.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bot/cogs/button_utility.py b/bot/cogs/button_utility.py index 96eb423..39069e4 100644 --- a/bot/cogs/button_utility.py +++ b/bot/cogs/button_utility.py @@ -310,9 +310,15 @@ async def post_challenge_notification( description="Post the mod mail contact panel." ) @app_commands.check(tortoise_bot_developer_only) - async def post_panel(self, interaction: discord.Interaction, channel_id: int): + async def post_panel(self, interaction: discord.Interaction, channel_id: str): + await interaction.response.defer(ephemeral=True) + try: + target_id = int(channel_id) + except ValueError: + await interaction.followup.send("❌ Please provide a valid numerical Channel ID.", ephemeral=True) + return - channel = interaction.guild.get_channel(channel_id) + channel = interaction.guild.get_channel(target_id) await channel.purge(limit=1) @@ -328,7 +334,7 @@ async def post_panel(self, interaction: discord.Interaction, channel_id: int): embed=embed, view=ModMailStartView() ) - await interaction.response.send_message( + await interaction.followup.send( embed=success("Done") ) From 252c6c5fd9ce4f8e66ab41f3a40ce0b184cd7d30 Mon Sep 17 00:00:00 2001 From: Ryuga Date: Fri, 29 May 2026 19:25:32 +0530 Subject: [PATCH 17/17] Update fix --- bot/cogs/teams.py | 9 ++++++++- bot/manager.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/bot/cogs/teams.py b/bot/cogs/teams.py index 4d31637..e7995c8 100644 --- a/bot/cogs/teams.py +++ b/bot/cogs/teams.py @@ -955,6 +955,7 @@ async def team_members(self, interaction: discord.Interaction): leader_display = leader_instance.mention if leader_instance else f"Unknown User (`{team['leader_id']}`)" member_mentions = [] + members_left = [] for row in db_members: user_id = row["user_id"] if user_id == team["leader_id"]: @@ -964,7 +965,7 @@ async def team_members(self, interaction: discord.Interaction): if member_m: member_mentions.append(f"`{member_m.display_name}`") else: - member_mentions.append(f"Unknown User (`{user_id}`)") + members_left.append(user_id) members_display = "\n".join(member_mentions) if member_mentions else "*No other members have joined yet.*" @@ -975,6 +976,12 @@ async def team_members(self, interaction: discord.Interaction): f"Team Profile: {team['name']}" ) + if members_left: + try: + await self.team.remove_members_bulk(team["team_id"], members_left) + except Exception: + pass + return await interaction.followup.send(embed=embed) diff --git a/bot/manager.py b/bot/manager.py index c0275ff..5678c3a 100644 --- a/bot/manager.py +++ b/bot/manager.py @@ -624,6 +624,19 @@ async def remove_member(self, team_id: int, user_id: int): WHERE team_id=$1 AND user_id=$2 """, team_id, user_id) + async def remove_members_bulk(self, team_id: int, user_ids: list[int]): + if not user_ids: + return + + await self.db.pool.execute( + """ + DELETE FROM team_members + WHERE team_id = $1 AND user_id = ANY($2) + """, + team_id, + user_ids + ) + async def get_user_team(self, guild_id: int, user_id: int): return await self.db.pool.fetchrow(""" SELECT * FROM team_members @@ -688,6 +701,7 @@ async def get_team_members(self, team_id: int): SELECT user_id FROM team_members WHERE team_id=$1 + ORDER BY joined_at ASC """, team_id) async def create_join_request(self, guild_id: int, team_id: int, user_id: int, reason: str = None) -> bool: