Add existing code
This commit is contained in:
		
							parent
							
								
									3152aa732c
								
							
						
					
					
						commit
						fee845ef9c
					
				
							
								
								
									
										28
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								setup.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| from setuptools import setup, find_packages | ||||
| 
 | ||||
| setup( | ||||
|     name = "xmpp_discord_bridge", | ||||
|     version = "0.1.0", | ||||
|     url = "https://git.polynom.me/PapaTutuWawa/xmpp-discord-bridge", | ||||
|     author = "Alexander \"PapaTutuWawa\"", | ||||
|     author_email = "papatutuwawa <at> polynom.me", | ||||
|     license = "GPLc3", | ||||
|     packages = find_packages(), | ||||
|     install_requires = [ | ||||
|         "requests>=2.26.0", | ||||
|         "slixmpp>=1.7.1", | ||||
|         "discord.py>=1.7.3", | ||||
|         "toml>=0.10.2" | ||||
|     ], | ||||
|     extra_require = { | ||||
|         "dev": [ | ||||
|             "black" | ||||
|         ] | ||||
|     }, | ||||
|     zip_safe = True, | ||||
|     entry_points = { | ||||
|         "console_scripts": [ | ||||
|             "discord-xmpp-bridge = discord_xmpp_bridge.main:main" | ||||
|         ] | ||||
|     } | ||||
| ) | ||||
							
								
								
									
										0
									
								
								xmpp_discord_bridge/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								xmpp_discord_bridge/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										578
									
								
								xmpp_discord_bridge/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										578
									
								
								xmpp_discord_bridge/main.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,578 @@ | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| import asyncio | ||||
| import signal | ||||
| import base64 | ||||
| import hashlib | ||||
| import hmac | ||||
| import urllib.parse | ||||
| from optparse import OptionParser | ||||
| 
 | ||||
| import toml | ||||
| import slixmpp | ||||
| from slixmpp import Message | ||||
| from slixmpp.componentxmpp import ComponentXMPP | ||||
| from slixmpp.exceptions import XMPPError, IqError | ||||
| from slixmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
| from slixmpp.jid import JID | ||||
| import discord | ||||
| import requests | ||||
| 
 | ||||
| class OOBData(ElementBase): | ||||
|     name = "x" | ||||
|     namespace = "jabber:x:oob" | ||||
|     plugin_attrib = "oob" | ||||
|     interfaces = {"url"} | ||||
|     sub_interfaces = interfaces | ||||
| 
 | ||||
| class AvatarManager: | ||||
|     def __init__(self, xmpp, config): | ||||
|         self._xmpp = xmpp | ||||
|         self._path = config["avatars"]["path"] | ||||
|         self._public = config["avatars"]["url"] | ||||
| 
 | ||||
|         self._avatars = {} | ||||
| 
 | ||||
|         self._logger = logging.getLogger("xmpp.avatar") | ||||
| 
 | ||||
|     def save_avatar(self, jid, data, type_): | ||||
|         filename = hashlib.sha1(data).hexdigest() + "." + type_.split("/")[1] | ||||
|         path = os.path.join(self._path, filename) | ||||
| 
 | ||||
|         if os.path.exists(path): | ||||
|             self._logger.debug("Avatar for %s already exists, not saving it again", | ||||
|                                jid) | ||||
|         else: | ||||
|             with open(path, "wb") as f: | ||||
|                 f.write(data) | ||||
| 
 | ||||
|         self._avatars[jid] = filename | ||||
|          | ||||
|     async def try_xep_0153(self, jid): | ||||
|          try: | ||||
|             iq = await self._xmpp.plugin["xep_0054"].get_vcard(jid=jid, | ||||
|                                                                ifrom=self._xmpp._bot_jid_full) | ||||
|             type_ = iq["vcard_temp"]["PHOTO"]["TYPE"] | ||||
|             data = iq["vcard_temp"]["PHOTO"]["BINVAL"] | ||||
|             self.save_avatar(jid, data, type_) | ||||
|             return True | ||||
|          except IqError: | ||||
|             self._logger.debug("Avatar retrieval via XEP-0054/XEP-0153 failed. Probably no vCard for XEP-0054 published") | ||||
|             return False | ||||
| 
 | ||||
