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