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
This commit is contained in:
Goffi 2025-07-04 12:33:42 +02:00
commit b1e0e336c2

View file

@ -18,41 +18,65 @@
# 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 libervia.backend.core.constants import Const as C
from libervia.backend.core.i18n import _, D_
from libervia.backend.core.log import getLogger
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__)
from wokkel import disco, iwokkel
try:
from twisted.words.protocols.xmlstream import XMPPHandler
except ImportError:
from wokkel.subprotocols import XMPPHandler
from zope.interface import implementer
from twisted.words.xish import domish
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(object):
# FIXME: check this implementation which doesn't seems to be used
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)
@ -72,41 +96,102 @@ class XEP_0297(object):
if isinstance(child, domish.Element) and not child.uri:
XEP_0297.update_uri(child, uri)
def forward(self, stanza, to_jid, stamp, body="", profile_key=C.PROF_KEY_NONE):
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 stanza (domish.Element): original stanza to be forwarded.
@param to_jid (JID): recipient JID.
@param stamp (datetime): offset-aware timestamp of the original reception.
@param body (unicode): optional description.
@param profile_key (unicode): %(doc_profile_key)s
@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
"""
# FIXME: this method is not used and doesn't use mess_data which should be used for client.send_message_data
# should it be deprecated? A method constructing the element without sending it seems more natural
log.warning(
"THIS METHOD IS DEPRECATED"
) #  FIXME: we use this warning until we check the method
msg = domish.Element((None, "message"))
msg["to"] = to_jid.full()
msg["type"] = stanza["type"]
body_elt = domish.Element((None, "body"))
if body:
body_elt.addContent(body)
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"))
delay_elt = self.host.plugins["XEP-0203"].delay(stamp)
forwarded_elt.addChild(delay_elt)
if not stanza.uri: # None or ''
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)
msg.addChild(body_elt)
msg.addChild(forwarded_elt)
client = self.host.get_client(profile_key)
return defer.ensureDeferred(client.send_message_data({"xml": msg}))
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)