|     async def try_xep_0084(self, jid): | ||||
|         try: | ||||
|             iq = await self._xmpp.plugin["xep_0060"].get_items(jid=jid, | ||||
|                                                                node="urn:xmpp:avatar:data", | ||||
|                                                                max_items=1, | ||||
|                                                                ifrom=self._xmpp._bot_jid_full) | ||||
|         except IqError: | ||||
|             self._logger.debug("Avatar retrieval via XEP-0084 failed. Probably no avatar published or subscription model not fulfilled.") | ||||
|             return False | ||||
|          | ||||
|     async def aquire_avatar(self, jid): | ||||
|         # First try vCard via 0054/0153 | ||||
|         for f in [self.try_xep_0153, self.try_xep_0084]: | ||||
|             if await f(jid): | ||||
|                 self._logger.debug("Avatar retrieval successful for %s", | ||||
|                                    jid) | ||||
|                 return | ||||
|          | ||||
|         self._logger.debug("Avatar retrieval failed for %s. Giving up.", | ||||
|                            jid) | ||||
| 
 | ||||
|     def get_avatar(self, jid): | ||||
|         return self._avatars.get(jid, None) | ||||
| 
 | ||||
| class DiscordClient(discord.Client): | ||||
|     def __init__(self, xmpp, config): | ||||
|         intents = discord.Intents.default() | ||||
|         intents.members = True | ||||
|         intents.presences = True | ||||
|         intents.messages = True | ||||
|         intents.reactions = True | ||||
| 
 | ||||
|         discord.Client.__init__(self, intents=intents) | ||||
| 
 | ||||
|         self._xmpp = xmpp | ||||
|         self._config = config | ||||
|         self._logger = logging.getLogger("discord.client") | ||||
| 
 | ||||
|     async def on_ready(self): | ||||
|         await self._xmpp.on_discord_ready() | ||||
| 
 | ||||
|     async def on_message(self, message): | ||||
|         await self._xmpp.on_discord_message(message) | ||||
| 
 | ||||
|     async def on_member_update(self, before, after): | ||||
|         await self._xmpp.on_discord_member_update(before, after) | ||||
| 
 | ||||
|     async def on_member_join(self, member): | ||||
|         await self._xmpp.on_discord_member_join(member) | ||||
| 
 | ||||
|     async def on_member_leave(self, member): | ||||
|         await self._xmpp.on_discord_member_leave(member) | ||||
| 
 | ||||
|     async def on_guild_channel_update(self, before, after): | ||||
|         await self._xmpp.on_discord_channel_update(before, after) | ||||
| 
 | ||||
|     async def on_reaction(self, payload): | ||||
|         message = await (self.get_guild(payload.guild_id) | ||||
|                          .get_channel(payload.channel_id) | ||||
|                          .fetch_message(payload.message_id)) | ||||
|          | ||||
|         await self._xmpp.on_discord_reaction(payload.guild_id, | ||||
|                                              payload.channel_id, | ||||
|                                              payload.emoji.name, | ||||
|                                              message, | ||||
|                                              payload.user_id, | ||||
|                                              payload.event_type) | ||||
|         | ||||
|     async def on_raw_reaction_add(self, payload): | ||||
|         await self.on_reaction(payload) | ||||
| 
 | ||||
|     async def on_raw_reaction_remove(self, payload): | ||||
|         await self.on_reaction(payload) | ||||
| 
 | ||||
| def discord_status_to_xmpp_show(status): | ||||
|     return { | ||||
|         discord.Status.online: "available", | ||||
|         discord.Status.idle: "away", | ||||
|         discord.Status.dnd: "dnd", | ||||
|         discord.Status.do_not_disturb: "dnd", | ||||
|         discord.Status.invisible: "xa", # TODO: Kinda | ||||
|         discord.Status.offline: "unavailable" | ||||
|     }.get(status, "available") | ||||
|          | ||||
| class BridgeComponent(ComponentXMPP): | ||||
|     def __init__(self, jid, secret, server, port, token, config): | ||||
|         ComponentXMPP.__init__(self, jid, secret, server, port) | ||||
| 
 | ||||
