source: wokkel/pubsub.py @ 10:a5e3c32d23ca

Last change on this file since 10:a5e3c32d23ca was 10:a5e3c32d23ca, checked in by Ralph Meijer <ralphm@…>, 14 years ago

Add unsubscribe method to PubSubClient?.

Author: ralphm
Fixes #4.

File size: 25.2 KB
Line 
1# -*- test-case-name: wokkel.test.test_pubsub -*-
2#
3# Copyright (c) 2003-2008 Ralph Meijer
4# See LICENSE for details.
5
6"""
7XMPP publish-subscribe protocol.
8
9This protocol is specified in
10U{XEP-0060<http://www.xmpp.org/extensions/xep-0060.html>}.
11"""
12
13from zope.interface import implements
14
15from twisted.internet import defer
16from twisted.words.protocols.jabber import jid, error, xmlstream
17from twisted.words.xish import domish
18
19from wokkel import disco, data_form
20from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
21from wokkel.iwokkel import IPubSubClient, IPubSubService
22
23# Iq get and set XPath queries
24IQ_GET = '/iq[@type="get"]'
25IQ_SET = '/iq[@type="set"]'
26
27# Publish-subscribe namespaces
28NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
29NS_PUBSUB_EVENT = NS_PUBSUB + '#event'
30NS_PUBSUB_ERRORS = NS_PUBSUB + '#errors'
31NS_PUBSUB_OWNER = NS_PUBSUB + "#owner"
32NS_PUBSUB_NODE_CONFIG = NS_PUBSUB + "#node_config"
33NS_PUBSUB_META_DATA = NS_PUBSUB + "#meta-data"
34
35# In publish-subscribe namespace XPath query selector.
36IN_NS_PUBSUB = '[@xmlns="' + NS_PUBSUB + '"]'
37IN_NS_PUBSUB_OWNER = '[@xmlns="' + NS_PUBSUB_OWNER + '"]'
38
39# Publish-subscribe XPath queries
40PUBSUB_ELEMENT = '/pubsub' + IN_NS_PUBSUB
41PUBSUB_OWNER_ELEMENT = '/pubsub' + IN_NS_PUBSUB_OWNER
42PUBSUB_GET = IQ_GET + PUBSUB_ELEMENT
43PUBSUB_SET = IQ_SET + PUBSUB_ELEMENT
44PUBSUB_OWNER_GET = IQ_GET + PUBSUB_OWNER_ELEMENT
45PUBSUB_OWNER_SET = IQ_SET + PUBSUB_OWNER_ELEMENT
46
47# Publish-subscribe command XPath queries
48PUBSUB_PUBLISH = PUBSUB_SET + '/publish' + IN_NS_PUBSUB
49PUBSUB_CREATE = PUBSUB_SET + '/create' + IN_NS_PUBSUB
50PUBSUB_SUBSCRIBE = PUBSUB_SET + '/subscribe' + IN_NS_PUBSUB
51PUBSUB_UNSUBSCRIBE = PUBSUB_SET + '/unsubscribe' + IN_NS_PUBSUB
52PUBSUB_OPTIONS_GET = PUBSUB_GET + '/options' + IN_NS_PUBSUB
53PUBSUB_OPTIONS_SET = PUBSUB_SET + '/options' + IN_NS_PUBSUB
54PUBSUB_DEFAULT = PUBSUB_OWNER_GET + '/default' + IN_NS_PUBSUB_OWNER
55PUBSUB_CONFIGURE_GET = PUBSUB_OWNER_GET + '/configure' + IN_NS_PUBSUB_OWNER
56PUBSUB_CONFIGURE_SET = PUBSUB_OWNER_SET + '/configure' + IN_NS_PUBSUB_OWNER
57PUBSUB_SUBSCRIPTIONS = PUBSUB_GET + '/subscriptions' + IN_NS_PUBSUB
58PUBSUB_AFFILIATIONS = PUBSUB_GET + '/affiliations' + IN_NS_PUBSUB
59PUBSUB_AFFILIATIONS_GET = PUBSUB_OWNER_GET + '/affiliations' + \
60                          IN_NS_PUBSUB_OWNER
61PUBSUB_AFFILIATIONS_SET = PUBSUB_OWNER_SET + '/affiliations' + \
62                          IN_NS_PUBSUB_OWNER
63PUBSUB_SUBSCRIPTIONS_GET = PUBSUB_OWNER_GET + '/subscriptions' + \
64                          IN_NS_PUBSUB_OWNER
65PUBSUB_SUBSCRIPTIONS_SET = PUBSUB_OWNER_SET + '/subscriptions' + \
66                          IN_NS_PUBSUB_OWNER
67PUBSUB_ITEMS = PUBSUB_GET + '/items' + IN_NS_PUBSUB
68PUBSUB_RETRACT = PUBSUB_SET + '/retract' + IN_NS_PUBSUB
69PUBSUB_PURGE = PUBSUB_OWNER_SET + '/purge' + IN_NS_PUBSUB_OWNER
70PUBSUB_DELETE = PUBSUB_OWNER_SET + '/delete' + IN_NS_PUBSUB_OWNER
71
72class BadRequest(error.StanzaError):
73    """
74    Bad request stanza error.
75    """
76    def __init__(self):
77        error.StanzaError.__init__(self, 'bad-request')
78
79
80class SubscriptionPending(Exception):
81    """
82    Raised when the requested subscription is pending acceptance.
83    """
84
85
86class SubscriptionUnconfigured(Exception):
87    """
88    Raised when the requested subscription needs to be configured before
89    becoming active.
90    """
91
92
93class PubSubError(error.StanzaError):
94    """
95    Exception with publish-subscribe specific condition.
96    """
97    def __init__(self, condition, pubsubCondition, feature=None, text=None):
98        appCondition = domish.Element((NS_PUBSUB_ERRORS, pubsubCondition))
99        if feature:
100            appCondition['feature'] = feature
101        error.StanzaError.__init__(self, condition,
102                                         text=text,
103                                         appCondition=appCondition)
104
105
106class Unsupported(PubSubError):
107    def __init__(self, feature, text=None):
108        PubSubError.__init__(self, 'feature-not-implemented',
109                                   'unsupported',
110                                   feature,
111                                   text)
112
113
114class OptionsUnavailable(Unsupported):
115    def __init__(self):
116        Unsupported.__init__(self, 'subscription-options-unavailable')
117
118
119class Item(domish.Element):
120    """
121    Publish subscribe item.
122
123    This behaves like an object providing L{domish.IElement}.
124
125    Item payload can be added using C{addChild} or C{addRawXml}, or using the
126    C{payload} keyword argument to C{__init__}.
127    """
128
129    def __init__(self, id=None, payload=None):
130        """
131        @param id: optional item identifier
132        @type id: L{unicode}
133        @param payload: optional item payload. Either as a domish element, or
134                        as serialized XML.
135        @type payload: object providing L{domish.IElement} or L{unicode}.
136        """
137
138        domish.Element.__init__(self, (None, 'item'))
139        if id is not None:
140            self['id'] = id
141        if payload is not None:
142            if isinstance(payload, basestring):
143                self.addRawXml(payload)
144            else:
145                self.addChild(payload)
146
147
148class PubSubRequest(xmlstream.IQ):
149    """
150    Base class for publish subscribe user requests.
151
152    @cvar namespace: request namespace
153    @cvar verb: request verb
154    @cvar method: type attribute of the IQ request. Either C{'set'} or C{'get'}
155    @ivar command: command element of the request. This is the direct child of
156                   the C{pubsub} element in the C{namespace} with the name
157                   C{verb}.
158    """
159
160    namespace = NS_PUBSUB
161    method = 'set'
162
163    def __init__(self, xs):
164        xmlstream.IQ.__init__(self, xs, self.method)
165        self.addElement((self.namespace, 'pubsub'))
166
167        self.command = self.pubsub.addElement(self.verb)
168
169    def send(self, to):
170        """
171        Send out request.
172
173        Extends L{xmlstream.IQ.send} by requiring the C{to} parameter to be
174        a L{JID} instance.
175
176        @param to: Entity to send the request to.
177        @type to: L{JID}
178        """
179        destination = to.full()
180        return xmlstream.IQ.send(self, destination)
181
182
183class CreateNode(PubSubRequest):
184    verb = 'create'
185
186    def __init__(self, xs, node=None):
187        PubSubRequest.__init__(self, xs)
188        if node:
189            self.command["node"] = node
190
191
192class DeleteNode(PubSubRequest):
193    verb = 'delete'
194    def __init__(self, xs, node):
195        PubSubRequest.__init__(self, xs)
196        self.command["node"] = node
197
198
199class Subscribe(PubSubRequest):
200    verb = 'subscribe'
201
202    def __init__(self, xs, node, subscriber):
203        PubSubRequest.__init__(self, xs)
204        self.command["node"] = node
205        self.command["jid"] = subscriber.full()
206
207
208class Unsubscribe(PubSubRequest):
209    verb = 'unsubscribe'
210
211    def __init__(self, xs, node, subscriber):
212        PubSubRequest.__init__(self, xs)
213        self.command["node"] = node
214        self.command["jid"] = subscriber.full()
215
216
217class Publish(PubSubRequest):
218    verb = 'publish'
219
220    def __init__(self, xs, node):
221        PubSubRequest.__init__(self, xs)
222        self.command["node"] = node
223
224    def addItem(self, id=None, payload=None):
225        item = self.command.addElement("item")
226        item.addChild(payload)
227
228        if id is not None:
229            item["id"] = id
230
231        return item
232
233
234class PubSubClient(XMPPHandler):
235    """
236    Publish subscribe client protocol.
237    """
238
239    implements(IPubSubClient)
240
241    def connectionInitialized(self):
242        self.xmlstream.addObserver('/message/event[@xmlns="%s"]/items' %
243                                   NS_PUBSUB_EVENT, self._onItems)
244
245    def _onItems(self, message):
246        try:
247            service = jid.JID(message["from"])
248            recipient = jid.JID(message["to"])
249            nodeIdentifier = message.event.items["node"]
250        except KeyError:
251            return
252
253        items = [element for element in message.event.items.elements()
254                         if element.name == 'item']
255
256        self.itemsReceived(recipient, service, nodeIdentifier, items)
257
258    def itemsReceived(self, recipient, service, nodeIdentifier, items):
259        pass
260
261    def createNode(self, service, nodeIdentifier=None):
262        request = CreateNode(self.xmlstream, nodeIdentifier)
263
264        def cb(iq):
265            try:
266                new_node = iq.pubsub.create["node"]
267            except AttributeError:
268                # the suggested node identifier was accepted
269                new_node = nodeIdentifier
270            return new_node
271
272        return request.send(service).addCallback(cb)
273
274    def deleteNode(self, service, nodeIdentifier):
275        return DeleteNode(self.xmlstream, nodeIdentifier).send(service)
276
277    def subscribe(self, service, nodeIdentifier, subscriber):
278        request = Subscribe(self.xmlstream, nodeIdentifier, subscriber)
279
280        def cb(iq):
281            subscription = iq.pubsub.subscription["subscription"]
282
283            if subscription == 'pending':
284                raise SubscriptionPending
285            elif subscription == 'unconfigured':
286                raise SubscriptionUnconfigured
287            else:
288                # we assume subscription == 'subscribed'
289                # any other value would be invalid, but that should have
290                # yielded a stanza error.
291                return None
292
293        return request.send(service).addCallback(cb)
294
295    def unsubscribe(self, service, nodeIdentifier, subscriber):
296        request = Unsubscribe(self.xmlstream, nodeIdentifier, subscriber)
297        return request.send(service)
298
299    def publish(self, service, nodeIdentifier, items=[]):
300        request = Publish(self.xmlstream, nodeIdentifier)
301        for item in items:
302            request.command.addChild(item)
303
304        return request.send(service)
305
306
307class PubSubService(XMPPHandler, IQHandlerMixin):
308    """
309    Protocol implementation for a XMPP Publish Subscribe Service.
310
311    The word Service here is used as taken from the Publish Subscribe
312    specification. It is the party responsible for keeping nodes and their
313    subscriptions, and sending out notifications.
314
315    Methods from the L{IPubSubService} interface that are called as
316    a result of an XMPP request may raise exceptions. Alternatively the
317    deferred returned by these methods may have their errback called. These are
318    handled as follows:
319
320    * If the exception is an instance of L{error.StanzaError}, an error
321      response iq is returned.
322    * Any other exception is reported using L{log.msg}. An error response
323      with the condition C{internal-server-error} is returned.
324
325    The default implementation of said methods raises an L{Unsupported}
326    exception and are meant to be overridden.
327
328    @ivar discoIdentity: Service discovery identity as a dictionary with
329                         keys C{'category'}, C{'type'} and C{'name'}.
330    @ivar pubSubFeatures: List of supported publish-subscribe features for
331                          service discovery, as C{str}.
332    @type pubSubFeatures: C{list} or C{None}.
333    """
334
335    implements(IPubSubService)
336
337    iqHandlers = {
338            PUBSUB_PUBLISH: '_onPublish',
339            PUBSUB_CREATE: '_onCreate',
340            PUBSUB_SUBSCRIBE: '_onSubscribe',
341            PUBSUB_OPTIONS_GET: '_onOptionsGet',
342            PUBSUB_OPTIONS_SET: '_onOptionsSet',
343            PUBSUB_AFFILIATIONS: '_onAffiliations',
344            PUBSUB_ITEMS: '_onItems',
345            PUBSUB_RETRACT: '_onRetract',
346            PUBSUB_SUBSCRIPTIONS: '_onSubscriptions',
347            PUBSUB_UNSUBSCRIBE: '_onUnsubscribe',
348
349            PUBSUB_AFFILIATIONS_GET: '_onAffiliationsGet',
350            PUBSUB_AFFILIATIONS_SET: '_onAffiliationsSet',
351            PUBSUB_CONFIGURE_GET: '_onConfigureGet',
352            PUBSUB_CONFIGURE_SET: '_onConfigureSet',
353            PUBSUB_DEFAULT: '_onDefault',
354            PUBSUB_PURGE: '_onPurge',
355            PUBSUB_DELETE: '_onDelete',
356            PUBSUB_SUBSCRIPTIONS_GET: '_onSubscriptionsGet',
357            PUBSUB_SUBSCRIPTIONS_SET: '_onSubscriptionsSet',
358
359            }
360
361    def __init__(self):
362        self.discoIdentity = {'category': 'pubsub',
363                              'type': 'generic',
364                              'name': 'Generic Publish-Subscribe Service'}
365
366        self.pubSubFeatures = []
367
368    def connectionMade(self):
369        self.xmlstream.addObserver(PUBSUB_GET, self.handleRequest)
370        self.xmlstream.addObserver(PUBSUB_SET, self.handleRequest)
371        self.xmlstream.addObserver(PUBSUB_OWNER_GET, self.handleRequest)
372        self.xmlstream.addObserver(PUBSUB_OWNER_SET, self.handleRequest)
373
374    def getDiscoInfo(self, requestor, target, nodeIdentifier):
375        info = []
376
377        if not nodeIdentifier:
378            info.append(disco.DiscoIdentity(**self.discoIdentity))
379
380            info.append(disco.DiscoFeature(disco.NS_ITEMS))
381            info.extend([disco.DiscoFeature("%s#%s" % (NS_PUBSUB, feature))
382                         for feature in self.pubSubFeatures])
383
384            return defer.succeed(info)
385        else:
386            def toInfo(nodeInfo):
387                if not nodeInfo:
388                    return []
389
390                (nodeType, metaData) = nodeInfo
391                info.append(disco.Identity('pubsub', nodeType))
392                if metaData:
393                    form = data_form.Form(type="result",
394                                          form_type=NS_PUBSUB_META_DATA) 
395                    form.add_field("text-single",
396                                   "pubsub#node_type",
397                                   "The type of node (collection or leaf)",
398                                   nodeType)
399
400                    for metaDatum in metaData:
401                        form.add_field(**metaDatum)
402
403                    info.append(form)
404                return info
405
406            d = self.getNodeInfo(requestor, nodeIdentifier)
407            d.addCallback(toInfo)
408            return d
409
410    def getDiscoItems(self, requestor, target, nodeIdentifier):
411        if nodeIdentifier or self.hideNodes:
412            return defer.succeed([])
413
414        d = self.getNodes(requestor, target)
415        d.addCallback(lambda nodes: [disco.DiscoItem(target, node)
416                                     for node in nodes])
417        return d
418
419    def _onPublish(self, iq):
420        requestor = jid.internJID(iq["from"]).userhostJID()
421        service = jid.internJID(iq["to"])
422
423        try:
424            nodeIdentifier = iq.pubsub.publish["node"]
425        except KeyError:
426            raise BadRequest
427
428        items = []
429        for element in iq.pubsub.publish.elements():
430            if element.uri == NS_PUBSUB and element.name == 'item':
431                items.append(element)
432
433        return self.publish(requestor, service, nodeIdentifier, items)
434
435    def _onSubscribe(self, iq):
436        requestor = jid.internJID(iq["from"]).userhostJID()
437        service = jid.internJID(iq["to"])
438
439        try:
440            nodeIdentifier = iq.pubsub.subscribe["node"]
441            subscriber = jid.internJID(iq.pubsub.subscribe["jid"])
442        except KeyError:
443            raise BadRequest
444
445        def toResponse(subscription):
446            nodeIdentifier, state = subscription
447            response = domish.Element((NS_PUBSUB, "pubsub"))
448            subscription = response.addElement("subscription")
449            subscription["node"] = nodeIdentifier
450            subscription["jid"] = subscriber.full()
451            subscription["subscription"] = state
452            return response
453
454        d = self.subscribe(requestor, service, nodeIdentifier, subscriber)
455        d.addCallback(toResponse)
456        return d
457
458    def _onUnsubscribe(self, iq):
459        requestor = jid.internJID(iq["from"]).userhostJID()
460        service = jid.internJID(iq["to"])
461
462        try:
463            nodeIdentifier = iq.pubsub.unsubscribe["node"]
464            subscriber = jid.internJID(iq.pubsub.unsubscribe["jid"])
465        except KeyError:
466            raise BadRequest
467
468        return self.unsubscribe(requestor, service, nodeIdentifier, subscriber)
469
470    def _onOptionsGet(self, iq):
471        raise Unsupported('subscription-options-unavailable')
472
473    def _onOptionsSet(self, iq):
474        raise Unsupported('subscription-options-unavailable')
475
476    def _onSubscriptions(self, iq):
477        requestor = jid.internJID(iq["from"]).userhostJID()
478        service = jid.internJID(iq["to"])
479
480        def toResponse(result):
481            response = domish.Element((NS_PUBSUB, 'pubsub'))
482            subscriptions = response.addElement('subscriptions')
483            for node, subscriber, state in result:
484                item = subscriptions.addElement('subscription')
485                item['node'] = node
486                item['jid'] = subscriber.full()
487                item['subscription'] = state
488            return response
489
490        d = self.subscriptions(requestor, service)
491        d.addCallback(toResponse)
492        return d
493
494    def _onAffiliations(self, iq):
495        requestor = jid.internJID(iq["from"]).userhostJID()
496        service = jid.internJID(iq["to"])
497
498        def toResponse(result):
499            response = domish.Element((NS_PUBSUB, 'pubsub'))
500            affiliations = response.addElement('affiliations')
501
502            for nodeIdentifier, affiliation in result:
503                item = affiliations.addElement('affiliation')
504                item['node'] = nodeIdentifier
505                item['affiliation'] = affiliation
506
507            return response
508
509        d = self.affiliations(requestor, service)
510        d.addCallback(toResponse)
511        return d
512
513    def _onCreate(self, iq):
514        requestor = jid.internJID(iq["from"]).userhostJID()
515        service = jid.internJID(iq["to"])
516        nodeIdentifier = iq.pubsub.create.getAttribute("node")
517
518        def toResponse(result):
519            if not nodeIdentifier or nodeIdentifier != result:
520                response = domish.Element((NS_PUBSUB, 'pubsub'))
521                create = response.addElement('create')
522                create['node'] = result
523                return response
524            else:
525                return None
526
527        d = self.create(requestor, service, nodeIdentifier)
528        d.addCallback(toResponse)
529        return d
530
531    def _formFromConfiguration(self, options):
532        form = data_form.Form(type="form", form_type=NS_PUBSUB_NODE_CONFIG)
533
534        for option in options:
535            form.add_field(**option)
536
537        return form
538
539    def _onDefault(self, iq):
540        requestor = jid.internJID(iq["from"]).userhostJID()
541        service = jid.internJID(iq["to"])
542
543        def toResponse(options):
544            response = domish.Element((NS_PUBSUB_OWNER, "pubsub"))
545            default = response.addElement("default")
546            default.addChild(self._formFromConfiguration(options))
547            return response
548
549        d = self.getDefaultConfiguration(requestor, service)
550        d.addCallback(toResponse)
551        return d
552
553    def _onConfigureGet(self, iq):
554        requestor = jid.internJID(iq["from"]).userhostJID()
555        service = jid.internJID(iq["to"])
556        nodeIdentifier = iq.pubsub.configure.getAttribute("node")
557
558        def toResponse(options):
559            response = domish.Element((NS_PUBSUB_OWNER, "pubsub"))
560            configure = response.addElement("configure")
561            configure.addChild(self._formFromConfiguration(options))
562
563            if nodeIdentifier:
564                configure["node"] = nodeIdentifier
565
566            return response
567
568        d = self.getConfiguration(requestor, service, nodeIdentifier)
569        d.addCallback(toResponse)
570        return d
571
572    def _onConfigureSet(self, iq):
573        requestor = jid.internJID(iq["from"]).userhostJID()
574        service = jid.internJID(iq["to"])
575        nodeIdentifier = iq.pubsub.configure["node"]
576
577        def getFormOptions(self, form):
578            options = {}
579
580            for element in form.elements():
581                if element.name == 'field' and \
582                   element.uri == data_form.NS_X_DATA:
583                    try:
584                        options[element["var"]] = str(element.value)
585                    except (KeyError, AttributeError):
586                        raise BadRequest
587
588            return options
589
590        # Search configuration form with correct FORM_TYPE and process it
591
592        for element in iq.pubsub.configure.elements():
593            if element.name != 'x' or element.uri != data_form.NS_X_DATA:
594                continue
595
596            type = element.getAttribute("type")
597            if type == "cancel":
598                return None
599            elif type != "submit":
600                continue
601
602            options = getFormOptions(element)
603
604            if options["FORM_TYPE"] == NS_PUBSUB + "#node_config":
605                del options["FORM_TYPE"]
606                return self.setConfiguration(requestor, service,
607                                             nodeIdentifier, options)
608
609        raise BadRequest
610
611    def _onItems(self, iq):
612        requestor = jid.internJID(iq["from"]).userhostJID()
613        service = jid.internJID(iq["to"])
614
615        try:
616            nodeIdentifier = iq.pubsub.items["node"]
617        except KeyError:
618            raise BadRequest
619
620        maxItems = iq.pubsub.items.getAttribute('max_items')
621
622        if maxItems:
623            try:
624                maxItems = int(maxItems)
625            except ValueError:
626                raise BadRequest
627
628        itemIdentifiers = []
629        for child in iq.pubsub.items.elements():
630            if child.name == 'item' and child.uri == NS_PUBSUB:
631                try:
632                    itemIdentifiers.append(child["id"])
633                except KeyError:
634                    raise BadRequest
635
636        def toResponse(result):
637            response = domish.Element((NS_PUBSUB, 'pubsub'))
638            items = response.addElement('items')
639            items["node"] = nodeIdentifier
640
641            for item in result:
642                items.addRawXml(item)
643
644            return response
645
646        d = self.items(requestor, service, nodeIdentifier, maxItems,
647                       itemIdentifiers)
648        d.addCallback(toResponse)
649        return d
650
651    def _onRetract(self, iq):
652        requestor = jid.internJID(iq["from"]).userhostJID()
653        service = jid.internJID(iq["to"])
654
655        try:
656            nodeIdentifier = iq.pubsub.retract["node"]
657        except KeyError:
658            raise BadRequest
659
660        itemIdentifiers = []
661        for child in iq.pubsub.retract.elements():
662            if child.uri == NS_PUBSUB_OWNER and child.name == 'item':
663                try:
664                    itemIdentifiers.append(child["id"])
665                except KeyError:
666                    raise BadRequest
667
668        return self.retract(requestor, service, nodeIdentifier,
669                            itemIdentifiers)
670
671    def _onPurge(self, iq):
672        requestor = jid.internJID(iq["from"]).userhostJID()
673        service = jid.internJID(iq["to"])
674
675        try:
676            nodeIdentifier = iq.pubsub.purge["node"]
677        except KeyError:
678            raise BadRequest
679
680        return self.purge(requestor, service, nodeIdentifier)
681
682    def _onDelete(self, iq):
683        requestor = jid.internJID(iq["from"]).userhostJID()
684        service = jid.internJID(iq["to"])
685
686        try:
687            nodeIdentifier = iq.pubsub.delete["node"]
688        except KeyError:
689            raise BadRequest
690
691        return self.delete(requestor, service, nodeIdentifier)
692
693    def _onAffiliationsGet(self, iq):
694        raise Unsupported('modify-affiliations')
695
696    def _onAffiliationsSet(self, iq):
697        raise Unsupported('modify-affiliations')
698
699    def _onSubscriptionsGet(self, iq):
700        raise Unsupported('manage-subscriptions')
701
702    def _onSubscriptionsSet(self, iq):
703        raise Unsupported('manage-subscriptions')
704
705    # public methods
706
707    def notifyPublish(self, service, nodeIdentifier, notifications):
708
709        print notifications
710        for recipient, items in notifications:
711            message = domish.Element((None, "message"))
712            message["from"] = service.full()
713            message["to"] = recipient.full()
714            event = message.addElement((NS_PUBSUB_EVENT, "event"))
715            element = event.addElement("items")
716            element["node"] = nodeIdentifier
717            element.children = items
718            self.send(message)
719
720    def getNodeInfo(self, requestor, service, nodeIdentifier):
721        return None
722
723    def getNodes(self, requestor, service):
724        return []
725
726    def publish(self, requestor, service, nodeIdentifier, items):
727        raise Unsupported('publish')
728
729    def subscribe(self, requestor, service, nodeIdentifier, subscriber):
730        raise Unsupported('subscribe')
731
732    def unsubscribe(self, requestor, service, nodeIdentifier, subscriber):
733        raise Unsupported('subscribe')
734
735    def subscriptions(self, requestor, service):
736        raise Unsupported('retrieve-subscriptions')
737
738    def affiliations(self, requestor, service):
739        raise Unsupported('retrieve-affiliations')
740
741    def create(self, requestor, service, nodeIdentifier):
742        raise Unsupported('create-nodes')
743
744    def getDefaultConfiguration(self, requestor, service):
745        raise Unsupported('retrieve-default')
746
747    def getConfiguration(self, requestor, service, nodeIdentifier):
748        raise Unsupported('config-node')
749
750    def setConfiguration(self, requestor, service, nodeIdentifier, options):
751        raise Unsupported('config-node')
752
753    def items(self, requestor, service, nodeIdentifier, maxItems,
754                    itemIdentifiers):
755        raise Unsupported('retrieve-items')
756
757    def retract(self, requestor, service, nodeIdentifier, itemIdentifiers):
758        raise Unsupported('retract-items')
759
760    def purge(self, requestor, service, nodeIdentifier):
761        raise Unsupported('purge-nodes')
762
763    def delete(self, requestor, service, nodeIdentifier):
764        raise Unsupported('delete-nodes')
Note: See TracBrowser for help on using the repository browser.