From ece196748c643c4f0de53c9adbbf75c05b7ccbcc Mon Sep 17 00:00:00 2001 From: Rosie Val Date: Tue, 28 Jul 2026 23:53:57 +0100 Subject: [PATCH] Add Wormhole 'Find Origin' right-click context menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right-click any relayed message -> Apps -> Find Origin resolves it back to the true source: origin server (name + ID), channel, author (name + ID), message ID, and a jump link. Uses the existing message_map reverse lookup, falling back to the same brute-force search across the network's member channels that _ctx_delete_message/wh mod nuke already rely on when the relay map doesn't have a direct answer. Mod-gated (deanonymizes real identity across servers). Registered independently of the existing bulk Report/Bookmark/Delete/Profile context-menu registration, which is currently disabled — left that decision alone rather than reviving it as a side effect. Co-Authored-By: Claude Sonnet 5 --- wormhole/core.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/wormhole/core.py b/wormhole/core.py index 8e62998..ae79685 100644 --- a/wormhole/core.py +++ b/wormhole/core.py @@ -160,6 +160,7 @@ class WormholeV4( self._bg_tasks.append(asyncio.create_task(self._poll_expiry_loop())) # Context menus disabled - eliminates registration complexity # self._register_context_menus() + self._register_find_origin_context_menu() self._ready.set() log.info("Wormhole v%s ready — %d networks loaded", self.__version__, len(nets)) @@ -195,7 +196,7 @@ class WormholeV4( task.cancel() # Remove context menus (must specify type=discord.AppCommandType.message) for cmd_name in ("Report to Wormhole", "Bookmark (Wormhole)", - "Delete from Network", "Wormhole Profile"): + "Delete from Network", "Wormhole Profile", "Find Origin"): self.bot.tree.remove_command(cmd_name, type=discord.AppCommandType.message) # ── Background task loops ────────────────────────────────────────────── @@ -741,4 +742,91 @@ class WormholeV4( ) await interaction.response.send_message(embed=em, ephemeral=True) + async def _ctx_find_origin(self, interaction: discord.Interaction, message: discord.Message) -> None: + """Context menu: resolve a relayed copy back to its true origin server/channel/author. + + The relay map only tracks relayed-copy id -> original id (there's no + direct index of "which channel is the origin for this message"), so + when the right-clicked message isn't itself the original, this falls + back to searching the network's member channels — the same technique + `_ctx_delete_message`/`wh mod nuke` already use for the same reason. + """ + net_name = await self._net_for_ch(message.channel.id) + if not net_name: + await interaction.response.send_message( + "This channel isn't part of a wormhole network.", ephemeral=True + ) + return + nd = await self._net(net_name) + if not has_role(nd, interaction.user.id, Role.MODERATOR) and not await self.bot.is_owner(interaction.user): + await interaction.response.send_message("You need Moderator role or higher.", ephemeral=True) + return + + orig_id = self.msg_map.get_original(net_name, message.id) or message.id + + if orig_id == message.id: + origin_message = message + else: + origin_message = None + for ch_id in nd.get("channels", []): + ch = self.bot.get_channel(ch_id) + if not ch: + continue + try: + origin_message = await ch.fetch_message(orig_id) + break + except (discord.NotFound, discord.Forbidden, discord.HTTPException): + continue + + if not origin_message: + await interaction.response.send_message( + "Couldn't locate the original message — it may have been deleted, " + "or predates the bot's current in-memory relay map.", + ephemeral=True, + ) + return + + # If what we resolved still looks like a relayed copy (bot/webhook authored) + # rather than a genuine human original, the map couldn't fully resolve it — + # say so plainly instead of confidently showing the wrong server as "the origin". + looks_relayed = origin_message.webhook_id is not None or origin_message.author.id == self.bot.user.id + if looks_relayed and orig_id == message.id: + await interaction.response.send_message( + "This looks like a relayed copy, but its true origin isn't in the " + "bot's in-memory relay map (it may predate the bot's current session).", + ephemeral=True, + ) + return + + guild = origin_message.guild + channel = origin_message.channel + author = origin_message.author + + embed = discord.Embed(title="🔎 Wormhole — Origin Found", colour=COLOUR_INFO) + embed.add_field(name="Server", value=f"{guild.name}\n`{guild.id}`" if guild else "Unknown", inline=True) + embed.add_field( + name="Channel", + value=channel.mention if hasattr(channel, "mention") else str(channel), + inline=True, + ) + embed.add_field(name="Author", value=f"{author}\n`{author.id}`", inline=True) + embed.add_field(name="Message ID", value=f"`{origin_message.id}`", inline=True) + embed.add_field(name="Jump to Message", value=f"[Click here]({origin_message.jump_url})", inline=True) + if author.display_avatar: + embed.set_thumbnail(url=author.display_avatar.url) + await interaction.response.send_message(embed=embed, ephemeral=True) + await self._audit(net_name, "ctx_find_origin", str(interaction.user), str(orig_id)) + + def _register_find_origin_context_menu(self) -> None: + """Register the "Find Origin" context menu independently of the bulk + Report/Bookmark/Delete/Profile registration above, which is currently + disabled — leaving that decision alone rather than reviving it as a + side effect of adding this one.""" + try: + self.bot.tree.remove_command("Find Origin", type=discord.AppCommandType.message) + except Exception: + pass + ctx_find_origin = app_commands.ContextMenu(name="Find Origin", callback=self._ctx_find_origin) + self.bot.tree.add_command(ctx_find_origin) + # ── Root command group is defined in commands/_base.py ────────────────