|         self._config = config | ||||
|         self._logger = logging.getLogger("xmpp.bridge") | ||||
|         self._domain = jid | ||||
|         self._bot_nick = "Bot" | ||||
|         self._bot_jid_bare = JID("bot@" + jid) | ||||
|         self._bot_jid_full = JID("bot@" + jid + "/" + self._bot_nick) | ||||
| 
 | ||||
|         self._token = token | ||||
|         self._discord = None | ||||
| 
 | ||||
|         self._avatars = AvatarManager(self, config) | ||||
|          | ||||
|         # State tracking | ||||
|         self._virtual_muc_users = {} # MUC -> [Resources] | ||||
|         self._virtual_muc_nicks = {} # MUC -> User ID -> Nick | ||||
|         self._real_muc_users = {} # MUC -> [Resources] | ||||
|         self._guild_map = {} # Guild ID -> Channel ID -> MUC | ||||
|         self._muc_map = {} # MUC -> (Guild ID, Channel ID) | ||||
|         self._mucs = [] # List of known MUCs | ||||
|         self._webhooks = {} # MUC -> Webhook URL | ||||
| 
 | ||||
|         register_stanza_plugin(Message, OOBData) | ||||
|          | ||||
|         self.add_event_handler("session_start", self.on_session_start) | ||||
|         self.add_event_handler("groupchat_message", self.on_groupchat_message) | ||||
|         self.add_event_handler("groupchat_presence", self.on_groupchat_presence) | ||||
| 
 | ||||
|     async def send_oob_data(self, url, muc, member): | ||||
|         """ | ||||
|         Send a message using XEP-0066 OOB data | ||||
|         """ | ||||
|         proxy = self._config["general"].get("proxy_discord_urls_to", None) | ||||
|         if proxy: | ||||
|             hmac_str = urllib.parse.quote(base64.b64encode(hmac.digest(self._config["general"]["hmac_secret"].encode(), | ||||
|                                                                    url.encode(), | ||||
|                                                                    "sha256")), safe="") | ||||
|             proxy = proxy.replace("<hmac>", hmac_str).replace("<url>", urllib.parse.quote(url, safe="")) | ||||
|             url = proxy | ||||
| 
 | ||||
|         self._logger.debug("OOB URL: %s", url) | ||||
|         msg = self.make_message(muc, | ||||
|                                 mbody=url, | ||||
|                                 mtype="groupchat", | ||||
|                                 mfrom=self.spoof_member_jid(member.id)) | ||||
|         msg["oob"]["url"] = url | ||||
|         msg.send() | ||||
|          | ||||
|     def spoof_member_jid(self, id_): | ||||
|         """ | ||||
|         Return a full JID that we use for the puppets | ||||
|         """ | ||||
|         return JID(str(id_) + "@" + self._domain + "/discord") | ||||
| 
 | ||||
|     async def on_sigint(self): | ||||
|         await self._discord.close() | ||||
| 
 | ||||
|         # Remove all virtual users | ||||
|         # NOTE: We cannot use leave_muc as this would also remove the MUC | ||||
|         #       from slixmpp's internal tracking, which would probably break | ||||
|         #       later leaves | ||||
|         for muc in self._mucs: | ||||
|             for uid in self._virtual_muc_nicks[muc]: | ||||
|                 nick = self._virtual_muc_nicks[muc][uid] | ||||
|                 self.send_presence(pshow='unavailable', | ||||
|                                    pto="%s/%s" % (muc, nick), | ||||
|                                    pfrom=self.spoof_member_jid(uid)) | ||||
| 
 | ||||
|             # Remove the Bot user | ||||
|             self.send_presence(pshow='unavailable', | ||||
|                                pto="%s/%s" % (muc, "Bot"), | ||||
|                                pfrom=self._bot_jid_full) | ||||
| 
 | ||||
|         # Disconnect and close | ||||
|         await self.disconnect() | ||||
|      | ||||
|     async def on_discord_ready(self): | ||||
|         asyncio.get_event_loop().add_signal_handler(signal.SIGINT, | ||||
|                                                       lambda: asyncio.create_task(self.on_sigint())) | ||||
| 
 | ||||
