diff --git a/setup.py b/setup.py index 4bb5b8c..da98b32 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ setup( zip_safe = True, entry_points = { "console_scripts": [ - "discord-xmpp-bridge = discord_xmpp_bridge.main:main" + "xmpp-discord-bridge = xmpp_discord_bridge.main:main" ] } ) diff --git a/xmpp_discord_bridge/avatar.py b/xmpp_discord_bridge/avatar.py index e0d9505..a80e82a 100644 --- a/xmpp_discord_bridge/avatar.py +++ b/xmpp_discord_bridge/avatar.py @@ -1,8 +1,15 @@ import logging +import hashlib +import os from slixmpp.exceptions import IqError class AvatarManager: + """ + The AvatarManager class is responsible for aquiring the XMPP avatars + using the means of either XEP-0153 (XEP-0054), XEP-0084 or (in the future) + XEP-0292, storing and providing a publicly available URL to those avatars. + """ def __init__(self, xmpp, config): self._xmpp = xmpp self._path = config["avatars"]["path"] @@ -12,7 +19,7 @@ class AvatarManager: self._logger = logging.getLogger("xmpp.avatar") - def save_avatar(self, jid, data, type_): + def _save_avatar(self, jid, data, type_): filename = hashlib.sha1(data).hexdigest() + "." + type_.split("/")[1] path = os.path.join(self._path, filename) @@ -31,7 +38,7 @@ class AvatarManager: ifrom=self._xmpp._bot_jid_full) type_ = iq["vcard_temp"]["PHOTO"]["TYPE"] data = iq["vcard_temp"]["PHOTO"]["BINVAL"] - self.save_avatar(jid, data, type_) + 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") @@ -43,12 +50,17 @@ class AvatarManager: node="urn:xmpp:avatar:data", max_items=1, ifrom=self._xmpp._bot_jid_full) + data = iq["pubsub"]["items"]["substanzas"][0]["data"] + self._save_avatar(jid, data, "image/png") + return True 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 + """ + Attempt to request the avatar of @jid + """ for f in [self.try_xep_0153, self.try_xep_0084]: if await f(jid): self._logger.debug("Avatar retrieval successful for %s", @@ -58,5 +70,8 @@ class AvatarManager: self._logger.debug("Avatar retrieval failed for %s. Giving up.", jid) - def get_avatar(self, jid): + def get_avatar_url(self, jid): + """ + Returns either the URL to the avatar of @jid or None. + """ return self._avatars.get(jid, None) diff --git a/xmpp_discord_bridge/helpers.py b/xmpp_discord_bridge/helpers.py index 978be34..f9b78c3 100644 --- a/xmpp_discord_bridge/helpers.py +++ b/xmpp_discord_bridge/helpers.py @@ -1,9 +1,11 @@ +from discord import Status + 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" + Status.online: "available", + Status.idle: "away", + Status.dnd: "dnd", + Status.do_not_disturb: "dnd", + Status.invisible: "xa", # TODO: Kinda + Status.offline: "unavailable" }.get(status, "available") diff --git a/xmpp_discord_bridge/main.py b/xmpp_discord_bridge/main.py index 4148ab0..a44119b 100644 --- a/xmpp_discord_bridge/main.py +++ b/xmpp_discord_bridge/main.py @@ -16,6 +16,7 @@ from slixmpp.componentxmpp import ComponentXMPP from slixmpp.exceptions import XMPPError, IqError from slixmpp.xmlstream import register_stanza_plugin from slixmpp.jid import JID +from discord import Status import requests from xmpp_discord_bridge.slixmpp.oob import OOBData @@ -124,7 +125,7 @@ class BridgeComponent(ComponentXMPP): self._virtual_muc_users[muc] = [] self._virtual_muc_nicks[muc] = {} for member in dchannel.members: - if member.status == discord.Status.offline: + if member.status == Status.offline and not self._dont_ignore_offline: continue self._virtual_muc_users[muc].append(member.display_name) @@ -208,8 +209,8 @@ class BridgeComponent(ComponentXMPP): "username": message["from"].resource } - if self._relay_xmpp_avatars and self._avatars.get_avatar(message["from"]): - webhook["avatar_url"] = self._avatar.get_avatar(message["from"]) + if self._relay_xmpp_avatars and self._avatars.get_avatar_url(message["from"]): + webhook["avatar_url"] = self._avatar.get_avatar_url(message["from"]) # Look for mentions and replace them guild, channel = self._muc_map[muc] @@ -230,12 +231,13 @@ class BridgeComponent(ComponentXMPP): requests.post(self._webhooks[muc], data=webhook) - def virtual_user_update_presence(self, muc, uid, pshow): + def virtual_user_update_presence(self, muc, uid, pshow, pstatus=None): """ Change the status of a virtual MUC member. NOTE: This assumes that the user is in the MUC """ self.send_presence(pshow=pshow, + pstatus=pstatus, pto="%s/%s" % (muc, self._virtual_muc_nicks[muc][uid]), pfrom=self.spoof_member_jid(uid)) @@ -246,7 +248,7 @@ class BridgeComponent(ComponentXMPP): If update_state_tracking is True, then _virtual_muc_... gets updates. """ - if member.status == discord.Status.offline and not self._dont_ignore_offline: + if member.status == Status.offline and not self._dont_ignore_offline: return if update_state_tracking: @@ -256,13 +258,18 @@ class BridgeComponent(ComponentXMPP): # Prevent offline users from getting an unavailable presence by # accident pshow = discord_status_to_xmpp_show(member.status) - if member.status == discord.Status.offline: + pstatus = "" + if member.status == Status.offline: pshow = "xa" + if self._dont_ignore_offline: + pstatus = "Offline" + self.plugin["xep_0045"].join_muc(muc, nick=member.display_name, pfrom=self.spoof_member_jid(member.id), - pshow=pshow) + pshow=pshow, + pstatus=pstatus) async def on_discord_member_join(self, member): guild = member.guild.id @@ -300,11 +307,12 @@ class BridgeComponent(ComponentXMPP): # Handle a status change for channel in self._guild_map[guild]: muc = self._guild_map[guild][channel] - if after.status == discord.Status.offline: + if after.status == Status.offline: if self._dont_ignore_offline: self.virtual_user_update_presence(muc, after.id, - "xa") + "xa", + "Offline") else: self._logger.debug("%s went offline. Removing from state tracking.", after.display_name) @@ -315,7 +323,7 @@ class BridgeComponent(ComponentXMPP): # 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: + elif before.status == Status.offline and after.status != Status.offline and not self._dont_ignore_offline: self.virtual_user_join_muc(muc, after, update_state_tracking=True) else: self.virtual_user_update_presence(muc, @@ -425,7 +433,7 @@ class BridgeComponent(ComponentXMPP): self._real_muc_users[muc].append(resource) async def on_session_start(self, event): - self._discord = DiscordClient(self, config) + self._discord = DiscordClient(self, self._config) asyncio.ensure_future(self._discord.start(self._token)) def main(): @@ -441,7 +449,7 @@ def main(): "-d", "--debug", dest="debug", help="Enable debug logging", action="store_true" ) - (option, args) = parser.parse_args() + (options, args) = parser.parse_args() verbosity = logging.DEBUG if options.debug else logging.INFO general = config["general"]