fork of https://f-hub.org/XMPP/xmpp-discord-bridge

Keep track of discord/xmpp msg counterparts for reply context

+47 -11
+2 -1
setup.py
··· 12 12 "requests==2.27.1", 13 13 "slixmpp @ git+https://codeberg.org/poezio/slixmpp.git@fix-muc-join", 14 14 "nextcord", 15 - "toml==0.10.2" 15 + "toml==0.10.2", 16 + "bidict" 16 17 ], 17 18 extra_require = { 18 19 "dev": [
+45 -10
xmpp_discord_bridge/main.py
··· 4 4 import asyncio 5 5 import signal 6 6 import base64 7 + from bidict import bidict 7 8 import hashlib 8 9 import hmac 10 + from typing import Dict 9 11 import urllib.parse 10 12 from optparse import OptionParser 11 13 12 14 import toml 13 15 import slixmpp 14 16 from slixmpp.plugins.xep_0359 import StanzaID 17 + from slixmpp.plugins.xep_0461.stanza import Reply 15 18 from slixmpp.stanza import Message as XMPPMessage 16 19 from slixmpp.componentxmpp import ComponentXMPP 17 20 from slixmpp.exceptions import XMPPError, IqError ··· 50 53 self._real_muc_users = {} # MUC -> [Resources] 51 54 self._guild_map = {} # Guild ID -> Channel ID -> MUC 52 55 self._muc_map = {} # MUC -> (Guild ID, Channel ID) 53 - self._msg_map = {} # MUC -> Discord Message ID -> (XMPP Message ID, JID, MUC Nick, Body) 56 + self._msg_map: Dict[str, bidict] = {} # MUC -> Discord Message ID <-> XMPP Message ID 57 + self._xmpp_msg_cache = {} # XMPP Message ID -> (JID, MUC Nick, Body) 58 + self._discord_msg_cache = {} # Discord Message ID -> (Content, Author Mention) 54 59 self._mucs = [] # List of known MUCs 55 60 self._webhooks = {} # MUC -> Webhook URL 56 61 ··· 65 70 66 71 register_stanza_plugin(XMPPMessage, OOBData) 67 72 register_stanza_plugin(XMPPMessage, StanzaID) 73 + register_stanza_plugin(XMPPMessage, Reply) 68 74 69 75 self.add_event_handler("session_start", self.on_session_start) 70 76 self.add_event_handler("groupchat_message", self.on_groupchat_message) ··· 135 141 136 142 # Initialise state tracking 137 143 self._muc_map[muc] = (guild, channel) 138 - self._msg_map[muc] = {} 144 + self._msg_map[muc] = bidict() 139 145 self._virtual_muc_users[muc] = [] 140 146 self._virtual_muc_nicks[muc] = {} 141 147 for member in dchannel.members: ··· 241 247 if not message["body"]: 242 248 return 243 249 if not message["from"].resource in self._real_muc_users[muc]: 250 + # We should cache our mirrored messages for future reference; stanza_id not known at time of sending 251 + stanza_id = message["stanza_id"]["id"] 252 + for discord_msg_id in self._discord_msg_cache.keys(): 253 + discord_content, discord_mention = self._discord_msg_cache[discord_msg_id] 254 + xmpp_body = message["body"] 255 + # remove reply fallback text for edge case in message content comparison 256 + if message["reply"]: 257 + xmpp_reply: Reply = message["reply"] 258 + xmpp_body = xmpp_reply.strip_fallback_content() 259 + # naive string comparison between our just-sent and newly-received xmpp message 260 + if discord_content == xmpp_body: 261 + self._msg_map[muc][discord_msg_id] = stanza_id 262 + self._logger.debug("Matched discord message %s with xmpp stanza_id %s", discord_msg_id, stanza_id) 263 + # content no longer needed, prevents duplicate matches 264 + self._discord_msg_cache[discord_msg_id] = (None, discord_mention) 244 265 return 245 266 # Prevent the message being reflected back into Discord 246 267 if not message["to"] == self._bot_jid_full: ··· 254 275 for member in self._discord.get_guild(guild).get_channel(channel).members: 255 276 self._logger.debug("Checking %s", member.display_name) 256 277 if "@" + member.display_name in content: 257 - self._logger.debug("Found mention for %s. Replaceing.", 278 + self._logger.debug("Found mention for %s. Replacing.", 258 279 member.display_name) 259 280 content = content.replace("@" + member.display_name, 260 281 member.mention) ··· 272 293 # Makes it feel more native. 273 294 content = None 274 295 296 + if message["reply"]: 297 + xmpp_reply: Reply = message["reply"] 298 + discord_replied_id = self._msg_map[muc].inverse.get(xmpp_reply["id"]) 299 + if discord_replied_id and discord_replied_id in self._discord_msg_cache: 300 + _, mention = self._discord_msg_cache[discord_replied_id] 301 + # rewrite the fallback text to use a Discord-compatible ping 302 + fallback = xmpp_reply.get_fallback_body() 303 + if mention: 304 + fallback += mention + "\n" 305 + content = fallback + "\n" + xmpp_reply.strip_fallback_content() 306 + else: 307 + self._logger.warning("Cache miss for XMPP message with stanza id {}".format(xmpp_reply["id"])) 308 + 275 309 try: 276 310 webhook_msg: WebhookMessage = await self._webhooks[muc].send(content=content, 277 311 username=message["from"].resource, ··· 280 314 wait=True 281 315 ) 282 316 stanza_id = message["stanza_id"]["id"] 283 - self._msg_map[muc][webhook_msg.id] = (stanza_id, message["from"], message.get_mucnick(), content) 317 + self._msg_map[muc][webhook_msg.id] = stanza_id 318 + self._xmpp_msg_cache[stanza_id] = (message["from"], message.get_mucnick(), content) 284 319 except Exception as err: 285 320 self._logger.error("Webhook execution failed", exc_info=err) 286 321 ··· 324 359 pstatus=pstatus 325 360 ) 326 361 self.plugin["xep_0045"].make_join_stanza(muc, 327 - nick=self._config["general"]["discord2xmpp_name_fmt"].format( 328 - display_name=member.display_name 329 - ), 362 + nick=member.display_name, 330 363 presence_options=presence).send() 331 364 332 365 async def on_discord_member_join(self, member): ··· 467 500 else: 468 501 content = msg.clean_content 469 502 503 + self._discord_msg_cache[msg.id] = (content, msg.author.mention) 504 + 470 505 if msg.type == MessageType.reply and msg.reference: 471 506 # get xmpp message corresponding to replied-to discord message 472 507 discord_msg_id = msg.reference.message_id 473 - xmpp_msg_data = self._msg_map[muc].get(discord_msg_id) 474 - if xmpp_msg_data: 475 - xmpp_msg_id, xmpp_msg_from, xmpp_msg_from_nick, xmpp_msg_content = xmpp_msg_data 508 + xmpp_msg_id = self._msg_map[muc].get(discord_msg_id) 509 + if xmpp_msg_id: 510 + xmpp_msg_from, xmpp_msg_from_nick, xmpp_msg_content = self._xmpp_msg_cache[xmpp_msg_id] 476 511 self.plugin["xep_0461"].send_reply( 477 512 xmpp_msg_from, 478 513 xmpp_msg_id,