diff --git a/sat/plugins/plugin_exp_events.py b/sat/plugins/plugin_exp_events.py
index 1f5b464e..a3076739 100644
--- a/sat/plugins/plugin_exp_events.py
+++ b/sat/plugins/plugin_exp_events.py
@@ -17,11 +17,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from typing import Optional
import shortuuid
from sat.core.i18n import _
from sat.core import exceptions
from sat.core.constants import Const as C
from sat.core.log import getLogger
+from sat.core.xmpp import SatXMPPEntity
from sat.tools import utils
from sat.tools.common import uri as xmpp_uri
from sat.tools.common import date_utils
@@ -41,7 +43,7 @@ PLUGIN_INFO = {
C.PI_IMPORT_NAME: "EVENTS",
C.PI_TYPE: "EXP",
C.PI_PROTOCOLS: [],
- C.PI_DEPENDENCIES: ["XEP-0060", "INVITATION", "LIST_INTEREST"],
+ C.PI_DEPENDENCIES: ["XEP-0060", "INVITATION", "PUBSUB_INVITATION", "LIST_INTEREST"],
C.PI_RECOMMENDATIONS: ["XEP-0277", "EMAIL_INVITATION"],
C.PI_MAIN: "Events",
C.PI_HANDLER: "yes",
@@ -61,8 +63,7 @@ class Events(object):
self._i = self.host.plugins.get("EMAIL_INVITATION")
self._b = self.host.plugins.get("XEP-0277")
self.host.registerNamespace("event", NS_EVENT)
- self.host.plugins["INVITATION"].registerNamespace(NS_EVENT,
- self.register)
+ self.host.plugins["PUBSUB_INVITATION"].register(NS_EVENT, self)
host.bridge.addMethod(
"eventGet",
".plugin",
@@ -232,32 +233,6 @@ class Events(object):
raise exceptions.NotFound(_("No event with this id has been found"))
defer.returnValue(event_elt)
- def register(self, client, name, extra, service, node, event_id, item_elt,
- creator=False):
- """Register evenement in personal events list
-
- @param service(jid.JID): pubsub service of the event
- @param node(unicode): event node
- @param event_id(unicode): event id
- @param event_elt(domish.Element): event element
- note that this element will be modified in place
- @param creator(bool): True if client's profile is the creator of the node
- """
- event_elt = item_elt.event
- link_elt = event_elt.addElement("link")
- link_elt["service"] = service.full()
- link_elt["node"] = node
- link_elt["item"] = event_id
- __, event_data = self._parseEventElt(event_elt)
- name = event_data.get('name')
- if 'image' in event_data:
- extra = {'thumb_url': event_data['image']}
- else:
- extra = None
- return self.host.plugins['LIST_INTEREST'].registerPubsub(
- client, NS_EVENT, service, node, event_id, creator,
- name=name, element=event_elt, extra=extra)
-
def _eventGet(self, service, node, id_="", profile_key=C.PROF_KEY_NONE):
service = jid.JID(service) if service else None
node = node if node else NS_EVENT
@@ -289,10 +264,11 @@ class Events(object):
node = node or None
client = self.host.getClient(profile_key)
data["register"] = C.bool(data.get("register", C.BOOL_FALSE))
- return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT)
+ return defer.ensureDeferred(
+ self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT)
+ )
- @defer.inlineCallbacks
- def eventCreate(self, client, timestamp, data, service, node=None, event_id=NS_EVENT):
+ async def eventCreate(self, client, timestamp, data, service, node=None, event_id=NS_EVENT):
"""Create or replace an event
@param service(jid.JID, None): PubSub service
@@ -341,8 +317,8 @@ class Events(object):
del data[k]
if key not in data:
# FIXME: affiliate invitees
- uri_node = yield self._p.createNode(client, service)
- yield self._p.setConfiguration(
+ uri_node = await self._p.createNode(client, service)
+ await self._p.setConfiguration(
client,
service,
uri_node,
@@ -372,17 +348,23 @@ class Events(object):
item_elt = pubsub.Item(id=event_id, payload=event_elt)
try:
# TODO: check auto-create, no need to create node first if available
- node = yield self._p.createNode(client, service, nodeIdentifier=node)
+ node = await self._p.createNode(client, service, nodeIdentifier=node)
except error.StanzaError as e:
if e.condition == "conflict":
log.debug(_("requested node already exists"))
- yield self._p.publish(client, service, node, items=[item_elt])
+ await self._p.publish(client, service, node, items=[item_elt])
if register:
- yield self.register(
- client, None, {}, service, node, event_id, item_elt, creator=True)
- defer.returnValue(node)
+ extra = {}
+ self.onInvitationPreflight(
+ client, "", extra, service, node, event_id, item_elt
+ )
+ await self.host.plugins['LIST_INTEREST'].registerPubsub(
+ client, NS_EVENT, service, node, event_id, True,
+ extra.pop("name", ""), extra.pop("element"), extra
+ )
+ return node
def _eventModify(self, service, node, id_, timestamp_update, data_update,
profile_key=C.PROF_KEY_NONE):
@@ -390,12 +372,14 @@ class Events(object):
if not node:
raise ValueError(_("missing node"))
client = self.host.getClient(profile_key)
- return self.eventModify(
- client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update
+ return defer.ensureDeferred(
+ self.eventModify(
+ client, service, node, id_ or NS_EVENT, timestamp_update or None,
+ data_update
+ )
)
- @defer.inlineCallbacks
- def eventModify(
+ async def eventModify(
self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None
):
"""Update an event
@@ -403,13 +387,13 @@ class Events(object):
Similar as create instead that it update existing item instead of
creating or replacing it. Params are the same as for [eventCreate].
"""
- event_timestamp, event_metadata = yield self.eventGet(client, service, node, id_)
+ event_timestamp, event_metadata = await self.eventGet(client, service, node, id_)
new_timestamp = event_timestamp if timestamp_update is None else timestamp_update
new_data = event_metadata
if data_update:
for k, v in data_update.items():
new_data[k] = v
- yield self.eventCreate(client, new_timestamp, new_data, service, node, id_)
+ await self.eventCreate(client, new_timestamp, new_data, service, node, id_)
def _eventsListSerialise(self, events):
for timestamp, data in events:
@@ -543,52 +527,40 @@ class Events(object):
invitees[item["id"]] = data
defer.returnValue(invitees)
- def _invite(self, invitee_jid, service, node, item_id, profile):
- client = self.host.getClient(profile)
- service = jid.JID(service) if service else None
- node = node or None
- item_id = item_id or None
- invitee_jid = jid.JID(invitee_jid)
- return self.invite(client, invitee_jid, service, node, item_id)
-
- @defer.inlineCallbacks
- def invite(self, client, invitee_jid, service, node, item_id=NS_EVENT):
- """Invite an entity to the event
-
- This will set permission to let the entity access everything needed
- @pararm invitee_jid(jid.JID): entity to invite
- @param service(jid.JID, None): pubsub service
- None to use client's PEP
- @param node(unicode): event node
- @param item_id(unicode): event id
- """
- # FIXME: handle name and extra
- name = ''
- extra = {}
+ async def invitePreflight(
+ self,
+ client: SatXMPPEntity,
+ invitee_jid: jid.JID,
+ service: jid.JID,
+ node: str,
+ item_id: Optional[str] = None,
+ name: str = '',
+ extra: Optional[dict] = None,
+ ) -> None:
if self._b is None:
raise exceptions.FeatureNotFound(
_('"XEP-0277" (blog) plugin is needed for this feature')
)
if item_id is None:
- item_id = NS_EVENT
+ item_id = extra["default_item_id"] = NS_EVENT
- # first we authorize our invitee to see the nodes of interest
- yield self._p.setNodeAffiliations(client, service, node, {invitee_jid: "member"})
- log.debug(_("affiliation set on event node"))
- __, event_data = yield self.eventGet(client, service, node, item_id)
+ __, event_data = await self.eventGet(client, service, node, item_id)
log.debug(_("got event data"))
invitees_service = jid.JID(event_data["invitees_service"])
invitees_node = event_data["invitees_node"]
blog_service = jid.JID(event_data["blog_service"])
blog_node = event_data["blog_node"]
- yield self._p.setNodeAffiliations(
+ await self._p.setNodeAffiliations(
client, invitees_service, invitees_node, {invitee_jid: "publisher"}
)
- log.debug(_("affiliation set on invitee node"))
- yield self._p.setNodeAffiliations(
+ log.debug(
+ f"affiliation set on invitee node (jid: {invitees_service}, "
+ f"node: {invitees_node!r})"
+ )
+ await self._p.setNodeAffiliations(
client, blog_service, blog_node, {invitee_jid: "member"}
)
- blog_items, __ = yield self._b.mbGet(client, blog_service, blog_node, None)
+ blog_items, __ = await self._b.mbGet(client, blog_service, blog_node, None)
for item in blog_items:
try:
@@ -601,15 +573,15 @@ class Events(object):
)
)
else:
- yield self._p.setNodeAffiliations(
+ await self._p.setNodeAffiliations(
client, comments_service, comments_node, {invitee_jid: "publisher"}
)
log.debug(_("affiliation set on blog and comments nodes"))
- # now we send the invitation
- pubsub_invitation = self.host.plugins['INVITATION']
- pubsub_invitation.sendPubsubInvitation(client, invitee_jid, service, node,
- item_id, name, extra)
+ def _invite(self, invitee_jid, service, node, item_id, profile):
+ return self.host.plugins["PUBSUB_INVITATION"]._sendPubsubInvitation(
+ invitee_jid, service, node, item_id or NS_EVENT, profile_key=profile
+ )
def _inviteByEmail(self, service, node, id_=NS_EVENT, email="", emails_extra=None,
name="", host_name="", language="", url_template="",
@@ -631,12 +603,11 @@ class Events(object):
):
value = locals()[key]
kwargs[key] = str(value)
- return self.inviteByEmail(
+ return defer.ensureDeferred(self.inviteByEmail(
client, jid.JID(service) if service else None, node, id_ or NS_EVENT, **kwargs
- )
+ ))
- @defer.inlineCallbacks
- def inviteByEmail(self, client, service, node, id_=NS_EVENT, **kwargs):
+ async def inviteByEmail(self, client, service, node, id_=NS_EVENT, **kwargs):
"""High level method to create an email invitation to an event
@param service(unicode, None): PubSub service
@@ -656,11 +627,37 @@ class Events(object):
"pubsub", path=service.full(), node=node, item=id_
)
kwargs["extra"] = {"event_uri": event_uri}
- invitation_data = yield self._i.create(**kwargs)
+ invitation_data = await self._i.create(**kwargs)
invitee_jid = invitation_data["jid"]
log.debug(_("invitation created"))
# now that we have a jid, we can send normal invitation
- yield self.invite(client, invitee_jid, service, node, id_)
+ await self.invite(client, invitee_jid, service, node, id_)
+
+ def onInvitationPreflight(
+ self,
+ client: SatXMPPEntity,
+ name: str,
+ extra: dict,
+ service: jid.JID,
+ node: str,
+ item_id: Optional[str],
+ item_elt: domish.Element
+ ) -> None:
+ event_elt = item_elt.event
+ link_elt = event_elt.addElement("link")
+ link_elt["service"] = service.full()
+ link_elt["node"] = node
+ link_elt["item"] = item_id
+ __, event_data = self._parseEventElt(event_elt)
+ try:
+ name = event_data["name"]
+ except KeyError:
+ pass
+ else:
+ extra["name"] = name
+ if 'image' in event_data:
+ extra["thumb_url"] = event_data['image']
+ extra["element"] = event_elt
@implementer(iwokkel.IDisco)
diff --git a/sat/plugins/plugin_exp_invitation.py b/sat/plugins/plugin_exp_invitation.py
index 52d1b21e..4b8cd75b 100644
--- a/sat/plugins/plugin_exp_invitation.py
+++ b/sat/plugins/plugin_exp_invitation.py
@@ -16,15 +16,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from typing import Optional
+from zope.interface import implementer
+from twisted.internet import defer
+from twisted.words.protocols.jabber import jid
+from twisted.words.protocols.jabber.xmlstream import XMPPHandler
+from wokkel import disco, iwokkel
from sat.core.i18n import _
from sat.core import exceptions
from sat.core.constants import Const as C
from sat.core.log import getLogger
-from twisted.internet import defer
-from twisted.words.protocols.jabber import jid
-from wokkel import disco, iwokkel
-from zope.interface import implementer
-from twisted.words.protocols.jabber.xmlstream import XMPPHandler
+from sat.core.xmpp import SatXMPPEntity
+from sat.tools import utils
log = getLogger(__name__)
@@ -123,16 +126,25 @@ class Invitation(object):
invitation_elt['thumb_url'] = thumb_url
return mess_data, invitation_elt
- def sendPubsubInvitation(self, client, invitee_jid, service, node,
- item_id, name, extra):
+ def sendPubsubInvitation(
+ self,
+ client: SatXMPPEntity,
+ invitee_jid: jid.JID,
+ service: jid.JID,
+ node: str,
+ item_id: Optional[str],
+ name: Optional[str],
+ extra: Optional[dict]
+ ) -> None:
"""Send an pubsub invitation in a stanza
- @param invitee_jid(jid.JID): entitee to send invitation to
- @param service(jid.JID): pubsub service
- @param node(unicode): pubsub node
- @param item_id(unicode): pubsub id
- @param name(unicode, None): see [_generateBaseInvitation]
- @param extra(dict, None): see [_generateBaseInvitation]
+ @param invitee_jid: entitee to send invitation to
+ @param service: pubsub service
+ @param node: pubsub node
+ @param item_id: pubsub id
+ None when the invitation is for a whole node
+ @param name: see [_generateBaseInvitation]
+ @param extra: see [_generateBaseInvitation]
"""
if extra is None:
extra = {}
@@ -141,8 +153,22 @@ class Invitation(object):
pubsub_elt = invitation_elt.addElement("pubsub")
pubsub_elt["service"] = service.full()
pubsub_elt["node"] = node
- pubsub_elt["item"] = item_id
- return client.send(mess_data["xml"])
+ if item_id is None:
+ try:
+ namespace = extra.pop("namespace")
+ except KeyError:
+ raise exceptions.DataError('"namespace" key is missing in "extra" data')
+ node_data_elt = pubsub_elt.addElement("node_data")
+ node_data_elt["namespace"] = namespace
+ try:
+ node_data_elt.addChild(extra["element"])
+ except KeyError:
+ pass
+ else:
+ pubsub_elt["item"] = item_id
+ if "element" in extra:
+ invitation_elt.addChild(extra.pop("element"))
+ client.send(mess_data["xml"])
async def sendFileSharingInvitation(
self, client, invitee_jid, service, repos_type=None, namespace=None, path=None,
@@ -207,53 +233,61 @@ class Invitation(object):
file_sharing_elt["path"] = path
client.send(mess_data["xml"])
- @defer.inlineCallbacks
- def _parsePubsubElt(self, client, pubsub_elt):
+ async def _parsePubsubElt(self, client, pubsub_elt):
try:
service = jid.JID(pubsub_elt["service"])
node = pubsub_elt["node"]
- item_id = pubsub_elt.getAttribute("item")
except (RuntimeError, KeyError):
- log.warning(_("Bad invitation, ignoring"))
- raise exceptions.DataError
+ raise exceptions.DataError("Bad invitation, ignoring")
- try:
- items, metadata = yield self._p.getItems(client, service, node,
- item_ids=[item_id])
- except Exception as e:
- log.warning(_("Can't get item linked with invitation: {reason}").format(
- reason=e))
- try:
- item_elt = items[0]
- except IndexError:
- log.warning(_("Invitation was linking to a non existing item"))
- raise exceptions.DataError
+ item_id = pubsub_elt.getAttribute("item")
- try:
- namespace = item_elt.firstChildElement().uri
- except Exception as e:
- log.warning(_("Can't retrieve namespace of invitation: {reason}").format(
- reason = e))
- raise exceptions.DataError
+ if item_id is not None:
+ try:
+ items, metadata = await self._p.getItems(
+ client, service, node, item_ids=[item_id]
+ )
+ except Exception as e:
+ log.warning(_("Can't get item linked with invitation: {reason}").format(
+ reason=e))
+ try:
+ item_elt = items[0]
+ except IndexError:
+ log.warning(_("Invitation was linking to a non existing item"))
+ raise exceptions.DataError
- args = [service, node, item_id, item_elt]
- defer.returnValue((namespace, args))
+ try:
+ namespace = item_elt.firstChildElement().uri
+ except Exception as e:
+ log.warning(_("Can't retrieve namespace of invitation: {reason}").format(
+ reason = e))
+ raise exceptions.DataError
- def _parseFileSharingElt(self, client, file_sharing_elt):
+ args = [service, node, item_id, item_elt]
+ else:
+ try:
+ node_data_elt = next(pubsub_elt.elements((NS_INVITATION, "node_data")))
+ except StopIteration:
+ raise exceptions.DataError("Bad invitation, ignoring")
+ namespace = node_data_elt['namespace']
+ args = [service, node, None, node_data_elt]
+
+ return namespace, args
+
+ async def _parseFileSharingElt(self, client, file_sharing_elt):
try:
service = jid.JID(file_sharing_elt["service"])
except (RuntimeError, KeyError):
log.warning(_("Bad invitation, ignoring"))
raise exceptions.DataError
repos_type = file_sharing_elt.getAttribute("type", "files")
- namespace = file_sharing_elt.getAttribute("namespace")
+ sharing_ns = file_sharing_elt.getAttribute("namespace")
path = file_sharing_elt.getAttribute("path")
- args = [service, repos_type, namespace, path]
+ args = [service, repos_type, sharing_ns, path]
ns_fis = self.host.getNamespace("fis")
return ns_fis, args
- @defer.inlineCallbacks
- def onInvitation(self, message_elt, client):
+ async def onInvitation(self, message_elt, client):
log.debug("invitation received [{profile}]".format(profile=client.profile))
invitation_elt = message_elt.invitation
@@ -275,7 +309,7 @@ class Invitation(object):
xml = elt.toXml()))
continue
try:
- namespace, args = yield method(client, elt)
+ namespace, args = await method(client, elt)
except exceptions.DataError:
log.warning("Can't parse invitation element: {xml}".format(
xml = elt.toXml()))
@@ -288,7 +322,7 @@ class Invitation(object):
'No handler for namespace "{namespace}", invitation ignored')
.format(namespace=namespace))
else:
- cb(client, name, extra, *args)
+ await utils.asDeferred(cb, client, namespace, name, extra, *args)
@implementer(iwokkel.IDisco)
@@ -299,7 +333,10 @@ class PubsubInvitationHandler(XMPPHandler):
def connectionInitialized(self):
self.xmlstream.addObserver(
- INVITATION, self.plugin_parent.onInvitation, client=self.parent
+ INVITATION,
+ lambda message_elt: defer.ensureDeferred(
+ self.plugin_parent.onInvitation(message_elt, client=self.parent)
+ ),
)
def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
diff --git a/sat/plugins/plugin_exp_invitation_file.py b/sat/plugins/plugin_exp_invitation_file.py
index f4685068..c430264f 100644
--- a/sat/plugins/plugin_exp_invitation_file.py
+++ b/sat/plugins/plugin_exp_invitation_file.py
@@ -19,6 +19,7 @@
from sat.core.i18n import _
from sat.core.constants import Const as C
from sat.core.log import getLogger
+from sat.core.xmpp import SatXMPPEntity
from sat.tools.common import data_format
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
@@ -69,7 +70,17 @@ class FileSharingInvitation:
extra=extra)
)
- def onInvitation(self, client, name, extra, service, repos_type, namespace, path):
+ def onInvitation(
+ self,
+ client: SatXMPPEntity,
+ namespace: str,
+ name: str,
+ extra: dict,
+ service: jid.JID,
+ repos_type: str,
+ sharing_ns: str,
+ path: str
+ ):
if repos_type == "files":
type_human = _("file sharing")
elif repos_type == "photos":
@@ -81,11 +92,12 @@ class FileSharingInvitation:
type_human = _("file sharing")
log.info(_(
'{profile} has received an invitation for a files repository ({type_human}) '
- 'with namespace {namespace!r} at path [{path}]').format(
- profile=client.profile, type_human=type_human, namespace=namespace, path=path)
+ 'with namespace {sharing_ns!r} at path [{path}]').format(
+ profile=client.profile, type_human=type_human, sharing_ns=sharing_ns,
+ path=path)
)
return defer.ensureDeferred(
self.host.plugins['LIST_INTEREST'].registerFileSharing(
- client, service, repos_type, namespace, path, name, extra
+ client, service, repos_type, sharing_ns, path, name, extra
)
)
diff --git a/sat/plugins/plugin_exp_invitation_pubsub.py b/sat/plugins/plugin_exp_invitation_pubsub.py
new file mode 100644
index 00000000..bfa439cc
--- /dev/null
+++ b/sat/plugins/plugin_exp_invitation_pubsub.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+
+# SàT plugin to send invitations for Pubsub
+# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.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 .
+
+from typing import Optional
+from twisted.internet import defer
+from twisted.words.protocols.jabber import jid
+from twisted.words.xish import domish
+from sat.core.i18n import _
+from sat.core.constants import Const as C
+from sat.core.log import getLogger
+from sat.core.xmpp import SatXMPPEntity
+from sat.tools import utils
+from sat.tools.common import data_format
+
+log = getLogger(__name__)
+
+
+PLUGIN_INFO = {
+ C.PI_NAME: "Pubsub Invitation",
+ C.PI_IMPORT_NAME: "PUBSUB_INVITATION",
+ C.PI_TYPE: "EXP",
+ C.PI_PROTOCOLS: [],
+ C.PI_DEPENDENCIES: ["XEP-0060", "INVITATION"],
+ C.PI_RECOMMENDATIONS: [],
+ C.PI_MAIN: "PubsubInvitation",
+ C.PI_HANDLER: "no",
+ C.PI_DESCRIPTION: _("Invitations for pubsub based features"),
+}
+
+
+class PubsubInvitation:
+
+ def __init__(self, host):
+ log.info(_("Pubsub Invitation plugin initialization"))
+ self.host = host
+ self._p = host.plugins["XEP-0060"]
+ # namespace to handler map
+ self._ns_handler = {}
+ host.bridge.addMethod(
+ "psInvite",
+ ".plugin",
+ in_sign="sssssss",
+ out_sign="",
+ method=self._sendPubsubInvitation,
+ async_=True
+ )
+
+ def register(
+ self,
+ namespace: str,
+ handler
+ ) -> None:
+ self._ns_handler[namespace] = handler
+ self.host.plugins["INVITATION"].registerNamespace(namespace, self.onInvitation)
+
+ def _sendPubsubInvitation(
+ self, invitee_jid_s, service_s, node, item_id=None,
+ name=None, extra_s='', profile_key=C.PROF_KEY_NONE):
+ client = self.host.getClient(profile_key)
+ invitee_jid = jid.JID(invitee_jid_s)
+ service = jid.JID(service_s)
+ extra = data_format.deserialise(extra_s)
+ return defer.ensureDeferred(
+ self.invite(
+ client,
+ invitee_jid,
+ service,
+ node,
+ item_id or None,
+ name=name or None,
+ extra=extra
+ )
+ )
+
+ async def invite(
+ self,
+ client: SatXMPPEntity,
+ invitee_jid: jid.JID,
+ service: jid.JID,
+ node: str,
+ item_id: Optional[str] = None,
+ name: str = '',
+ extra: Optional[dict] = None,
+ ) -> None:
+ if extra is None:
+ extra = {}
+ else:
+ namespace = extra.get("namespace")
+ if namespace:
+ try:
+ handler = self._ns_handler[namespace]
+ preflight = handler.invitePreflight
+ except KeyError:
+ pass
+ except AttributeError:
+ log.debug(f"no invitePreflight method found for {namespace!r}")
+ else:
+ await utils.asDeferred(
+ preflight,
+ client, invitee_jid, service, node, item_id, name, extra
+ )
+ if item_id is None:
+ item_id = extra.pop("default_item_id", None)
+
+ # we authorize our invitee to see the nodes of interest
+ await self._p.setNodeAffiliations(client, service, node, {invitee_jid: "member"})
+ log.debug(f"affiliation set on {service}'s {node!r} node")
+
+ # now we send the invitation
+ self.host.plugins["INVITATION"].sendPubsubInvitation(
+ client,
+ invitee_jid,
+ service,
+ node,
+ item_id,
+ name=name or None,
+ extra=extra
+ )
+
+ async def onInvitation(
+ self,
+ client: SatXMPPEntity,
+ namespace: str,
+ name: str,
+ extra: dict,
+ service: jid.JID,
+ node: str,
+ item_id: Optional[str],
+ item_elt: domish.Element
+ ) -> None:
+ if extra is None:
+ extra = {}
+ try:
+ handler = self._ns_handler[namespace]
+ preflight = handler.onInvitationPreflight
+ except KeyError:
+ pass
+ except AttributeError:
+ log.debug(f"no onInvitationPreflight method found for {namespace!r}")
+ else:
+ await utils.asDeferred(
+ preflight,
+ client, namespace, name, extra, service, node, item_id, item_elt
+ )
+ if item_id is None:
+ item_id = extra.pop("default_item_id", None)
+ creator = extra.pop("creator", False)
+ element = extra.pop("element", None)
+ if not name:
+ name = extra.pop("name", "")
+
+ return self.host.plugins['LIST_INTEREST'].registerPubsub(
+ client, namespace, service, node, item_id, creator,
+ name, element, extra)