|         for ch in self._config["discord"]["channels"]: | ||||
|             muc = ch["muc"] | ||||
|             channel = ch["channel"] | ||||
|             guild = ch["guild"] | ||||
|             dchannel = self._discord.get_channel(channel) | ||||
| 
 | ||||
|             # Initialise state tracking | ||||
|             self._muc_map[muc] = (guild, channel) | ||||
|             self._virtual_muc_users[muc] = [] | ||||
|             self._virtual_muc_nicks[muc] = {} | ||||
|             for member in dchannel.members: | ||||
|                 if member.status == discord.Status.offline: | ||||
|                     continue | ||||
|                  | ||||
|                 self._virtual_muc_users[muc].append(member.display_name) | ||||
|                 self._virtual_muc_nicks[muc][member.id] = member.display_name | ||||
|             self._real_muc_users[muc] = [] | ||||
|             self._mucs.append(muc) | ||||
|             if not guild in self._guild_map: | ||||
|                 self._guild_map[guild] = { | ||||
|                     channel: muc | ||||
|                 } | ||||
|             else: | ||||
|                 self._guild_map[guild][channel] = muc | ||||
| 
 | ||||
|             self._logger.debug("Joining %s", muc) | ||||
|             self.plugin["xep_0045"].join_muc(muc, | ||||
|                                              nick=self._bot_nick, | ||||
|                                              pfrom=self._bot_jid_full) | ||||
| 
 | ||||
|             # Set the subject | ||||
|             subject = dchannel.topic or "" | ||||
|             self.plugin["xep_0045"].set_subject(muc, | ||||
|                                                 subject, | ||||
|                                                 mfrom=self._bot_jid_full) | ||||
| 
 | ||||
|             # TODO: Is this working? | ||||
|             # Mirror the guild's icon | ||||
|             icon = await dchannel.guild.icon_url_as(static_format="png", | ||||
|                                                     format="png", | ||||
|                                                     size=128).read() | ||||
|             vcard = self.plugin["xep_0054"].make_vcard() | ||||
|             vcard["PHOTO"]["TYPE"] = "image/png" | ||||
|             vcard["PHOTO"]["BINVAL"] = base64.b64encode(icon) | ||||
|             self.send_raw(""" | ||||
| <iq type="set" from="{}" to="{}"> | ||||
|     <vCard xmlns="vcard-temp"> | ||||
| {} | ||||
|     </vCard> | ||||
| </iq> | ||||
|             """.format(self._bot_jid_full, | ||||
|                        muc, | ||||
|                        str(vcard))) | ||||
|              | ||||
|             # Aquire a webhook | ||||
|             webhook_url = "" | ||||
|             for webhook in await dchannel.webhooks(): | ||||
|                 if webhook.name == "discord-xmpp-bridge": | ||||
|                     webhook_url = webhook.url | ||||
|                     break | ||||
|             if not webhook_url: | ||||
|                 webhook = dchannel.create_webhook(name="discord-xmpp-bridge", | ||||
|                                                   reason="Bridging Discord and XMPP") | ||||
|                 webhook_url = webhook.url | ||||
|             self._webhooks[muc] = webhook_url | ||||
| 
 | ||||
|             # Make sure our virtual users can join | ||||
|             affiliation_delta = [ | ||||
|                 (self.spoof_member_jid(member.id).bare, "member") for member in dchannel.members | ||||
|             ] | ||||
|             await self.plugin["xep_0045"].send_affiliation_list(muc, | ||||
|                                                                 affiliation_delta, | ||||
|                                                                 ifrom=self._bot_jid_full) | ||||
|              | ||||
|             for member in dchannel.members: | ||||
|                 self.virtual_user_join_muc(muc, member, update_state_tracking=False) | ||||
| 
 | ||||
|             self._logger.info("%s is ready", muc) | ||||
|         self._logger.info("Bridge is ready") | ||||
| 
 | ||||
