This commit is contained in:
2026-08-02 18:57:40 +02:00
parent 5e5ab8681a
commit 6fa59321c0
5759 changed files with 712133 additions and 66 deletions
@@ -0,0 +1,66 @@
########################################################################
# File name: __init__.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
"""
:mod:`~aioxmpp.carbons` -- Message Carbons (:xep:`280`)
#######################################################
Message Carbons is an XMPP extension which allows an entity to receive copies
of inbound and outbound messages received and sent by other resources of the
same account. It is specified in :xep:`280`. The goal of this feature is to
allow users to have multiple devices which all have a consistent view on the
messages sent and received.
This subpackage provides basic support for Message Carbons. It allows enabling
and disabling the feature at the server side.
Service
=======
.. currentmodule:: aioxmpp
.. autoclass:: CarbonsClient
.. currentmodule:: aioxmpp.carbons
.. currentmodule:: aioxmpp.carbons.xso
.. module:: aioxmpp.carbons.xso
XSOs
====
.. attribute:: aioxmpp.Message.xep0280_sent
On a Carbon message, this holds the :class:`~.carbons.xso.Sent` XSO which in
turn holds the carbonated stanza.
.. attribute:: aioxmpp.Message.xep0280_received
On a Carbon message, this holds the :class:`~.carbons.xso.Received` XSO
which in turn holds the carbonated stanza.
.. autoclass:: Received
.. autoclass:: Sent
"""
from .service import CarbonsClient # NOQA: F401
@@ -0,0 +1,106 @@
########################################################################
# File name: service.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
import asyncio
import aioxmpp.service
from aioxmpp.utils import namespaces
from . import xso as carbons_xso
class CarbonsClient(aioxmpp.service.Service):
"""
Provide an interface to enable and disable Message Carbons on the server
side.
.. note::
This service deliberately does not provide a way to actually obtain sent
or received carbonated messages.
The common way for a service to do this would be a stanza filter (see
:class:`aioxmpp.stream.StanzaStream`); however, in general the use and
further distribution of carbonated messages highly depends on the
application: it does, for example, not make sense to simply unwrap
carbonated messages.
.. automethod:: enable
.. automethod:: disable
"""
ORDER_AFTER = [
aioxmpp.DiscoClient,
]
async def _check_for_feature(self):
disco_client = self.dependencies[aioxmpp.DiscoClient]
info = await disco_client.query_info(
self.client.local_jid.replace(
localpart=None,
resource=None,
)
)
if namespaces.xep0280_carbons_2 not in info.features:
raise RuntimeError(
"Message Carbons ({}) are not supported by the server".format(
namespaces.xep0280_carbons_2
)
)
async def enable(self):
"""
Enable message carbons.
:raises RuntimeError: if the server does not support message carbons.
:raises aioxmpp.XMPPError: if the server responded with an error to the
request.
:raises: as specified in :meth:`aioxmpp.Client.send`
"""
await self._check_for_feature()
iq = aioxmpp.IQ(
type_=aioxmpp.IQType.SET,
payload=carbons_xso.Enable()
)
await self.client.send(iq)
async def disable(self):
"""
Disable message carbons.
:raises RuntimeError: if the server does not support message carbons.
:raises aioxmpp.XMPPError: if the server responded with an error to the
request.
:raises: as specified in :meth:`aioxmpp.Client.send`
"""
await self._check_for_feature()
iq = aioxmpp.IQ(
type_=aioxmpp.IQType.SET,
payload=carbons_xso.Disable()
)
await self.client.send(iq)
@@ -0,0 +1,105 @@
########################################################################
# File name: xso.py
# This file is part of: aioxmpp
#
# LICENSE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
########################################################################
import aioxmpp.xso as xso
from aioxmpp.utils import namespaces
from ..misc import Forwarded
from ..stanza import Message, IQ
namespaces.xep0280_carbons_2 = "urn:xmpp:carbons:2"
@IQ.as_payload_class
class Enable(xso.XSO):
TAG = (namespaces.xep0280_carbons_2, "enable")
@IQ.as_payload_class
class Disable(xso.XSO):
TAG = (namespaces.xep0280_carbons_2, "disable")
class _CarbonsWrapper(xso.XSO):
forwarded = xso.Child([Forwarded])
@property
def stanza(self):
"""
The wrapped stanza, usually a :class:`aioxmpp.Message`.
Internally, this accesses the :attr:`~.misc.Forwarded.stanza` attribute
of :attr:`forwarded`. If :attr:`forwarded` is :data:`None`, reading
this attribute returns :data:`None`. Writing to this attribute creates
a new :class:`~.misc.Forwarded` object if necessary, but re-uses an
existing object if available.
"""
if self.forwarded is None:
return None
return self.forwarded.stanza
@stanza.setter
def stanza(self, value):
if self.forwarded is None:
self.forwarded = Forwarded()
self.forwarded.stanza = value
class Sent(_CarbonsWrapper):
"""
Wrap a stanza which was sent by another entity of the same account.
:class:`Sent` XSOs are available in Carbon messages at
:attr:`aioxmpp.Message.xep0280_sent`.
.. autoattribute:: stanza
.. attribute:: forwarded
The full :class:`~.misc.Forwarded` object which holds the sent stanza.
"""
TAG = (namespaces.xep0280_carbons_2, "sent")
class Received(_CarbonsWrapper):
"""
Wrap a stanza which was received by another entity of the same account.
:class:`Received` XSOs are available in Carbon messages at
:attr:`aioxmpp.Message.xep0280_received`.
.. autoattribute:: stanza
.. attribute:: forwarded
The full :class:`~.misc.Forwarded` object which holds the received
stanza.
"""
TAG = (namespaces.xep0280_carbons_2, "received")
Message.xep0280_sent = xso.Child([Sent])
Message.xep0280_received = xso.Child([Received])