From a5b24d6caa6a7aa1122cd8e93559b2e531ad2642 Mon Sep 17 00:00:00 2001 From: Muhammad Hasan Date: Sat, 13 Dec 2025 20:52:12 +0500 Subject: [PATCH 1/7] Fix trashcan delete reaction in DMs --- bot/exts/core/help.py | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 2960d7222b..c360426987 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -4,7 +4,7 @@ from contextlib import suppress from typing import NamedTuple -from discord import Colour, Embed, HTTPException, Message, Reaction, User +from discord import Colour, Embed, HTTPException, Message, Reaction, User, RawReactionActionEvent, NotFound from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from pydis_core.utils.logging import get_logger @@ -209,6 +209,44 @@ async def on_reaction_add(self, reaction: Reaction, user: User) -> None: with suppress(HTTPException): await self.message.remove_reaction(reaction, user) + + async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: + """Handle raw reaction events for DM compatibility.""" + # Ignore if not our message + if payload.message_id != self.message.id: + return + + # Ignore if not the session author + if payload.user_id != self.author.id: + return + + # Get the emoji string + emoji = str(payload.emoji) + + # Check if valid action + if emoji not in REACTIONS: + return + + self.reset_timeout() + + # Run relevant action method + action = getattr(self, f"do_{REACTIONS[emoji]}", None) + if action: + await action() + + # Remove the reaction to prep for re-use + try: + # We need to get the actual message and user objects for remove_reaction + channel = self._bot.get_channel(payload.channel_id) + if channel: + message = await channel.fetch_message(payload.message_id) + user = self._bot.get_user(payload.user_id) + if user and message: + await message.remove_reaction(payload.emoji, user) + except (HTTPException, NotFound): + # Ignore errors when removing reactions + pass + async def on_message_delete(self, message: Message) -> None: """Closes the help session when the help message is deleted.""" if message.id == self.message.id: @@ -220,6 +258,7 @@ async def prepare(self) -> None: await self.update_page() self._bot.add_listener(self.on_reaction_add) + self._bot.add_listener(self.on_raw_reaction_add) self._bot.add_listener(self.on_message_delete) self.add_reactions() @@ -460,6 +499,7 @@ async def start(cls, ctx: Context, *command, **options) -> "HelpSession": async def stop(self) -> None: """Stops the help session, removes event listeners and attempts to delete the help message.""" self._bot.remove_listener(self.on_reaction_add) + self._bot.remove_listener(self.on_raw_reaction_add) self._bot.remove_listener(self.on_message_delete) # ignore if permission issue, or the message doesn't exist From cd7d01e0a768c0c43a3e9f194c8e4773010789ec Mon Sep 17 00:00:00 2001 From: Muhammad Hasan Date: Sat, 13 Dec 2025 20:59:47 +0500 Subject: [PATCH 2/7] run pre-commit hooks --- bot/exts/core/help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index c360426987..a86015aae0 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -4,7 +4,7 @@ from contextlib import suppress from typing import NamedTuple -from discord import Colour, Embed, HTTPException, Message, Reaction, User, RawReactionActionEvent, NotFound +from discord import Colour, Embed, HTTPException, Message, NotFound, RawReactionActionEvent, Reaction, User from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from pydis_core.utils.logging import get_logger From 5a6c7585f839b10be1d7e5245cbf4aa38282a52c Mon Sep 17 00:00:00 2001 From: Muhammad Hasan Date: Sun, 14 Dec 2025 14:09:50 +0500 Subject: [PATCH 3/7] remove try-except block for deleting reactions in on_raw_reaction_add --- bot/exts/core/help.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index a86015aae0..32b3af1aa1 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -4,7 +4,7 @@ from contextlib import suppress from typing import NamedTuple -from discord import Colour, Embed, HTTPException, Message, NotFound, RawReactionActionEvent, Reaction, User +from discord import Colour, Embed, HTTPException, Message, RawReactionActionEvent, Reaction, User from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from pydis_core.utils.logging import get_logger @@ -234,19 +234,6 @@ async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: if action: await action() - # Remove the reaction to prep for re-use - try: - # We need to get the actual message and user objects for remove_reaction - channel = self._bot.get_channel(payload.channel_id) - if channel: - message = await channel.fetch_message(payload.message_id) - user = self._bot.get_user(payload.user_id) - if user and message: - await message.remove_reaction(payload.emoji, user) - except (HTTPException, NotFound): - # Ignore errors when removing reactions - pass - async def on_message_delete(self, message: Message) -> None: """Closes the help session when the help message is deleted.""" if message.id == self.message.id: From 87970cccc712c735db51fb8388b9d674abc91545 Mon Sep 17 00:00:00 2001 From: Muhammad Hasan Date: Sat, 20 Dec 2025 12:28:41 +0500 Subject: [PATCH 4/7] remove all instances of on_reaction_add in help.py and readd reaction removal logic --- bot/exts/core/help.py | 44 ++++++++++++++----------------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 32b3af1aa1..9b899ded57 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -4,7 +4,7 @@ from contextlib import suppress from typing import NamedTuple -from discord import Colour, Embed, HTTPException, Message, RawReactionActionEvent, Reaction, User +from discord import Colour, Embed, HTTPException, Message, NotFound, RawReactionActionEvent from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from pydis_core.utils.logging import get_logger @@ -182,33 +182,6 @@ def reset_timeout(self) -> None: # recreate the timeout task self._timeout_task = self._bot.loop.create_task(self.timeout()) - async def on_reaction_add(self, reaction: Reaction, user: User) -> None: - """Event handler for when reactions are added on the help message.""" - # ensure it was the relevant session message - if reaction.message.id != self.message.id: - return - - # ensure it was the session author who reacted - if user.id != self.author.id: - return - - emoji = str(reaction.emoji) - - # check if valid action - if emoji not in REACTIONS: - return - - self.reset_timeout() - - # Run relevant action method - action = getattr(self, f"do_{REACTIONS[emoji]}", None) - if action: - await action() - - # remove the added reaction to prep for re-use - with suppress(HTTPException): - await self.message.remove_reaction(reaction, user) - async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: """Handle raw reaction events for DM compatibility.""" @@ -229,6 +202,19 @@ async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: self.reset_timeout() + # Remove the reaction to prep for re-use + try: + # We need to get the actual message and user objects for remove_reaction + channel = self._bot.get_channel(payload.channel_id) + if channel: + message = await channel.fetch_message(payload.message_id) + user = await self._bot.fetch_user(payload.user_id) + if user and message: + await message.remove_reaction(payload.emoji, user) + except (HTTPException, NotFound): + # Ignore errors when removing reactions + pass + # Run relevant action method action = getattr(self, f"do_{REACTIONS[emoji]}", None) if action: @@ -244,7 +230,6 @@ async def prepare(self) -> None: await self.build_pages() await self.update_page() - self._bot.add_listener(self.on_reaction_add) self._bot.add_listener(self.on_raw_reaction_add) self._bot.add_listener(self.on_message_delete) @@ -485,7 +470,6 @@ async def start(cls, ctx: Context, *command, **options) -> "HelpSession": async def stop(self) -> None: """Stops the help session, removes event listeners and attempts to delete the help message.""" - self._bot.remove_listener(self.on_reaction_add) self._bot.remove_listener(self.on_raw_reaction_add) self._bot.remove_listener(self.on_message_delete) From f48376e649c56600b603a05edbf88981abae17a2 Mon Sep 17 00:00:00 2001 From: Muhammad Hasan Date: Sat, 20 Dec 2025 15:53:08 +0500 Subject: [PATCH 5/7] check channel type and skip if it is a DM, minimize API calls by using PartialMessage instead of a message object --- bot/exts/core/help.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 9b899ded57..5e57f861db 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -4,7 +4,7 @@ from contextlib import suppress from typing import NamedTuple -from discord import Colour, Embed, HTTPException, Message, NotFound, RawReactionActionEvent +from discord import Colour, Embed, HTTPException, Message, NotFound, RawReactionActionEvent, ChannelType, Object from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from pydis_core.utils.logging import get_logger @@ -206,11 +206,9 @@ async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: try: # We need to get the actual message and user objects for remove_reaction channel = self._bot.get_channel(payload.channel_id) - if channel: - message = await channel.fetch_message(payload.message_id) - user = await self._bot.fetch_user(payload.user_id) - if user and message: - await message.remove_reaction(payload.emoji, user) + if channel.type != ChannelType.private: + message = channel.get_partial_message(payload.message_id) + await message.remove_reaction(payload.emoji, Object(id=payload.user_id)) except (HTTPException, NotFound): # Ignore errors when removing reactions pass From ec4f972ce4207d9faa4c0c9da78cbd922230e094 Mon Sep 17 00:00:00 2001 From: Muhammad Hasan Date: Sat, 20 Dec 2025 15:53:35 +0500 Subject: [PATCH 6/7] run pre-commit hooks --- bot/exts/core/help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 5e57f861db..8681929565 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -4,7 +4,7 @@ from contextlib import suppress from typing import NamedTuple -from discord import Colour, Embed, HTTPException, Message, NotFound, RawReactionActionEvent, ChannelType, Object +from discord import ChannelType, Colour, Embed, HTTPException, Message, NotFound, Object, RawReactionActionEvent from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from pydis_core.utils.logging import get_logger From dbdcc385f52225f24b006595322fef6381ce0012 Mon Sep 17 00:00:00 2001 From: Muhammad Hasan Date: Sat, 20 Dec 2025 16:08:21 +0500 Subject: [PATCH 7/7] simplify check for dm channel --- bot/exts/core/help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 8681929565..ac2aad547a 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -4,7 +4,7 @@ from contextlib import suppress from typing import NamedTuple -from discord import ChannelType, Colour, Embed, HTTPException, Message, NotFound, Object, RawReactionActionEvent +from discord import Colour, Embed, HTTPException, Message, NotFound, Object, RawReactionActionEvent from discord.ext import commands from discord.ext.commands import CheckFailure, Cog as DiscordCog, Command, Context from pydis_core.utils.logging import get_logger @@ -206,7 +206,7 @@ async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: try: # We need to get the actual message and user objects for remove_reaction channel = self._bot.get_channel(payload.channel_id) - if channel.type != ChannelType.private: + if channel: message = channel.get_partial_message(payload.message_id) await message.remove_reaction(payload.emoji, Object(id=payload.user_id)) except (HTTPException, NotFound):