|     async def on_groupchat_message(self, message): | ||||
|         muc = message["from"].bare | ||||
|         if not message["body"]: | ||||
|             return | ||||
|         if not message["from"].resource in self._real_muc_users[muc]: | ||||
|             return | ||||
|         # Prevent the message being reflected back into Discord | ||||
|         if not message["to"] == self._bot_jid_full: | ||||
|             return | ||||
| 
 | ||||
|         webhook = { | ||||
|             "content": message["body"], | ||||
|             "username": message["from"].resource | ||||
|         } | ||||
| 
 | ||||
|         if self._config["general"]["relay_xmpp_avatars"] and self._avatars.get_avatar(message["from"]): | ||||
|             webhook["avatar_url"] = self._avatar.get_avatar(message["from"]) | ||||
| 
 | ||||
|         # Look for mentions and replace them | ||||
|         guild, channel = self._muc_map[muc] | ||||
|         for member in self._discord.get_guild(guild).get_channel(channel).members: | ||||
|             self._logger.debug("Checking %s", member.display_name) | ||||
|             if "@" + member.display_name in webhook["content"]: | ||||
|                 self._logger.debug("Found mention for %s. Replaceing.", | ||||
|                                    member.display_name) | ||||
|                 webhook["content"] = webhook["content"].replace("@" + member.display_name, | ||||
|                                                                 member.mention) | ||||
|              | ||||
|         if message["oob"]["url"] and message["body"] == message["oob"]["url"]: | ||||
|             webhook["embed"] = [{ | ||||
|                 "type": "rich", | ||||
|                 "url": message["oob"]["url"] | ||||
|             }] | ||||
|              | ||||
|         requests.post(self._webhooks[muc], | ||||
|                       data=webhook) | ||||
| 
 | ||||
|     def virtual_user_update_presence(self, muc, uid, pshow): | ||||
|         """ | ||||
|         Change the status of a virtual MUC member. | ||||
|         NOTE: This assumes that the user is in the MUC | ||||
|         """ | ||||
|         self.send_presence(pshow=pshow, | ||||
|                            pto="%s/%s" % (muc, self._virtual_muc_nicks[muc][uid]), | ||||
|                            pfrom=self.spoof_member_jid(uid)) | ||||
|          | ||||
|     def virtual_user_join_muc(self, muc, member, update_state_tracking=False): | ||||
|         """ | ||||
|         Makes the a puppet of member (@discord.Member) join the | ||||
|         MUC. Does nothing if the member is offline. | ||||
| 
 | ||||
|         If update_state_tracking is True, then _virtual_muc_... gets updates. | ||||
|         """ | ||||
|         if member.status == discord.Status.offline and not self._config["general"]["dont_ignore_offline"]: | ||||
|             return | ||||
| 
 | ||||
|         if update_state_tracking: | ||||
|             self._virtual_muc_users[muc].append(member.display_name) | ||||
|             self._virtual_muc_nicks[muc][member.id] = member.display_name | ||||
| 
 | ||||
|         # Prevent offline users from getting an unavailable presence by | ||||
|         # accident | ||||
|         pshow = discord_status_to_xmpp_show(member.status) | ||||
|         if member.status == discord.Status.offline: | ||||
|             pshow = "xa" | ||||
| 
 | ||||
|         self.plugin["xep_0045"].join_muc(muc, | ||||
|                                          nick=member.display_name, | ||||
|                                          pfrom=self.spoof_member_jid(member.id), | ||||
|                                          pshow=pshow) | ||||
|          | ||||
|     async def on_discord_member_join(self, member): | ||||
|         guild = member.guild.id | ||||
|         if not guild in self._guild_map: | ||||
|             return | ||||
| 
 | ||||
|         self._logger.debug("%s joined a known guild. Updating channels.", | ||||
|                            member.display_name) | ||||
|         for channel in self._guild_map[guild]: | ||||
|             muc = self._guild_map[guild][channel] | ||||
|             self.virtual_user_join_muc(muc, member, update_state_tracking=True) | ||||
| 
 | ||||
|     async def on_discord_member_leave(self, member): | ||||
|         guild = member.guild.id | ||||
|         if not guild in self._guild_map: | ||||
|             return | ||||
| 
 | ||||
