view sat/plugins/plugin_exp_list_of_interest.py @ 2912:a3faf1c86596

plugin events: refactored invitation and personal lists logic: - invitation logic has been moved to a new generic "plugin_exp_invitation" plugin - plugin_misc_invitations has be rename "plugin_exp_email_invitation" to avoid confusion - personal event list has be refactored to use a new experimental "list of interest", which regroup all interestings items, events or other ones
author Goffi <goffi@goffi.org>
date Sun, 14 Apr 2019 08:21:51 +0200
parents
children 672e6be3290f
line wrap: on
line source

#!/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 <http://www.gnu.org/licenses/>.

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 []