Pull apart the main file

This commit is contained in:
PapaTutuWawa 2021-09-15 22:33:59 +02:00
parent fee845ef9c
commit 1fb4157794
6 changed files with 156 additions and 136 deletions

View File

@ -0,0 +1,62 @@
import logging
from slixmpp.exceptions import IqError
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)

View File

@ -0,0 +1,53 @@
import logging
import discord
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)

View File

@ -0,0 +1,9 @@
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")

View File

@ -14,136 +14,14 @@ import slixmpp
from slixmpp import Message from slixmpp import Message
from slixmpp.componentxmpp import ComponentXMPP from slixmpp.componentxmpp import ComponentXMPP
from slixmpp.exceptions import XMPPError, IqError from slixmpp.exceptions import XMPPError, IqError
from slixmpp.xmlstream import ElementBase, register_stanza_plugin from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.jid import JID from slixmpp.jid import JID
import discord
import requests import requests
class OOBData(ElementBase): from xmpp_discord_bridge.slixmpp.oob import OOBData
name = "x" from xmpp_discord_bridge.avatar import AvatarManager
namespace = "jabber:x:oob" from xmpp_discord_bridge.discord import DiscordClient
plugin_attrib = "oob" from xmpp_discord_bridge.helpers import discord_status_to_xmpp_show
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): class BridgeComponent(ComponentXMPP):
def __init__(self, jid, secret, server, port, token, config): def __init__(self, jid, secret, server, port, token, config):
@ -170,6 +48,14 @@ class BridgeComponent(ComponentXMPP):
self._mucs = [] # List of known MUCs self._mucs = [] # List of known MUCs
self._webhooks = {} # MUC -> Webhook URL self._webhooks = {} # MUC -> Webhook URL
# Settings
self._proxy_url_template = self._config["general"].get("proxy_discord_urls_to", "")
self._proxy_hmac_secret = self._config["general"].get("hmac_secret", "")
self._relay_xmpp_avatars = self._config["general"].get("relay_xmpp_avatars", False)
self._dont_ignore_offline = self._config["general"].get("dont_ignore_offline", True)
self._reactions_compat = self._config["general"].get("reactions_compat", True)
self._muc_mention_compat = self._config["general"].get("muc_mention_compat", True)
register_stanza_plugin(Message, OOBData) register_stanza_plugin(Message, OOBData)
self.add_event_handler("session_start", self.on_session_start) self.add_event_handler("session_start", self.on_session_start)
@ -180,12 +66,11 @@ class BridgeComponent(ComponentXMPP):
""" """
Send a message using XEP-0066 OOB data Send a message using XEP-0066 OOB data
""" """
proxy = self._config["general"].get("proxy_discord_urls_to", None) if self._proxy_url_template:
if proxy: hmac_str = urllib.parse.quote(base64.b64encode(hmac.digest(self._proxy_hmac_secret.encode(),
hmac_str = urllib.parse.quote(base64.b64encode(hmac.digest(self._config["general"]["hmac_secret"].encode(),
url.encode(), url.encode(),
"sha256")), safe="") "sha256")), safe="")
proxy = proxy.replace("<hmac>", hmac_str).replace("<url>", urllib.parse.quote(url, safe="")) proxy = self._proxy_url_template.replace("<hmac>", hmac_str).replace("<url>", urllib.parse.quote(url, safe=""))
url = proxy url = proxy
self._logger.debug("OOB URL: %s", url) self._logger.debug("OOB URL: %s", url)
@ -323,7 +208,7 @@ class BridgeComponent(ComponentXMPP):
"username": message["from"].resource "username": message["from"].resource
} }
if self._config["general"]["relay_xmpp_avatars"] and self._avatars.get_avatar(message["from"]): if self._relay_xmpp_avatars and self._avatars.get_avatar(message["from"]):
webhook["avatar_url"] = self._avatar.get_avatar(message["from"]) webhook["avatar_url"] = self._avatar.get_avatar(message["from"])
# Look for mentions and replace them # Look for mentions and replace them
@ -361,7 +246,7 @@ class BridgeComponent(ComponentXMPP):
If update_state_tracking is True, then _virtual_muc_... gets updates. 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"]: if member.status == discord.Status.offline and not self._dont_ignore_offline:
return return
if update_state_tracking: if update_state_tracking:
@ -416,7 +301,7 @@ class BridgeComponent(ComponentXMPP):
for channel in self._guild_map[guild]: for channel in self._guild_map[guild]:
muc = self._guild_map[guild][channel] muc = self._guild_map[guild][channel]
if after.status == discord.Status.offline: if after.status == discord.Status.offline:
if self._config["general"]["dont_ignore_offline"]: if self._dont_ignore_offline:
self.virtual_user_update_presence(muc, self.virtual_user_update_presence(muc,
after.id, after.id,
"xa") "xa")
@ -466,7 +351,7 @@ class BridgeComponent(ComponentXMPP):
user: discord.Member user: discord.Member
kind: Either "add" or "remove" kind: Either "add" or "remove"
""" """
if not self._config["general"]["reactions_compat"]: if not self._reactions_compat:
self._logger.debug("Got a reaction but reactions_compat is turned off. Ignoring.") self._logger.debug("Got a reaction but reactions_compat is turned off. Ignoring.")
return return
if not guild in self._guild_map: if not guild in self._guild_map:
@ -503,7 +388,7 @@ class BridgeComponent(ComponentXMPP):
self._logger.debug("Message empty. Not relaying.") self._logger.debug("Message empty. Not relaying.")
return return
if self._config["general"]["muc_mention_compat"]: if self._muc_mention_compat:
mentions = [mention.display_name for mention in msg.mentions] mentions = [mention.display_name for mention in msg.mentions]
content = ", ".join(mentions) + ": " + msg.clean_content content = ", ".join(mentions) + ": " + msg.clean_content
else: else:

View File

View File

@ -0,0 +1,11 @@
from slixmpp.xmlstream import ElementBase
class OOBData(ElementBase):
"""
XEP-0066 OOB data element for messages
"""
name = "x"
namespace = "jabber:x:oob"
plugin_attrib = "oob"
interfaces = {"url"}
sub_interfaces = interfaces