|         self._logger.debug("%s left a known guild. Updating channels.", | ||||
|                            member.display_name) | ||||
|         for channel in self._guild_map[guild]: | ||||
|             muc = self._guild_map[member.guild.id][channel] | ||||
|             self._virtual_muc_users[muc].remove(member.display_name) | ||||
|             del self._virtual_muc_nicks[muc][member.id] | ||||
| 
 | ||||
|             self.virtual_user_update_presence(muc, member.id, "unavailable") | ||||
| 
 | ||||
|              | ||||
|     async def on_discord_member_update(self, before, after): | ||||
|         guild = after.guild.id | ||||
|         if not guild in self._guild_map: | ||||
|             return | ||||
| 
 | ||||
|         # TODO: Handle nick changes | ||||
|         if before.status != after.status: | ||||
|             # Handle a status change | ||||
|             for channel in self._guild_map[guild]: | ||||
|                 muc = self._guild_map[guild][channel] | ||||
|                 if after.status == discord.Status.offline: | ||||
|                     if self._config["general"]["dont_ignore_offline"]: | ||||
|                         self.virtual_user_update_presence(muc, | ||||
|                                                           after.id, | ||||
|                                                           "xa") | ||||
|                     else: | ||||
|                         self._logger.debug("%s went offline. Removing from state tracking.", | ||||
|                                            after.display_name) | ||||
|                         self.virtual_user_update_presence(muc, | ||||
|                                                           after.id, | ||||
|                                                           discord_status_to_xmpp_show(after.status)) | ||||
| 
 | ||||
|                         # Remove from all state tracking | ||||
|                         self._virtual_muc_users[muc].remove(after.display_name) | ||||
|                         del self._virtual_muc_nicks[muc][after.id] | ||||
|                 elif before.status == discord.Status.offline and after.status != discord.Status.offline: | ||||
|                     self.virtual_user_join_muc(muc, after, update_state_tracking=True) | ||||
|                 else: | ||||
|                     self.virtual_user_update_presence(muc, | ||||
|                                                       after.id, | ||||
|                                                       discord_status_to_xmpp_show(after.status)) | ||||
| 
 | ||||
| 
 | ||||
|     async def on_discord_channel_update(self, before, after): | ||||
|         if after.type != discord.ChannelType.text: | ||||
|             return | ||||
| 
 | ||||
|         guild = after.guild.id | ||||
|         channel = after.id | ||||
|         if not guild in self._guild_map: | ||||
|             return | ||||
|         if not channel in self._guild_map[guild]: | ||||
|             return | ||||
| 
 | ||||
|         # NOTE: We can only really handle description changes | ||||
|         muc = self._guild_map[guild][channel] | ||||
|         if before.topic != after.topic: | ||||
|             self._logger.debug("Channel %s changed the topic. Relaying.", | ||||
|                                after.name) | ||||
|             self.plugin["xep_0045"].set_subject(muc, | ||||
|                                                 after.topic or "", | ||||
|                                                 mfrom=self._bot_jid_full) | ||||
| 
 | ||||
|     async def on_discord_reaction(self, guild, channel, emoji_str, msg, uid, kind): | ||||
|         """ | ||||
|         Handle a Discord reaction. | ||||
| 
 | ||||
|         reaction: discord.Reaction | ||||
|         user: discord.Member | ||||
|         kind: Either "add" or "remove" | ||||
|         """ | ||||
|         if not self._config["general"]["reactions_compat"]: | ||||
|             self._logger.debug("Got a reaction but reactions_compat is turned off. Ignoring.") | ||||
|             return | ||||
|         if not guild in self._guild_map: | ||||
|             return | ||||
|         if not channel in self._guild_map[guild]: | ||||
|             return | ||||
| 
 | ||||
|         muc = self._guild_map[guild][channel] | ||||
| 
 | ||||
|         # TODO: Handle attachments | ||||
|         content = "> " + msg.clean_content.replace("\n", "\n> ") + "\n" | ||||
|         content += "+" if kind == "REACTION_ADD" else "-" | ||||
|         content += " " + emoji_str | ||||
| 
 | ||||
