diff --git a/sat/plugins/plugin_exp_events.py b/sat/plugins/plugin_exp_events.py
old mode 100755
new mode 100644
index c9fa5c35..284bf49c
--- a/sat/plugins/plugin_exp_events.py
+++ b/sat/plugins/plugin_exp_events.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+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
-
-log = getLogger(__name__)
from sat.tools import utils
from sat.tools.common import uri as xmpp_uri
from sat.tools.common import date_utils
@@ -32,29 +31,24 @@ from twisted.words.xish import domish
from wokkel import disco, iwokkel
from zope.interface import implements
from twisted.words.protocols.jabber.xmlstream import XMPPHandler
-
from wokkel import pubsub
-import shortuuid
+
+log = getLogger(__name__)
PLUGIN_INFO = {
- C.PI_NAME: "Event plugin",
+ C.PI_NAME: "Events",
C.PI_IMPORT_NAME: "EVENTS",
C.PI_TYPE: "EXP",
C.PI_PROTOCOLS: [],
- C.PI_DEPENDENCIES: ["XEP-0060"],
- C.PI_RECOMMENDATIONS: ["INVITATIONS", "XEP-0277"],
+ C.PI_DEPENDENCIES: [u"XEP-0060", u"INVITATION", u"LIST_INTEREST"],
+ C.PI_RECOMMENDATIONS: ["XEP-0277", "EMAIL_INVITATION"],
C.PI_MAIN: "Events",
C.PI_HANDLER: "yes",
- C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management"""),
+ C.PI_DESCRIPTION: _(u"""Experimental implementation of XMPP events management"""),
}
NS_EVENT = "org.salut-a-toi.event:0"
-NS_EVENT_LIST = NS_EVENT + "#list"
-NS_EVENT_INVIT = NS_EVENT + "#invitation"
-INVITATION = '/message[@type="chat"]/invitation[@xmlns="{ns_invit}"]'.format(
- ns_invit=NS_EVENT_INVIT
-)
class Events(object):
@@ -64,8 +58,10 @@ class Events(object):
log.info(_(u"Event plugin initialization"))
self.host = host
self._p = self.host.plugins["XEP-0060"]
- self._i = self.host.plugins.get("INVITATIONS")
+ self._i = self.host.plugins.get("EMAIL_INVITATION")
self._b = self.host.plugins.get("XEP-0277")
+ self.host.plugins[u"INVITATION"].registerNamespace(NS_EVENT,
+ self.register)
host.bridge.addMethod(
"eventGet",
".plugin",
@@ -229,11 +225,12 @@ class Events(object):
items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_])
try:
event_elt = next(items[0].elements(NS_EVENT, u"event"))
+ except StopIteration:
+ raise exceptions.NotFound(_(u"No event element has been found"))
except IndexError:
raise exceptions.NotFound(_(u"No event with this id has been found"))
defer.returnValue(event_elt)
- @defer.inlineCallbacks
def register(self, client, service, node, event_id, event_elt, creator=False):
"""register evenement in personal events list
@@ -244,32 +241,13 @@ class Events(object):
note that this element will be modified in place
@param creator(bool): True if client's profile is the creator of the node
"""
- # we save a link to the event in our local list
- try:
- # TODO: check auto-create, no need to create node first if available
- options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}
- yield self._p.createNode(
- client,
- client.jid.userhostJID(),
- nodeIdentifier=NS_EVENT_LIST,
- options=options,
- )
- except error.StanzaError as e:
- if e.condition == u"conflict":
- log.debug(_(u"requested node already exists"))
- link_elt = event_elt.addElement((NS_EVENT_LIST, "link"))
+ link_elt = event_elt.addElement("link")
link_elt["service"] = service.full()
link_elt["node"] = node
link_elt["item"] = event_id
- item_id = xmpp_uri.buildXMPPUri(
- u"pubsub", path=service.full(), node=node, item=event_id
- )
- if creator:
- event_elt["creator"] = "true"
- item_elt = pubsub.Item(id=item_id, payload=event_elt)
- yield self._p.publish(
- client, client.jid.userhostJID(), NS_EVENT_LIST, items=[item_elt]
- )
+ return self.host.plugins[u'LIST_INTEREST'].registerPubsub(
+ client, NS_EVENT, service, node, event_id, creator,
+ element=event_elt)
def _eventGet(self, service, node, id_=u"", profile_key=C.PROF_KEY_NONE):
service = jid.JID(service) if service else None
@@ -396,17 +374,11 @@ class Events(object):
yield self.register(client, service, node, event_id, event_elt, creator=True)
defer.returnValue(node)
- def _eventModify(
- self,
- service,
- node,
- id_,
- timestamp_update,
- data_update,
- profile_key=C.PROF_KEY_NONE,
- ):
+ def _eventModify(self, service, node, id_, timestamp_update, data_update,
+ profile_key=C.PROF_KEY_NONE):
service = jid.JID(service) if service else None
- node = node if node else NS_EVENT
+ if not node:
+ raise ValueError(_(u"missing node"))
client = self.host.getClient(profile_key)
return self.eventModify(
client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update
@@ -444,24 +416,24 @@ class Events(object):
return d
@defer.inlineCallbacks
- def eventsList(self, client, service, node):
+ def eventsList(self, client, service, node=None):
"""Retrieve list of registered events
@return list(tuple(int, dict)): list of events (timestamp + metadata)
"""
- if not node:
- node = NS_EVENT_LIST
- items = yield self._p.getItems(client, service, node)
+ items, metadata = yield self.host.plugins[u'LIST_INTEREST'].listInterests(
+ client, service, node, namespace=NS_EVENT)
events = []
- for item in items[0]:
+ for item in items:
try:
- event_elt = next(item.elements(NS_EVENT, u"event"))
+ event_elt = next(item.interest.pubsub.elements(NS_EVENT, u"event"))
except IndexError:
- log.error(
+ log.warning(
_(u"No event found in item {item_id}").format(item_id=item["id"])
)
- timestamp, data = self._parseEventElt(event_elt)
- events.append((timestamp, data))
+ else:
+ timestamp, data = self._parseEventElt(event_elt)
+ events.append((timestamp, data))
defer.returnValue(events)
def _eventInviteeGet(self, service, node, profile_key):
@@ -479,12 +451,12 @@ class Events(object):
@return (dict): a dict with current attendance status,
an empty dict is returned if nothing has been answered yed
"""
- items, metadata = yield self._p.getItems(
- client, service, node, item_ids=[client.jid.userhost()]
- )
try:
+ items, metadata = yield self._p.getItems(
+ client, service, node, item_ids=[client.jid.userhost()]
+ )
event_elt = next(items[0].elements(NS_EVENT, u"invitee"))
- except IndexError:
+ except (exceptions.NotFound, IndexError):
# no item found, event data are not set yet
defer.returnValue({})
data = {}
@@ -542,13 +514,9 @@ class Events(object):
event_elt = next(item.elements(NS_EVENT, u"invitee"))
except StopIteration:
# no item found, event data are not set yet
- log.warning(
- _(
- u"no data found for {item_id} (service: {service}, node: {node})".format(
- item_id=item["id"], service=service, node=node
- )
- )
- )
+ log.warning(_(
+ u"no data found for {item_id} (service: {service}, node: {node})"
+ .format(item_id=item["id"], service=service, node=node)))
else:
data = {}
for key in (u"attend", u"guests"):
@@ -559,30 +527,6 @@ class Events(object):
invitees[item["id"]] = data
defer.returnValue(invitees)
- def sendMessageInvitation(self, client, invitee_jid, service, node, item_id):
- """Send an invitation in a stanza
-
- @param invitee_jid(jid.JID): entitee to send invitation to
- @param service(jid.JID): pubsub service of the event
- @param node(unicode): node of the event
- @param item_id(unicode): id of the event
- """
- mess_data = {
- "from": client.jid,
- "to": invitee_jid,
- "uid": "",
- "message": {},
- "type": C.MESS_TYPE_CHAT,
- "subject": {},
- "extra": {},
- }
- client.generateMessageXML(mess_data)
- event_elt = mess_data["xml"].addElement("invitation", NS_EVENT_INVIT)
- event_elt["service"] = service.full()
- event_elt["node"] = node
- event_elt["item"] = item_id
- client.send(mess_data["xml"])
-
def _invite(self, invitee_jid, service, node, item_id, profile):
client = self.host.getClient(profile)
service = jid.JID(service) if service else None
@@ -644,23 +588,14 @@ class Events(object):
log.debug(_(u"affiliation set on blog and comments nodes"))
# now we send the invitation
- self.sendMessageInvitation(client, invitee_jid, service, node, item_id)
+ pubsub_invitation = self.host.plugins[u'PUBSUB_INVITATION']
+ pubsub_invitation.sendPubsubInvitation(client, invitee_jid, service, node,
+ item_id)
- def _inviteByEmail(
- self,
- service,
- node,
- id_=NS_EVENT,
- email=u"",
- emails_extra=None,
- name=u"",
- host_name=u"",
- language=u"",
- url_template=u"",
- message_subject=u"",
- message_body=u"",
- profile_key=C.PROF_KEY_NONE,
- ):
+ def _inviteByEmail(self, service, node, id_=NS_EVENT, email=u"", emails_extra=None,
+ name=u"", host_name=u"", language=u"", url_template=u"",
+ message_subject=u"", message_body=u"",
+ profile_key=C.PROF_KEY_NONE):
client = self.host.getClient(profile_key)
kwargs = {
u"profile": client.profile,
@@ -708,19 +643,6 @@ class Events(object):
# now that we have a jid, we can send normal invitation
yield self.invite(client, invitee_jid, service, node, id_)
- @defer.inlineCallbacks
- def onInvitation(self, message_elt, client):
- invitation_elt = message_elt.invitation
- try:
- service = jid.JID(invitation_elt["service"])
- node = invitation_elt["node"]
- event_id = invitation_elt["item"]
- except (RuntimeError, KeyError):
- log.warning(_(u"Bad invitation: {xml}").format(xml=message_elt.toXml()))
-
- event_elt = yield self.getEventElement(client, service, node, event_id)
- yield self.register(client, service, node, event_id, event_elt, creator=False)
-
class EventsHandler(XMPPHandler):
implements(iwokkel.IDisco)
@@ -728,20 +650,9 @@ class EventsHandler(XMPPHandler):
def __init__(self, plugin_parent):
self.plugin_parent = plugin_parent
- @property
- def host(self):
- return self.plugin_parent.host
-
- def connectionInitialized(self):
- self.xmlstream.addObserver(
- INVITATION, self.plugin_parent.onInvitation, client=self.parent
- )
-
def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
return [
disco.DiscoFeature(NS_EVENT),
- disco.DiscoFeature(NS_EVENT_LIST),
- disco.DiscoFeature(NS_EVENT_INVIT),
]
def getDiscoItems(self, requestor, target, nodeIdentifier=""):
diff --git a/sat/plugins/plugin_exp_invitation.py b/sat/plugins/plugin_exp_invitation.py
new file mode 100644
index 00000000..94b5a040
--- /dev/null
+++ b/sat/plugins/plugin_exp_invitation.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# SAT plugin to detect language (experimental)
+# Copyright (C) 2009-2019 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 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 implements
+from twisted.words.protocols.jabber.xmlstream import XMPPHandler
+
+log = getLogger(__name__)
+
+
+PLUGIN_INFO = {
+ C.PI_NAME: "Invitation",
+ C.PI_IMPORT_NAME: "INVITATION",
+ C.PI_TYPE: "EXP",
+ C.PI_PROTOCOLS: [],
+ C.PI_DEPENDENCIES: ["XEP-0060"],
+ C.PI_RECOMMENDATIONS: [],
+ C.PI_MAIN: "Invitation",
+ C.PI_HANDLER: "yes",
+ C.PI_DESCRIPTION: _(u"Experimental handling of invitations"),
+}
+
+NS_INVITATION = u"https://salut-a-toi/protocol/invitation:0"
+INVITATION = '/message/invitation[@xmlns="{ns_invit}"]'.format(
+ ns_invit=NS_INVITATION
+)
+NS_INVITATION_LIST = NS_INVITATION + u"#list"
+
+
+class Invitation(object):
+
+ def __init__(self, host):
+ log.info(_(u"Invitation plugin initialization"))
+ self.host = host
+ self._p = self.host.plugins["XEP-0060"]
+ # map from namespace of the invitation to callback handling it
+ self._ns_cb = {}
+
+ def getHandler(self, client):
+ return PubsubInvitationHandler(self)
+
+ def registerNamespace(self, namespace, callback):
+ """Register a callback for a namespace
+
+ @param namespace(unicode): namespace handled
+ @param callback(callbable): method handling the invitation
+ For pubsub invitation, it will be called with following arguments:
+ - client
+ - service(jid.JID): pubsub service jid
+ - node(unicode): pubsub node
+ - item_id(unicode, None): pubsub item id
+ - item_elt(domish.Element): item of the invitation
+ @raise exceptions.ConflictError: this namespace is already registered
+ """
+ if namespace in self._ns_cb:
+ raise exceptions.ConflictError(
+ u"invitation namespace {namespace} is already register with {callback}"
+ .format(namespace=namespace, callback=self._ns_cb[namespace]))
+ self._ns_cb[namespace] = callback
+
+ def sendPubsubInvitation(self, client, invitee_jid, service, node,
+ item_id):
+ """Send an 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
+ """
+ mess_data = {
+ "from": client.jid,
+ "to": invitee_jid,
+ "uid": "",
+ "message": {},
+ "type": C.MESS_TYPE_CHAT,
+ "subject": {},
+ "extra": {},
+ }
+ client.generateMessageXML(mess_data)
+ invitation_elt = mess_data["xml"].addElement("invitation", NS_INVITATION)
+ pubsub_elt = invitation_elt.addElement(u"pubsub")
+ pubsub_elt[u"service"] = service.full()
+ pubsub_elt[u"node"] = node
+ pubsub_elt[u"item"] = item_id
+ client.send(mess_data[u"xml"])
+
+ @defer.inlineCallbacks
+ 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(_(u"Bad invitation, ignoring"))
+ raise exceptions.DataError
+
+ try:
+ items, metadata = yield self._p.getItems(client, service, node,
+ item_ids=[item_id])
+ except Exception as e:
+ log.warning(_(u"Can't get item linked with invitation: {reason}").format(
+ reason=e))
+ try:
+ item_elt = items[0]
+ except IndexError:
+ log.warning(_(u"Invitation was linking to a non existing item"))
+ raise exceptions.DataError
+
+ try:
+ namespace = item_elt.firstChildElement().uri
+ except Exception as e:
+ log.warning(_(u"Can't retrieve namespace of invitation: {reason}").format(
+ reason = e))
+ raise exceptions.DataError
+
+ args = [service, node, item_id, item_elt]
+ defer.returnValue((namespace, args))
+
+ @defer.inlineCallbacks
+ def onInvitation(self, message_elt, client):
+ invitation_elt = message_elt.invitation
+ for elt in invitation_elt.elements():
+ if elt.uri != NS_INVITATION:
+ log.warning(u"unexpected element: {xml}".format(xml=elt.toXml()))
+ continue
+ if elt.name == u"pubsub":
+ method = self._parsePubsubElt
+ else:
+ log.warning(u"not implemented invitation element: {xml}".format(
+ xml = elt.toXml()))
+ continue
+ try:
+ namespace, args = yield method(client, elt)
+ except exceptions.DataError:
+ log.warning(u"Can't parse invitation element: {xml}".format(
+ xml = elt.toXml()))
+ continue
+
+ try:
+ cb = self._ns_cb[namespace]
+ except KeyError:
+ log.warning(_(u'No handler for namespace "{namespace}", invitation ignored')
+ .format(namespace=namespace))
+ else:
+ cb(client, *args)
+
+
+class PubsubInvitationHandler(XMPPHandler):
+ implements(iwokkel.IDisco)
+
+ def __init__(self, plugin_parent):
+ self.plugin_parent = plugin_parent
+
+ def connectionInitialized(self):
+ self.xmlstream.addObserver(
+ INVITATION, self.plugin_parent.onInvitation, client=self.parent
+ )
+
+ def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
+ return [
+ disco.DiscoFeature(NS_INVITATION),
+ ]
+
+ def getDiscoItems(self, requestor, target, nodeIdentifier=""):
+ return []
diff --git a/sat/plugins/plugin_exp_list_of_interest.py b/sat/plugins/plugin_exp_list_of_interest.py
new file mode 100644
index 00000000..07987211
--- /dev/null
+++ b/sat/plugins/plugin_exp_list_of_interest.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# SAT plugin to detect language (experimental)
+# Copyright (C) 2009-2019 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 sat.core.i18n import _
+from sat.core.constants import Const as C
+from sat.core.log import getLogger
+from wokkel import disco, iwokkel, pubsub
+from zope.interface import implements
+from twisted.internet import defer
+from twisted.words.protocols.jabber import error as jabber_error
+from twisted.words.protocols.jabber.xmlstream import XMPPHandler
+from twisted.words.xish import domish
+
+log = getLogger(__name__)
+
+
+PLUGIN_INFO = {
+ C.PI_NAME: "List of Interest",
+ C.PI_IMPORT_NAME: "LIST_INTEREST",
+ C.PI_TYPE: "EXP",
+ C.PI_PROTOCOLS: [],
+ C.PI_DEPENDENCIES: ["XEP-0060"],
+ C.PI_RECOMMENDATIONS: [],
+ C.PI_MAIN: "ListInterest",
+ C.PI_HANDLER: "yes",
+ C.PI_DESCRIPTION: _(u"Experimental handling of interesting XMPP locations"),
+}
+
+NS_LIST_INTEREST = "https://salut-a-toi/protocol/list-interest:0"
+
+
+class ListInterest(object):
+ namespace = NS_LIST_INTEREST
+
+ def __init__(self, host):
+ log.info(_(u"List of Interest plugin initialization"))
+ self.host = host
+ self._p = self.host.plugins["XEP-0060"]
+
+ def getHandler(self, client):
+ return ListInterestHandler(self)
+
+ @defer.inlineCallbacks
+ def createNode(self, client):
+ try:
+ # TODO: check auto-create, no need to create node first if available
+ options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}
+ yield self._p.createNode(
+ client,
+ client.jid.userhostJID(),
+ nodeIdentifier=NS_LIST_INTEREST,
+ options=options,
+ )
+ except jabber_error.StanzaError as e:
+ if e.condition == u"conflict":
+ log.debug(_(u"requested node already exists"))
+
+ @defer.inlineCallbacks
+ def registerPubsub(self, client, namespace, service, node, item_id=None,
+ creator=False, name=None, element=None):
+ """Register an interesting element in personal list
+
+ @param namespace(unicode): namespace of the interest
+ this is used as a cache, to avoid the need to retrieve the item only to get
+ its namespace
+ @param service(jid.JID): pubsub service of the
+ @param node(unicode): target pubsub node
+ @param item_id(unicode, None): target pubsub id
+ @param creator(bool): True if client's profile is the creator of the node
+ This is used a cache, to avoid the need to retrieve affiliations
+ @param name(unicode, None): name of the interest
+ @param element(domish.Element, None): element to attach
+ may be used to cache some extra data
+ """
+ yield self.createNode(client)
+ interest_elt = domish.Element((NS_LIST_INTEREST, u"interest"))
+ interest_elt[u"namespace"] = namespace
+ if name is not None:
+ interest_elt[u'name'] = name
+ pubsub_elt = interest_elt.addElement(u"pubsub")
+ pubsub_elt[u"service"] = service.full()
+ pubsub_elt[u"node"] = node
+ if item_id is not None:
+ pubsub_elt[u"item"] = item_id
+ if creator:
+ pubsub_elt[u"creator"] = C.BOOL_TRUE
+ if element is not None:
+ pubsub_elt.addChild(element)
+ item_elt = pubsub.Item(payload=interest_elt)
+ yield self._p.publish(
+ client, client.jid.userhostJID(), NS_LIST_INTEREST, items=[item_elt]
+ )
+
+ @defer.inlineCallbacks
+ def listInterests(self, client, service=None, node=None, namespace=None):
+ """Retrieve list of interests
+
+ @param service(jid.JID, None): service to use
+ None to use own PEP
+ @param node(unicode, None): node to use
+ None to use default node
+ @param namespace(unicode, None): filter interests of this namespace
+ None to retrieve all interests
+ @return: same as [XEP_0060.getItems]
+ """
+ # TODO: if a MAM filter were available, it would improve performances
+ if not node:
+ node = NS_LIST_INTEREST
+ items, metadata = yield self._p.getItems(client, service, node)
+ if namespace is not None:
+ filtered_items = []
+ for item in items:
+ try:
+ interest_elt = next(item.elements(NS_LIST_INTEREST, u"interest"))
+ except StopIteration:
+ log.warning(_(u"Missing interest element: {xml}").format(
+ xml=interest_elt.toXml()))
+ continue
+ if interest_elt.getAttribute(u"namespace") == namespace:
+ filtered_items.append(item)
+ items = filtered_items
+
+ defer.returnValue((items, metadata))
+
+
+class ListInterestHandler(XMPPHandler):
+ implements(iwokkel.IDisco)
+
+ def __init__(self, plugin_parent):
+ self.plugin_parent = plugin_parent
+
+ def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
+ return [
+ disco.DiscoFeature(NS_LIST_INTEREST),
+ ]
+
+ def getDiscoItems(self, requestor, target, nodeIdentifier=""):
+ return []
diff --git a/sat/plugins/plugin_misc_invitations.py b/sat/plugins/plugin_misc_email_invitation.py
similarity index 99%
rename from sat/plugins/plugin_misc_invitations.py
rename to sat/plugins/plugin_misc_email_invitation.py
index 89f94675..9349e095 100644
--- a/sat/plugins/plugin_misc_invitations.py
+++ b/sat/plugins/plugin_misc_email_invitation.py
@@ -35,7 +35,7 @@ log = getLogger(__name__)
PLUGIN_INFO = {
C.PI_NAME: "Invitations",
- C.PI_IMPORT_NAME: "INVITATIONS",
+ C.PI_IMPORT_NAME: "EMAIL_INVITATION",
C.PI_TYPE: C.PLUG_TYPE_MISC,
C.PI_DEPENDENCIES: ['XEP-0077'],
C.PI_RECOMMENDATIONS: ["IDENTITY"],