From be25187cb5bb8a8d69a2f7d356da3233a7705c99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 07:09:58 +0000 Subject: [PATCH 1/4] Add fallback for old Discord message edits Agent-Logs-Url: https://github.com/r-hensley/Rai/sessions/1b16eacd-42df-49ec-973c-dd8fc78c4295 Co-authored-by: r-hensley <30587035+r-hensley@users.noreply.github.com> --- cogs/events.py | 17 ++++++++++++++- tests/cogs/test_events.py | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/cogs/test_events.py diff --git a/cogs/events.py b/cogs/events.py index f8b28a53..54ad2d5c 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -36,6 +36,21 @@ class Events(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot + @staticmethod + async def edit_message_with_old_message_fallback(message: discord.Message, **kwargs): + try: + return await message.edit(**kwargs) + except discord.HTTPException as e: + if e.code != 30046: + raise + + try: + await message.delete() + except (discord.Forbidden, discord.NotFound, discord.HTTPException): + pass + + return await message.channel.send(**kwargs) + # for debugging infinite loops/crashes etc # @self.bot.event # async def on_message(msg: discord.Message): @@ -156,7 +171,7 @@ def on_member_update_check(m_before, m_after): f"~~User {refreshed_author.mention} has potentially sent a message with their native " f"language and is still untagged~~:\n" f">>> ~~[{msg_content}](<{reaction.message.jump_url}>)~~") - await sent_msg.edit(content=new_msg) + await self.edit_message_with_old_message_fallback(sent_msg, content=new_msg) utils.asyncio_task(check_untagged_jho_users) diff --git a/tests/cogs/test_events.py b/tests/cogs/test_events.py new file mode 100644 index 00000000..0ab08180 --- /dev/null +++ b/tests/cogs/test_events.py @@ -0,0 +1,46 @@ +import asyncio +from unittest.mock import AsyncMock, Mock + +import discord +import pytest + +from cogs.events import Events + + +class FakeHTTPException(discord.HTTPException): + def __init__(self, code: int, status: int = 429): + self.code = code + self.status = status + self.text = "fake http exception" + self.response = Mock() + self.args = (self.text,) + + +def test_edit_message_with_old_message_fallback_resends_old_messages(): + message = Mock() + replacement = Mock() + message.edit = AsyncMock(side_effect=FakeHTTPException(30046)) + message.delete = AsyncMock() + message.channel = Mock() + message.channel.send = AsyncMock(return_value=replacement) + + result = asyncio.run(Events.edit_message_with_old_message_fallback(message, content="updated")) + + message.edit.assert_awaited_once_with(content="updated") + message.delete.assert_awaited_once() + message.channel.send.assert_awaited_once_with(content="updated") + assert result is replacement + + +def test_edit_message_with_old_message_fallback_reraises_other_http_errors(): + message = Mock() + message.edit = AsyncMock(side_effect=FakeHTTPException(50035, status=400)) + message.delete = AsyncMock() + message.channel = Mock() + message.channel.send = AsyncMock() + + with pytest.raises(FakeHTTPException): + asyncio.run(Events.edit_message_with_old_message_fallback(message, content="updated")) + + message.delete.assert_not_called() + message.channel.send.assert_not_called() From 26827ecd69ad95f0a3693ff1dbac02c3b04a6e92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 07:11:26 +0000 Subject: [PATCH 2/4] Address review nits for message edit fallback Agent-Logs-Url: https://github.com/r-hensley/Rai/sessions/1b16eacd-42df-49ec-973c-dd8fc78c4295 Co-authored-by: r-hensley <30587035+r-hensley@users.noreply.github.com> --- cogs/events.py | 1 + tests/cogs/test_events.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cogs/events.py b/cogs/events.py index 54ad2d5c..e078065b 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -41,6 +41,7 @@ async def edit_message_with_old_message_fallback(message: discord.Message, **kwa try: return await message.edit(**kwargs) except discord.HTTPException as e: + # Discord returns error code 30046 when a message is too old to keep editing. if e.code != 30046: raise diff --git a/tests/cogs/test_events.py b/tests/cogs/test_events.py index 0ab08180..66ec7ca8 100644 --- a/tests/cogs/test_events.py +++ b/tests/cogs/test_events.py @@ -16,7 +16,7 @@ def __init__(self, code: int, status: int = 429): self.args = (self.text,) -def test_edit_message_with_old_message_fallback_resends_old_messages(): +def test_edit_message_with_old_message_fallback_deletes_and_resends_when_message_too_old(): message = Mock() replacement = Mock() message.edit = AsyncMock(side_effect=FakeHTTPException(30046)) From c906128d7097edaf1597bfc9d2247e1fc4cee521 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 07:30:16 +0000 Subject: [PATCH 3/4] Adjust temporary tag notification cleanup logic Agent-Logs-Url: https://github.com/r-hensley/Rai/sessions/de389e48-3846-4927-9da1-3591a421d6a7 Co-authored-by: r-hensley <30587035+r-hensley@users.noreply.github.com> --- cogs/events.py | 19 +++++++-------- tests/cogs/test_events.py | 50 +++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index e078065b..8ce51789 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -37,21 +37,20 @@ def __init__(self, bot: commands.Bot): self.bot = bot @staticmethod - async def edit_message_with_old_message_fallback(message: discord.Message, **kwargs): - try: - return await message.edit(**kwargs) - except discord.HTTPException as e: - # Discord returns error code 30046 when a message is too old to keep editing. - if e.code != 30046: - raise + async def edit_or_delete_temporary_notification(message: discord.Message, **kwargs): + if getattr(message.channel, "last_message_id", None) == message.id: + try: + return await message.edit(**kwargs) + except discord.HTTPException as e: + # Discord returns error code 30046 when a message is too old to keep editing. + if e.code != 30046: + raise try: await message.delete() except (discord.Forbidden, discord.NotFound, discord.HTTPException): pass - return await message.channel.send(**kwargs) - # for debugging infinite loops/crashes etc # @self.bot.event # async def on_message(msg: discord.Message): @@ -172,7 +171,7 @@ def on_member_update_check(m_before, m_after): f"~~User {refreshed_author.mention} has potentially sent a message with their native " f"language and is still untagged~~:\n" f">>> ~~[{msg_content}](<{reaction.message.jump_url}>)~~") - await self.edit_message_with_old_message_fallback(sent_msg, content=new_msg) + await self.edit_or_delete_temporary_notification(sent_msg, content=new_msg) utils.asyncio_task(check_untagged_jho_users) diff --git a/tests/cogs/test_events.py b/tests/cogs/test_events.py index 66ec7ca8..960f7ac5 100644 --- a/tests/cogs/test_events.py +++ b/tests/cogs/test_events.py @@ -16,31 +16,61 @@ def __init__(self, code: int, status: int = 429): self.args = (self.text,) -def test_edit_message_with_old_message_fallback_deletes_and_resends_when_message_too_old(): +def test_edit_or_delete_temporary_notification_edits_latest_message(): message = Mock() - replacement = Mock() + updated = Mock() + message.id = 123 + message.edit = AsyncMock(return_value=updated) + message.delete = AsyncMock() + message.channel = Mock() + message.channel.last_message_id = 123 + + result = asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) + + message.edit.assert_awaited_once_with(content="updated") + message.delete.assert_not_called() + assert result is updated + + +def test_edit_or_delete_temporary_notification_deletes_non_latest_message_without_editing(): + message = Mock() + message.id = 123 + message.edit = AsyncMock() + message.delete = AsyncMock() + message.channel = Mock() + message.channel.last_message_id = 456 + + result = asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) + + message.edit.assert_not_called() + message.delete.assert_awaited_once() + assert result is None + + +def test_edit_or_delete_temporary_notification_deletes_when_latest_message_is_too_old_to_edit(): + message = Mock() + message.id = 123 message.edit = AsyncMock(side_effect=FakeHTTPException(30046)) message.delete = AsyncMock() message.channel = Mock() - message.channel.send = AsyncMock(return_value=replacement) + message.channel.last_message_id = 123 - result = asyncio.run(Events.edit_message_with_old_message_fallback(message, content="updated")) + result = asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) message.edit.assert_awaited_once_with(content="updated") message.delete.assert_awaited_once() - message.channel.send.assert_awaited_once_with(content="updated") - assert result is replacement + assert result is None -def test_edit_message_with_old_message_fallback_reraises_other_http_errors(): +def test_edit_or_delete_temporary_notification_reraises_other_http_errors(): message = Mock() + message.id = 123 message.edit = AsyncMock(side_effect=FakeHTTPException(50035, status=400)) message.delete = AsyncMock() message.channel = Mock() - message.channel.send = AsyncMock() + message.channel.last_message_id = 123 with pytest.raises(FakeHTTPException): - asyncio.run(Events.edit_message_with_old_message_fallback(message, content="updated")) + asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) message.delete.assert_not_called() - message.channel.send.assert_not_called() From f0d03f636b78ed18a307b6de43b232a6510f532f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 07:52:49 +0000 Subject: [PATCH 4/4] Inline tag notification cleanup logic Agent-Logs-Url: https://github.com/r-hensley/Rai/sessions/064ee40f-9108-4140-b17e-ab62352677cc Co-authored-by: r-hensley <30587035+r-hensley@users.noreply.github.com> --- cogs/events.py | 29 +++++++-------- tests/cogs/test_events.py | 76 --------------------------------------- 2 files changed, 13 insertions(+), 92 deletions(-) delete mode 100644 tests/cogs/test_events.py diff --git a/cogs/events.py b/cogs/events.py index 8ce51789..ef172880 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -36,21 +36,6 @@ class Events(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - @staticmethod - async def edit_or_delete_temporary_notification(message: discord.Message, **kwargs): - if getattr(message.channel, "last_message_id", None) == message.id: - try: - return await message.edit(**kwargs) - except discord.HTTPException as e: - # Discord returns error code 30046 when a message is too old to keep editing. - if e.code != 30046: - raise - - try: - await message.delete() - except (discord.Forbidden, discord.NotFound, discord.HTTPException): - pass - # for debugging infinite loops/crashes etc # @self.bot.event # async def on_message(msg: discord.Message): @@ -171,7 +156,19 @@ def on_member_update_check(m_before, m_after): f"~~User {refreshed_author.mention} has potentially sent a message with their native " f"language and is still untagged~~:\n" f">>> ~~[{msg_content}](<{reaction.message.jump_url}>)~~") - await self.edit_or_delete_temporary_notification(sent_msg, content=new_msg) + if getattr(sent_msg.channel, "last_message_id", None) == sent_msg.id: + try: + await sent_msg.edit(content=new_msg) + return + except discord.HTTPException as e: + # Discord returns error code 30046 when a message is too old to keep editing. + if e.code != 30046: + raise + + try: + await sent_msg.delete() + except (discord.Forbidden, discord.NotFound, discord.HTTPException): + pass utils.asyncio_task(check_untagged_jho_users) diff --git a/tests/cogs/test_events.py b/tests/cogs/test_events.py deleted file mode 100644 index 960f7ac5..00000000 --- a/tests/cogs/test_events.py +++ /dev/null @@ -1,76 +0,0 @@ -import asyncio -from unittest.mock import AsyncMock, Mock - -import discord -import pytest - -from cogs.events import Events - - -class FakeHTTPException(discord.HTTPException): - def __init__(self, code: int, status: int = 429): - self.code = code - self.status = status - self.text = "fake http exception" - self.response = Mock() - self.args = (self.text,) - - -def test_edit_or_delete_temporary_notification_edits_latest_message(): - message = Mock() - updated = Mock() - message.id = 123 - message.edit = AsyncMock(return_value=updated) - message.delete = AsyncMock() - message.channel = Mock() - message.channel.last_message_id = 123 - - result = asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) - - message.edit.assert_awaited_once_with(content="updated") - message.delete.assert_not_called() - assert result is updated - - -def test_edit_or_delete_temporary_notification_deletes_non_latest_message_without_editing(): - message = Mock() - message.id = 123 - message.edit = AsyncMock() - message.delete = AsyncMock() - message.channel = Mock() - message.channel.last_message_id = 456 - - result = asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) - - message.edit.assert_not_called() - message.delete.assert_awaited_once() - assert result is None - - -def test_edit_or_delete_temporary_notification_deletes_when_latest_message_is_too_old_to_edit(): - message = Mock() - message.id = 123 - message.edit = AsyncMock(side_effect=FakeHTTPException(30046)) - message.delete = AsyncMock() - message.channel = Mock() - message.channel.last_message_id = 123 - - result = asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) - - message.edit.assert_awaited_once_with(content="updated") - message.delete.assert_awaited_once() - assert result is None - - -def test_edit_or_delete_temporary_notification_reraises_other_http_errors(): - message = Mock() - message.id = 123 - message.edit = AsyncMock(side_effect=FakeHTTPException(50035, status=400)) - message.delete = AsyncMock() - message.channel = Mock() - message.channel.last_message_id = 123 - - with pytest.raises(FakeHTTPException): - asyncio.run(Events.edit_or_delete_temporary_notification(message, content="updated")) - - message.delete.assert_not_called()