|         self.send_message(mto=muc, | ||||
|                           mbody=content, | ||||
|                           mtype="groupchat", | ||||
|                           mfrom=self.spoof_member_jid(uid)) | ||||
| 
 | ||||
|     async def on_discord_message(self, msg): | ||||
|         guild, channel = msg.guild.id, msg.channel.id | ||||
|         if not (guild in self._guild_map and channel in self._guild_map[guild]): | ||||
|             return | ||||
| 
 | ||||
|         muc = self._guild_map[guild][channel] | ||||
|         if msg.author.bot and msg.author.display_name in self._real_muc_users[muc]: | ||||
|             return | ||||
| 
 | ||||
|         # TODO: Handle embeds | ||||
|         for attachment in msg.attachments: | ||||
|             await self.send_oob_data(attachment.url, muc, msg.author) | ||||
| 
 | ||||
|         if not msg.clean_content: | ||||
|             self._logger.debug("Message empty. Not relaying.") | ||||
|             return | ||||
| 
 | ||||
|         if self._config["general"]["muc_mention_compat"]: | ||||
|             mentions = [mention.display_name for mention in msg.mentions] | ||||
|             content = ", ".join(mentions) + ": " + msg.clean_content | ||||
|         else: | ||||
|             content = msg.clean_content | ||||
|          | ||||
|         self.send_message(mto=muc, | ||||
|                           mbody=content, | ||||
|                           mtype="groupchat", | ||||
|                           mfrom=self.spoof_member_jid(msg.author.id)) | ||||
| 
 | ||||
|     async def on_groupchat_presence(self, presence): | ||||
|         muc = presence["from"].bare | ||||
|         resource = presence["from"].resource | ||||
| 
 | ||||
|         if not muc in self._mucs: | ||||
|             self._logger.warn("Received presence in unknown MUC %s", muc) | ||||
|             return | ||||
|         if resource in self._virtual_muc_users[muc]: | ||||
|             return | ||||
|         if not presence["to"] == self._bot_jid_full: | ||||
|             return | ||||
|         if resource == self._bot_nick: | ||||
|             return | ||||
|          | ||||
|         if presence["type"] == "unavailable": | ||||
|             try: | ||||
|                 self._real_muc_users.remove(resource) | ||||
|             except: | ||||
|                 self._logger.debug("Trying to remove %s from %s, but user is not in list. Skipping...", | ||||
|                                    muc, | ||||
|                                    resource) | ||||
|         else: | ||||
|             await self._avatars.aquire_avatar(presence["from"]) | ||||
|             self._real_muc_users[muc].append(resource) | ||||
|          | ||||
|     async def on_session_start(self, event): | ||||
|         self._discord = DiscordClient(self, config) | ||||
|         asyncio.ensure_future(self._discord.start(self._token)) | ||||
| 
 | ||||
| def main(): | ||||
|     if os.path.exists("./config.toml"): | ||||
|         config = toml.load("./config.toml") | ||||
|     elif os.path.exists("/etc/discord-xmpp-bridge/config.toml"): | ||||
|         config = toml.load("/etc/discord-xmpp-bridge/config.toml") | ||||
|     else: | ||||
|         raise Exception("config.toml not found") | ||||
| 
 | ||||
|     parser = OptionParser() | ||||
|     parser.add_option( | ||||
|         "-d", "--debug", dest="debug", help="Enable debug logging", action="store_true" | ||||
|     ) | ||||
| 
 | ||||
|     (option, args) = parser.parse_args() | ||||
|     verbosity = logging.DEBUG if options.debug else logging.INFO | ||||
|      | ||||
|     general = config["general"] | ||||
|     xmpp = BridgeComponent(general["jid"], | ||||
|                            general["secret"], | ||||
|                            general["server"], | ||||
|                            general["port"], | ||||
|                            general["discord_token"], | ||||
|                            config) | ||||
|     for xep in [ "0030", "0199", "0045", "0084", "0153", "0054", "0060" ]: | ||||
|         xmpp.register_plugin(f"xep_{xep}") | ||||
| 
 | ||||
|     logging.basicConfig(stream=sys.stdout, level=verbosity) | ||||
|      | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
|          | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user