Compare commits
No commits in common. "277dcab40d906ef25379b3289dfdb42266c22538" and "f0a7869888c5247eea4ab5a4f0a5ac659a613969" have entirely different histories.
277dcab40d
...
f0a7869888
@ -8,8 +8,6 @@ import os
|
||||
import asyncache, cachetools
|
||||
import aiohttp
|
||||
import re
|
||||
import urllib.parse
|
||||
import markdownify
|
||||
|
||||
# For Matrix, dynamic thumbnails and unauthorized media should be both enabled.
|
||||
|
||||
@ -68,7 +66,7 @@ class GatewayMatrix:
|
||||
|
||||
if was_edit:
|
||||
async def q_msg():
|
||||
await puppet["intent"].send_message_event(room_id = self.room_name_to_room_id[room_name], event_type = mautrix.types.event.type.EventType.ROOM_MESSAGE, content = mautrix.types.event.message.TextMessageEventContent(msgtype = mautrix.types.event.message.MessageType.TEXT, format = None, formatted_body = None, body = body, relates_to = mautrix.types.event.message.RelatesTo(rel_type = mautrix.types.event.message.RelationType.REPLACE, event_id = self.msg_unique_id_to_event_id[msg_unique_id])))
|
||||
await puppet["intent"].send_message_event(room_id = self.room_name_to_room_id[room_name], event_type = mautrix.types.event.type.EventType.ROOM_MESSAGE, content = mautrix.types.event.message.TextMessageEventContent(msgtype = mautrix.types.event.message.MessageType.TEXT, format = mautrix.types.event.message.Format.HTML, formatted_body = body, relates_to = mautrix.types.event.message.RelatesTo(rel_type = mautrix.types.event.message.RelationType.REPLACE, event_id = self.msg_unique_id_to_event_id[msg_unique_id])))
|
||||
|
||||
return True
|
||||
puppet["queue"].append(q_msg)
|
||||
@ -81,7 +79,7 @@ class GatewayMatrix:
|
||||
puppet["last_avatar_hash"] = avatar_hash
|
||||
await puppet["intent"].set_avatar_url(await puppet["intent"].upload_media(data = avatar))
|
||||
|
||||
event_id = await puppet["intent"].send_message_event(room_id = self.room_name_to_room_id[room_name], event_type = mautrix.types.event.type.EventType.ROOM_MESSAGE, content = mautrix.types.event.message.TextMessageEventContent(msgtype = mautrix.types.event.message.MessageType.TEXT, format = None, formatted_body = None, body = body))
|
||||
event_id = await puppet["intent"].send_message_event(room_id = self.room_name_to_room_id[room_name], event_type = mautrix.types.event.type.EventType.ROOM_MESSAGE, content = mautrix.types.event.message.TextMessageEventContent(msgtype = mautrix.types.event.message.MessageType.TEXT, format = mautrix.types.event.message.Format.HTML, formatted_body = body))
|
||||
|
||||
self.msg_unique_id_to_event_id[msg_unique_id] = event_id
|
||||
self.event_id_to_msg_unique_id[event_id] = msg_unique_id
|
||||
@ -130,11 +128,12 @@ class GatewayMatrix:
|
||||
return
|
||||
|
||||
if re.match(re.compile(f"@{re.escape(self.bot_localpart)}[^:]*\\:{re.escape(self.domain)}"), event.sender):
|
||||
# Ignore ours
|
||||
return
|
||||
|
||||
if str(event.type) == "m.room.message":
|
||||
if isinstance(event.content, mautrix.types.event.message.TextMessageEventContent):
|
||||
if str(event.content.msgtype) != "m.text":
|
||||
return
|
||||
|
||||
was_edit = event.content._relates_to and str(event.content._relates_to.rel_type) == "m.replace"
|
||||
|
||||
if was_edit:
|
||||
@ -149,24 +148,9 @@ class GatewayMatrix:
|
||||
nick_name = (await self.appserv.intent.get_displayname(event.sender)) or event.sender
|
||||
avatar = await self.get_native_user_avatar(event.sender)
|
||||
|
||||
if isinstance(event.content, mautrix.types.event.message.MediaMessageEventContent):
|
||||
content_url = str(self.appserv.intent.api.get_download_url(event.content.url))
|
||||
content_url = urllib.parse.urlparse(content_url)._replace(netloc = "matrix." + self.domain).geturl()
|
||||
|
||||
body = ""
|
||||
attachments = [content_url]
|
||||
else:
|
||||
body = event.content.formatted_body if event.content.format else event.content.body
|
||||
attachments = []
|
||||
|
||||
def code_language_callback(el):
|
||||
c = el.find("code")
|
||||
if c is not None and c["class"]:
|
||||
c = c["class"][0]
|
||||
return c[9:] if "language-" in c else c
|
||||
return None
|
||||
body = markdownify.markdownify(body, newline_style = 'backslash', code_language_callback = code_language_callback)
|
||||
|
||||
if ("matrix", self.gateway_name, event.sender) not in self.active_users:
|
||||
self.active_users[("matrix", self.gateway_name, event.sender)] = None
|
||||
PLEXUS.pub("new_user", ("matrix", self.gateway_name, event.sender), nick_name)
|
||||
@ -224,9 +208,7 @@ class GatewayMatrix:
|
||||
if mxc_uri:
|
||||
# https://mementomori.social/@rolle/113732824034474418
|
||||
async with self.appserv.http_session.get(str(self.appserv.intent.api.get_download_url(mxc_uri, download_type = "thumbnail")) + "?width=128&height=128&method=scale&allow_redirect=true") as resp:
|
||||
if resp.status == 200:
|
||||
return await resp.read()
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@ -5,23 +5,7 @@ from slixmpp.componentxmpp import ComponentXMPP
|
||||
from slixmpp.types import PresenceArgs
|
||||
import slixmpp.stanza
|
||||
import slixmpp.plugins.xep_0084.stanza
|
||||
import slixmpp.plugins.xep_0060.stanza
|
||||
import slixmpp.plugins.xep_0292.stanza
|
||||
import slixmpp.plugins.xep_0153.stanza
|
||||
import xml.etree.ElementTree as ET
|
||||
import base64, time
|
||||
import magic
|
||||
|
||||
# Avatars were one of the hardest things to get right in this gateway.
|
||||
# At least Gajim works with the following protocol:
|
||||
# 1. If a puppet joins or changes their avatar, we send a presence stanza with the new avatar hash (XEP-0153)
|
||||
# 2. Then the client notices the update and requests the new avatar
|
||||
# 3. If a native user (non-puppet) joins a room, they *WONT* query the XEP-0153 or XEP-0054 avatars, but they'll query the vCard4 avatars!
|
||||
# Therefore, this gateway has to support both XEP-0153, XEP-0054 and vCard4 requests
|
||||
|
||||
NON_BMP_RE = re.compile(u"[^\U00000000-\U0000d7ff\U0000e000-\U0000ffff]", flags=re.UNICODE)
|
||||
def non_bmp(s):
|
||||
return NON_BMP_RE.sub(u'', s)
|
||||
|
||||
class GatewayXMPP(ComponentXMPP):
|
||||
def __init__(self, plexus, settings, gateway_name, gateway_data):
|
||||
@ -69,18 +53,13 @@ class GatewayXMPP(ComponentXMPP):
|
||||
self.register_plugin('xep_0359') # Stanza IDs
|
||||
self.register_plugin('xep_0308') # Message correction
|
||||
self.register_plugin('xep_0424') # Message retraction
|
||||
|
||||
#self.register_plugin('xep_0084') # user avatar
|
||||
self.register_plugin('xep_0054') # vcard-temp
|
||||
self.register_plugin('xep_0153') # vcard-temp avatars update
|
||||
self.register_plugin('xep_0084') # user avatar
|
||||
|
||||
def new_user(identification, nick_name):
|
||||
if identification[0] == "xmpp" and identification[1] == self.gateway_name:
|
||||
# Ignore ours
|
||||
return
|
||||
|
||||
nick_name = non_bmp(nick_name)
|
||||
|
||||
puppet_data = {
|
||||
"identification": identification,
|
||||
"jid": f"{hashlib.sha256(str(identification).encode()).hexdigest()[:24]}@{self.pfrom}/mirror",
|
||||
@ -91,7 +70,7 @@ class GatewayXMPP(ComponentXMPP):
|
||||
# Messages are added and executed as separate steps
|
||||
"queue": [],
|
||||
|
||||
"avatar": None
|
||||
"avatar": {"state": "ready", "desired": None, "timeout": 0}
|
||||
}
|
||||
self.my_puppets[identification] = puppet_data
|
||||
PLEXUS.sub("new_user", new_user)
|
||||
@ -107,33 +86,17 @@ class GatewayXMPP(ComponentXMPP):
|
||||
if len(attachments):
|
||||
body = str(body) + "\n\n" + "\n".join([str(a) for a in attachments])
|
||||
|
||||
if avatar and (puppet_data["avatar"]["desired"] is None):
|
||||
puppet_data["avatar"]["desired"] = avatar
|
||||
puppet_data["avatar"]["state"] = "waiting"
|
||||
puppet_data["avatar"]["timeout"] = time.time_ns()
|
||||
|
||||
def queue_callback():
|
||||
kwargs = {
|
||||
"mto": muc_jid, "mbody": body, "mtype": "groupchat", "mfrom": puppet_data["jid"]
|
||||
}
|
||||
xmpp_msg = self.make_message(**kwargs)
|
||||
|
||||
if puppet_data["avatar"] != avatar:
|
||||
puppet_data["avatar"] = avatar
|
||||
|
||||
# Advertise new puppet avatar
|
||||
|
||||
def cb(f):
|
||||
if not f.exception():
|
||||
stanz = self.plugin["xep_0045"].make_join_stanza(muc_jid, nick = puppet_data["nicknames"][muc_jid]["nick"], presence_options = PresenceArgs(pstatus = "", pshow = "chat", pfrom = puppet_data["jid"]))
|
||||
|
||||
x = ET.SubElement(stanz.xml, "{vcard-temp:x:update}x")
|
||||
photo = ET.SubElement(x, "{vcard-temp:x:update}photo")
|
||||
photo.text = str(hashlib.sha1(avatar).hexdigest())
|
||||
|
||||
print("NEW PUPPET AVATAR", ET.tostring(stanz.xml, encoding = "unicode"))
|
||||
|
||||
stanz.send()
|
||||
|
||||
# Although xep_0153 plugin does send_last_presence, it is not aware of the MUCs we are connected to, so we do that ourselves in cb
|
||||
# This calls set_avatar per each room, when it should be done only once, but whatever
|
||||
self.plugin["xep_0153"].set_avatar(jid = puppet_data["jid"], avatar = avatar).add_done_callback(cb)
|
||||
|
||||
if msg_unique_id in self.xdm_id_to_base_id:
|
||||
# This message is an edit of an older message
|
||||
xmpp_msg["replace"]["id"] = self.xdm_id_to_base_id[msg_unique_id]
|
||||
@ -150,7 +113,6 @@ class GatewayXMPP(ComponentXMPP):
|
||||
puppet_data["queue"].append(queue_callback)
|
||||
|
||||
# If the user changed their nickname, try to update it on the puppet
|
||||
nick_name = non_bmp(nick_name)
|
||||
if nick_name != puppet_data["nicknames"][muc_jid]["nick"]:
|
||||
assert puppet_data["nicknames"][muc_jid]["state"] == "joined"
|
||||
puppet_data["nicknames"][muc_jid]["state"] = "joining"
|
||||
@ -187,7 +149,7 @@ class GatewayXMPP(ComponentXMPP):
|
||||
# stored in a queue before being sent.
|
||||
|
||||
def is_puppet_ready(self, puppet_data):
|
||||
return all([nn["state"] == "joined" for nn in puppet_data["nicknames"].values()])
|
||||
return puppet_data["avatar"]["state"] == "ready" and all([nn["state"] == "joined" for nn in puppet_data["nicknames"].values()])
|
||||
|
||||
async def step_puppets(self):
|
||||
while True:
|
||||
@ -199,7 +161,13 @@ class GatewayXMPP(ComponentXMPP):
|
||||
cb = puppet_data["queue"].pop(0)
|
||||
cb()
|
||||
else:
|
||||
# We are still looking for nicknames
|
||||
# We are still looking for nicknames OR trying to setup an avatar
|
||||
|
||||
if puppet_data["avatar"]["state"] == "waiting" and time.time_ns() >= puppet_data["avatar"]["timeout"]:
|
||||
pass
|
||||
|
||||
puppet_data["avatar"]["state"] = "ready"
|
||||
|
||||
for muc_jid, nn in puppet_data["nicknames"].items():
|
||||
if nn["state"] == "failed":
|
||||
nn["nick"] = nn["nick"] + " (real)"
|
||||
@ -253,6 +221,9 @@ class GatewayXMPP(ComponentXMPP):
|
||||
|
||||
PLEXUS.pub("message", msg_unique_id, self.muc_to_room_name[msg["from"].bare], f"{msg['from'].resource}", self.avatar_cache[msg["from"].full], "xmpp", self.gateway_name, (msg["from"].bare, msg["from"].resource), msg["body"], [], False)
|
||||
|
||||
if msg["from"].full not in self.avatar_cache:
|
||||
# First try loading avatar before sending message
|
||||
|
||||
def groupchat_presence(self, presence):
|
||||
if presence["from"].bare not in self.relevant_mucs:
|
||||
return
|
||||
@ -274,57 +245,12 @@ class GatewayXMPP(ComponentXMPP):
|
||||
data = stanza.xml.find(".//{urn:xmpp:avatar:data}data")
|
||||
if data is not None:
|
||||
self.avatar_cache[presence["from"].full] = base64.standard_b64decode("".join(data.itertext()))
|
||||
# We need this callback to ensure exceptions are handled
|
||||
def cb(fut):
|
||||
fut.exception()
|
||||
self.plugin["xep_0060"].get_items(jid = presence["from"].full, node = "urn:xmpp:avatar:data", ifrom = self.pfrom, callback = got_avatar, max_items = 1).add_done_callback(cb)
|
||||
self.plugin["xep_0060"].get_items(jid = presence["from"].full, node = "urn:xmpp:avatar:data", ifrom = self.pfrom, callback = got_avatar, max_items = 1)
|
||||
|
||||
PLEXUS.pub("new_user", ("xmpp", self.gateway_name, (presence["from"].bare, presence["from"].resource)), presence["from"].resource)
|
||||
|
||||
def iq_funker(self, iq):
|
||||
print("GOT IQ", iq)
|
||||
if iq["type"] == "get" and iq.xml.find(".//{vcard-temp}vCard") is not None:
|
||||
for puppet_identification, puppet_data in self.my_puppets.items():
|
||||
if puppet_data["jid"] == iq["to"].full + "/mirror":
|
||||
ret = self.Iq()
|
||||
ret["id"] = iq["id"]
|
||||
ret["to"] = iq["from"]
|
||||
ret["from"] = iq["to"]
|
||||
ret["type"] = "result"
|
||||
ret["vcard_temp"]["PHOTO"]["BINVAL"] = puppet_data['avatar']#base64.standard_b64encode(puppet_data['avatar']).decode()
|
||||
|
||||
print(ET.tostring(ret.xml, encoding = "unicode"))
|
||||
|
||||
ret.send()
|
||||
|
||||
break
|
||||
|
||||
if iq["type"] == "get" and (iq.xml.find(".//{http://jabber.org/protocol/pubsub}pubsub/{http://jabber.org/protocol/pubsub}items[@node='urn:xmpp:vcard4']") is not None):
|
||||
for puppet_identification, puppet_data in self.my_puppets.items():
|
||||
if puppet_data["jid"] == iq["to"].full + "/mirror":
|
||||
ret = self.Iq()
|
||||
ret["id"] = iq["id"]
|
||||
ret["to"] = iq["from"]
|
||||
ret["from"] = iq["to"]
|
||||
ret["type"] = "result"
|
||||
ret["pubsub"]["items"]["node"] = "urn:xmpp:vcard4"
|
||||
|
||||
vcard = slixmpp.plugins.xep_0292.stanza.VCard4()
|
||||
vcard.set_full_name(str(puppet_identification[-1]))
|
||||
if puppet_data["avatar"]:
|
||||
photo = ET.SubElement(vcard.xml, "{urn:ietf:params:xml:ns:vcard-4.0}photo")
|
||||
uri = ET.SubElement(photo, "{urn:ietf:params:xml:ns:vcard-4.0}uri")
|
||||
uri.text = f"data:{magic.from_buffer(puppet_data['avatar'], mime = True)};base64,{base64.standard_b64encode(puppet_data['avatar']).decode()}"
|
||||
print(ET.tostring(vcard.xml, encoding = "unicode"))
|
||||
|
||||
item = slixmpp.plugins.xep_0060.stanza.Item()
|
||||
item["id"] = hashlib.sha256((puppet_data["jid"] + " avatar lol").encode()).hexdigest()
|
||||
item["payload"] = vcard
|
||||
ret["pubsub"]["items"].append(item)
|
||||
|
||||
ret.send()
|
||||
|
||||
break
|
||||
|
||||
def nickname_conflict(self, presence):
|
||||
# FAILURE CASE:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user