Mercurial > libervia-backend
view libervia/backend/plugins/plugin_xep_0297.py @ 4382:b897d98b2c51 default tip
plugin XEP-0297: Reworked `forward` method and add bridge method:
`Forward` method has been reworked and now includes a fallback. XEP-0297 ask to not use
fallback, but following a discussion on xsf@, we agreed that this is a legacy thing and a
fallback should nowadays be used, I'll propose a patch to the specification.
A `message_forward` has been added to bridge.
rel 461
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 04 Jul 2025 12:33:42 +0200 |
parents | 4b842c1fb686 |
children |
line wrap: on
line source
#!/usr/bin/env python3 # SAT plugin for Stanza Forwarding (XEP-0297) # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) # Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from datetime import datetime from typing import cast from dateutil import tz from twisted.internet import defer from twisted.words.protocols.jabber import jid from twisted.words.protocols.jabber.xmlstream import XMPPHandler from twisted.words.xish import domish from wokkel import disco, iwokkel from zope.interface import implementer from libervia.backend import G from libervia.backend.core import exceptions from libervia.backend.core.constants import Const as C from libervia.backend.core.core_types import SatXMPPEntity from libervia.backend.models.core import MessageData from libervia.backend.core.i18n import D_, _ from libervia.backend.core.log import getLogger from libervia.backend.memory.sqla_mapping import History from libervia.backend.plugins.plugin_xep_0428 import XEP_0428 from libervia.backend.tools.common.date_utils import date_fmt log = getLogger(__name__) PLUGIN_INFO = { C.PI_NAME: "Stanza Forwarding", C.PI_IMPORT_NAME: "XEP-0297", C.PI_TYPE: "XEP", C.PI_PROTOCOLS: ["XEP-0297"], C.PI_DEPENDENCIES: ["XEP-0428"], C.PI_MAIN: "XEP_0297", C.PI_HANDLER: "yes", C.PI_DESCRIPTION: D_("""Implementation of Stanza Forwarding"""), } NS_FORWARD = "urn:xmpp:forward:0" class XEP_0297: # TODO: Handle forwarded message reception, for now the fallback will be used. # TODO: Add a method to forward Pubsub Item, and notably blog content. def __init__(self, host): log.info(_("Stanza Forwarding plugin initialization")) self.host = host self._fallback = cast(XEP_0428, host.plugins["XEP-0428"]) host.register_namespace("forward", NS_FORWARD) host.bridge.add_method( "message_forward", ".plugin", in_sign="sss", out_sign="", method=self._forward, async_=True, ) def get_handler(self, client): return XEP_0297_handler(self, client.profile) @classmethod def update_uri(cls, element, uri): """Update recursively the element URI. @param element (domish.Element): element to update @param uri (unicode): new URI """ # XXX: we need this because changing the URI of an existing element # containing children doesn't update the children's blank URI. element.uri = uri element.defaultUri = uri for child in element.children: if isinstance(child, domish.Element) and not child.uri: XEP_0297.update_uri(child, uri) def _forward( self, message_id: str, recipient_jid_s: str, profile_key: str ) -> defer.Deferred[None]: client = self.host.get_client(profile_key) recipient_jid = jid.JID(recipient_jid_s) return defer.ensureDeferred(self.forward_by_id(client, message_id, recipient_jid)) async def forward_by_id( self, client: SatXMPPEntity, message_id: str, recipient_jid: jid.JID ) -> None: history = cast(History, await G.storage.get( client, History, History.uid, message_id, joined_loads=[History.messages, History.subjects, History.thread], )) if not history: raise exceptions.NotFound( f"No history found with message {message_id!r}." ) # FIXME: Q&D way to get MessageData from History. History should have a proper # way to do that. serialised_history = history.serialise() serialised_history["from"] = jid.JID(serialised_history["from"]) serialised_history["to"] = jid.JID(serialised_history["to"]) mess_data = MessageData(serialised_history) client.generate_message_xml(mess_data) message_elt = cast(domish.Element, mess_data["xml"]) timestamp_float = float(history.timestamp or history.received_timestamp) timestamp = datetime.fromtimestamp(timestamp_float, tz.tzutc()) fallback_lines = [ f"{client.jid.userhost()} is forwarding this message to you:", f"[{date_fmt(timestamp_float)}]", f"From: {history.source_jid.full()}", f"To: {history.dest_jid.full()}" ] for subject in history.subjects: if subject.language: fallback_lines.append(f"Subject [{subject.language}]: {subject.subject}") else: fallback_lines.append(f"Subject: {subject.subject}") for message in history.messages: if message.language: fallback_lines.append(f"Message [{message.language}]: {message.message}") else: fallback_lines.append(f"Message: {message.message}") fallback_msg = "\n".join(fallback_lines) await self.forward(client, message_elt, recipient_jid, timestamp, fallback_msg) async def forward( self, client: SatXMPPEntity, stanza: domish.Element, to_jid: jid.JID, timestamp: datetime|None, fallback_msg: str | None = None ): """Forward a message to the given JID. @param client: client instance. @param stanza: original stanza to be forwarded. @param to_jid: recipient JID. @param timestamp: offset-aware timestamp of the original reception. @param body: optional description. @return: a Deferred when the message has been sent """ message_elt = domish.Element((None, "message")) message_elt["to"] = to_jid.full() message_elt["type"] = stanza["type"] forwarded_elt = domish.Element((C.NS_FORWARD, "forwarded")) if timestamp: delay_elt = self.host.plugins["XEP-0203"].delay(timestamp) forwarded_elt.addChild(delay_elt) if not stanza.uri: XEP_0297.update_uri(stanza, "jabber:client") forwarded_elt.addChild(stanza) message_elt.addChild(domish.Element((None, "body"))) message_elt.addChild(forwarded_elt) self._fallback.add_fallback_elt(message_elt, NS_FORWARD, fallback_msg) return await client.send_message_data( MessageData({"xml": message_elt, "extra": {}}) ) @implementer(iwokkel.IDisco) class XEP_0297_handler(XMPPHandler): def __init__(self, plugin_parent, profile): self.plugin_parent = plugin_parent self.host = plugin_parent.host self.profile = profile def getDiscoInfo(self, requestor, target, nodeIdentifier=""): return [disco.DiscoFeature(C.NS_FORWARD)] def getDiscoItems(self, requestor, target, nodeIdentifier=""): return []