source: wokkel/xmppim.py @ 172:ea2774e1c71c

Last change on this file since 172:ea2774e1c71c was 172:ea2774e1c71c, checked in by Ralph Meijer <ralphm@…>, 8 years ago

Clean up of RosterItem? and RosterClientProtocol?.

RosterItem:

  • Renamed attributes jid and ask to entity and pendingOut respectively.
  • Can represent roster items to be removed or that have been removed.
  • Now has fromElement and toElement methods.

RosterRequest is a new class to represent roster request stanzas.

RosterClientProtocol:

  • Roster returned from getRoster is now indexed by JIDs (instead of the unicode representation of the JID).
  • Outgoing requests are now done using RosterRequest.
  • onRosterSet and onRosterRemove are deprecated in favor of setReceived and removeReceived, respectively. These are called with a RosterRequest to have access to addressing and roster version information.

RosterPushIgnored can be raised to return a service-unavailable stanza
error for unwanted pushes.

This also fixes a problem with checking the sender address for roster pushes.

Author: ralphm.
Fixes: #71.

  • Property exe set to *
File size: 28.9 KB
Line 
1# -*- test-case-name: wokkel.test.test_xmppim -*-
2#
3# Copyright (c) Ralph Meijer.
4# See LICENSE for details.
5
6"""
7XMPP IM protocol support.
8
9This module provides generic implementations for the protocols defined in
10U{RFC 6121<http://www.xmpp.org/rfcs/rfc6121.html>} (XMPP IM).
11"""
12
13import warnings
14
15from twisted.internet import defer
16from twisted.words.protocols.jabber import error
17from twisted.words.protocols.jabber.jid import JID
18from twisted.words.xish import domish
19
20from wokkel.generic import ErrorStanza, Stanza, Request
21from wokkel.subprotocols import IQHandlerMixin
22from wokkel.subprotocols import XMPPHandler
23
24NS_XML = 'http://www.w3.org/XML/1998/namespace'
25NS_ROSTER = 'jabber:iq:roster'
26
27XPATH_ROSTER_SET = "/iq[@type='set']/query[@xmlns='%s']" % NS_ROSTER
28
29
30
31class Presence(domish.Element):
32    def __init__(self, to=None, type=None):
33        domish.Element.__init__(self, (None, "presence"))
34        if type:
35            self["type"] = type
36
37        if to is not None:
38            self["to"] = to.full()
39
40class AvailablePresence(Presence):
41    def __init__(self, to=None, show=None, statuses=None, priority=0):
42        Presence.__init__(self, to, type=None)
43
44        if show in ['away', 'xa', 'chat', 'dnd']:
45            self.addElement('show', content=show)
46
47        if statuses is not None:
48            for lang, status in statuses.iteritems():
49                s = self.addElement('status', content=status)
50                if lang:
51                    s[(NS_XML, "lang")] = lang
52
53        if priority != 0:
54            self.addElement('priority', content=unicode(int(priority)))
55
56class UnavailablePresence(Presence):
57    def __init__(self, to=None, statuses=None):
58        Presence.__init__(self, to, type='unavailable')
59
60        if statuses is not None:
61            for lang, status in statuses.iteritems():
62                s = self.addElement('status', content=status)
63                if lang:
64                    s[(NS_XML, "lang")] = lang
65
66class PresenceClientProtocol(XMPPHandler):
67
68    def connectionInitialized(self):
69        self.xmlstream.addObserver('/presence', self._onPresence)
70
71    def _getStatuses(self, presence):
72        statuses = {}
73        for element in presence.elements():
74            if element.name == 'status':
75                lang = element.getAttribute((NS_XML, 'lang'))
76                text = unicode(element)
77                statuses[lang] = text
78        return statuses
79
80    def _onPresence(self, presence):
81        type = presence.getAttribute("type", "available")
82        try:
83            handler = getattr(self, '_onPresence%s' % (type.capitalize()))
84        except AttributeError:
85            return
86        else:
87            handler(presence)
88
89    def _onPresenceAvailable(self, presence):
90        entity = JID(presence["from"])
91
92        show = unicode(presence.show or '')
93        if show not in ['away', 'xa', 'chat', 'dnd']:
94            show = None
95
96        statuses = self._getStatuses(presence)
97
98        try:
99            priority = int(unicode(presence.priority or '')) or 0
100        except ValueError:
101            priority = 0
102
103        self.availableReceived(entity, show, statuses, priority)
104
105    def _onPresenceUnavailable(self, presence):
106        entity = JID(presence["from"])
107
108        statuses = self._getStatuses(presence)
109
110        self.unavailableReceived(entity, statuses)
111
112    def _onPresenceSubscribed(self, presence):
113        self.subscribedReceived(JID(presence["from"]))
114
115    def _onPresenceUnsubscribed(self, presence):
116        self.unsubscribedReceived(JID(presence["from"]))
117
118    def _onPresenceSubscribe(self, presence):
119        self.subscribeReceived(JID(presence["from"]))
120
121    def _onPresenceUnsubscribe(self, presence):
122        self.unsubscribeReceived(JID(presence["from"]))
123
124
125    def availableReceived(self, entity, show=None, statuses=None, priority=0):
126        """
127        Available presence was received.
128
129        @param entity: entity from which the presence was received.
130        @type entity: {JID}
131        @param show: detailed presence information. One of C{'away'}, C{'xa'},
132                     C{'chat'}, C{'dnd'} or C{None}.
133        @type show: C{str} or C{NoneType}
134        @param statuses: dictionary of natural language descriptions of the
135                         availability status, keyed by the language
136                         descriptor. A status without a language
137                         specified, is keyed with C{None}.
138        @type statuses: C{dict}
139        @param priority: priority level of the resource.
140        @type priority: C{int}
141        """
142
143    def unavailableReceived(self, entity, statuses=None):
144        """
145        Unavailable presence was received.
146
147        @param entity: entity from which the presence was received.
148        @type entity: {JID}
149        @param statuses: dictionary of natural language descriptions of the
150                         availability status, keyed by the language
151                         descriptor. A status without a language
152                         specified, is keyed with C{None}.
153        @type statuses: C{dict}
154        """
155
156    def subscribedReceived(self, entity):
157        """
158        Subscription approval confirmation was received.
159
160        @param entity: entity from which the confirmation was received.
161        @type entity: {JID}
162        """
163
164    def unsubscribedReceived(self, entity):
165        """
166        Unsubscription confirmation was received.
167
168        @param entity: entity from which the confirmation was received.
169        @type entity: {JID}
170        """
171
172    def subscribeReceived(self, entity):
173        """
174        Subscription request was received.
175
176        @param entity: entity from which the request was received.
177        @type entity: {JID}
178        """
179
180    def unsubscribeReceived(self, entity):
181        """
182        Unsubscription request was received.
183
184        @param entity: entity from which the request was received.
185        @type entity: {JID}
186        """
187
188    def available(self, entity=None, show=None, statuses=None, priority=0):
189        """
190        Send available presence.
191
192        @param entity: optional entity to which the presence should be sent.
193        @type entity: {JID}
194        @param show: optional detailed presence information. One of C{'away'},
195                     C{'xa'}, C{'chat'}, C{'dnd'}.
196        @type show: C{str}
197        @param statuses: dictionary of natural language descriptions of the
198                         availability status, keyed by the language
199                         descriptor. A status without a language
200                         specified, is keyed with C{None}.
201        @type statuses: C{dict}
202        @param priority: priority level of the resource.
203        @type priority: C{int}
204        """
205        self.send(AvailablePresence(entity, show, statuses, priority))
206
207    def unavailable(self, entity=None, statuses=None):
208        """
209        Send unavailable presence.
210
211        @param entity: optional entity to which the presence should be sent.
212        @type entity: {JID}
213        @param statuses: dictionary of natural language descriptions of the
214                         availability status, keyed by the language
215                         descriptor. A status without a language
216                         specified, is keyed with C{None}.
217        @type statuses: C{dict}
218        """
219        self.send(UnavailablePresence(entity, statuses))
220
221    def subscribe(self, entity):
222        """
223        Send subscription request
224
225        @param entity: entity to subscribe to.
226        @type entity: {JID}
227        """
228        self.send(Presence(to=entity, type='subscribe'))
229
230    def unsubscribe(self, entity):
231        """
232        Send unsubscription request
233
234        @param entity: entity to unsubscribe from.
235        @type entity: {JID}
236        """
237        self.send(Presence(to=entity, type='unsubscribe'))
238
239    def subscribed(self, entity):
240        """
241        Send subscription confirmation.
242
243        @param entity: entity that subscribed.
244        @type entity: {JID}
245        """
246        self.send(Presence(to=entity, type='subscribed'))
247
248    def unsubscribed(self, entity):
249        """
250        Send unsubscription confirmation.
251
252        @param entity: entity that unsubscribed.
253        @type entity: {JID}
254        """
255        self.send(Presence(to=entity, type='unsubscribed'))
256
257
258
259class BasePresence(Stanza):
260    """
261    Stanza of kind presence.
262    """
263    stanzaKind = 'presence'
264
265
266
267class AvailabilityPresence(BasePresence):
268    """
269    Presence.
270
271    This represents availability presence (as opposed to
272    L{SubscriptionPresence}).
273
274    @ivar available: The availability being communicated.
275    @type available: C{bool}
276    @ivar show: More specific availability. Can be one of C{'chat'}, C{'away'},
277                C{'xa'}, C{'dnd'} or C{None}.
278    @type show: C{str} or C{NoneType}
279    @ivar statuses: Natural language texts to detail the (un)availability.
280                    These are represented as a mapping from language code
281                    (C{str} or C{None}) to the corresponding text (C{unicode}).
282                    If the key is C{None}, the associated text is in the
283                    default language.
284    @type statuses: C{dict}
285    @ivar priority: Priority level for this resource. Must be between -128 and
286                    127. Defaults to 0.
287    @type priority: C{int}
288    """
289
290    childParsers = {(None, 'show'): '_childParser_show',
291                     (None, 'status'): '_childParser_status',
292                     (None, 'priority'): '_childParser_priority'}
293
294    def __init__(self, recipient=None, sender=None, available=True,
295                       show=None, status=None, statuses=None, priority=0):
296        BasePresence.__init__(self, recipient=recipient, sender=sender)
297        self.available = available
298        self.show = show
299        self.statuses = statuses or {}
300        if status:
301            self.statuses[None] = status
302        self.priority = priority
303
304
305    def __get_status(self):
306        if None in self.statuses:
307            return self.statuses[None]
308        elif self.statuses:
309            for status in self.status.itervalues():
310                return status
311        else:
312            return None
313
314    status = property(__get_status)
315
316
317    def _childParser_show(self, element):
318        show = unicode(element)
319        if show in ('chat', 'away', 'xa', 'dnd'):
320            self.show = show
321
322
323    def _childParser_status(self, element):
324        lang = element.getAttribute((NS_XML, 'lang'), None)
325        text = unicode(element)
326        self.statuses[lang] = text
327
328
329    def _childParser_priority(self, element):
330        try:
331            self.priority = int(unicode(element))
332        except ValueError:
333            pass
334
335
336    def parseElement(self, element):
337        BasePresence.parseElement(self, element)
338
339        if self.stanzaType == 'unavailable':
340            self.available = False
341
342
343    def toElement(self):
344        if not self.available:
345            self.stanzaType = 'unavailable'
346
347        presence = BasePresence.toElement(self)
348
349        if self.available:
350            if self.show in ('chat', 'away', 'xa', 'dnd'):
351                presence.addElement('show', content=self.show)
352            if self.priority != 0:
353                presence.addElement('priority', content=unicode(self.priority))
354
355        for lang, text in self.statuses.iteritems():
356            status = presence.addElement('status', content=text)
357            if lang:
358                status[(NS_XML, 'lang')] = lang
359
360        return presence
361
362
363
364class SubscriptionPresence(BasePresence):
365    """
366    Presence subscription request or response.
367
368    This kind of presence is used to represent requests for presence
369    subscription and their replies.
370
371    Based on L{BasePresence} and {Stanza}, it just uses the C{stanzaType}
372    attribute to represent the type of subscription presence. This can be
373    one of C{'subscribe'}, C{'unsubscribe'}, C{'subscribed'} and
374    C{'unsubscribed'}.
375    """
376
377
378
379class ProbePresence(BasePresence):
380    """
381    Presence probe request.
382    """
383
384    stanzaType = 'probe'
385
386
387
388class BasePresenceProtocol(XMPPHandler):
389    """
390    XMPP Presence base protocol handler.
391
392    This class is the base for protocol handlers that receive presence
393    stanzas. Listening to all incoming presence stanzas, it extracts the
394    stanza's type and looks up a matching stanza parser and calls the
395    associated method. The method's name is the type + C{Received}. E.g.
396    C{availableReceived}. See L{PresenceProtocol} for a complete example.
397
398    @cvar presenceTypeParserMap: Maps presence stanza types to their respective
399        stanza parser classes (derived from L{Stanza}).
400    @type presenceTypeParserMap: C{dict}
401    """
402
403    presenceTypeParserMap = {}
404
405    def connectionInitialized(self):
406        self.xmlstream.addObserver("/presence", self._onPresence)
407
408
409
410    def _onPresence(self, element):
411        """
412        Called when a presence stanza has been received.
413        """
414        stanza = Stanza.fromElement(element)
415
416        presenceType = stanza.stanzaType or 'available'
417
418        try:
419            parser = self.presenceTypeParserMap[presenceType]
420        except KeyError:
421            return
422
423        presence = parser.fromElement(element)
424
425        try:
426            handler = getattr(self, '%sReceived' % presenceType)
427        except AttributeError:
428            return
429        else:
430            handler(presence)
431
432
433
434class PresenceProtocol(BasePresenceProtocol):
435
436    presenceTypeParserMap = {
437                'error': ErrorStanza,
438                'available': AvailabilityPresence,
439                'unavailable': AvailabilityPresence,
440                'subscribe': SubscriptionPresence,
441                'unsubscribe': SubscriptionPresence,
442                'subscribed': SubscriptionPresence,
443                'unsubscribed': SubscriptionPresence,
444                'probe': ProbePresence,
445                }
446
447
448    def errorReceived(self, presence):
449        """
450        Error presence was received.
451        """
452        pass
453
454
455    def availableReceived(self, presence):
456        """
457        Available presence was received.
458        """
459        pass
460
461
462    def unavailableReceived(self, presence):
463        """
464        Unavailable presence was received.
465        """
466        pass
467
468
469    def subscribedReceived(self, presence):
470        """
471        Subscription approval confirmation was received.
472        """
473        pass
474
475
476    def unsubscribedReceived(self, presence):
477        """
478        Unsubscription confirmation was received.
479        """
480        pass
481
482
483    def subscribeReceived(self, presence):
484        """
485        Subscription request was received.
486        """
487        pass
488
489
490    def unsubscribeReceived(self, presence):
491        """
492        Unsubscription request was received.
493        """
494        pass
495
496
497    def probeReceived(self, presence):
498        """
499        Probe presence was received.
500        """
501        pass
502
503
504    def available(self, recipient=None, show=None, statuses=None, priority=0,
505                        status=None, sender=None):
506        """
507        Send available presence.
508
509        @param recipient: Optional Recipient to which the presence should be
510            sent.
511        @type recipient: {JID}
512
513        @param show: Optional detailed presence information. One of C{'away'},
514            C{'xa'}, C{'chat'}, C{'dnd'}.
515        @type show: C{str}
516
517        @param statuses: Mapping of natural language descriptions of the
518           availability status, keyed by the language descriptor. A status
519           without a language specified, is keyed with C{None}.
520        @type statuses: C{dict}
521
522        @param priority: priority level of the resource.
523        @type priority: C{int}
524        """
525        presence = AvailabilityPresence(recipient=recipient, sender=sender,
526                                        show=show, statuses=statuses,
527                                        status=status, priority=priority)
528        self.send(presence.toElement())
529
530
531    def unavailable(self, recipient=None, statuses=None, sender=None):
532        """
533        Send unavailable presence.
534
535        @param recipient: Optional entity to which the presence should be sent.
536        @type recipient: {JID}
537
538        @param statuses: dictionary of natural language descriptions of the
539            availability status, keyed by the language descriptor. A status
540            without a language specified, is keyed with C{None}.
541        @type statuses: C{dict}
542        """
543        presence = AvailabilityPresence(recipient=recipient, sender=sender,
544                                        available=False, statuses=statuses)
545        self.send(presence.toElement())
546
547
548    def subscribe(self, recipient, sender=None):
549        """
550        Send subscription request
551
552        @param recipient: Entity to subscribe to.
553        @type recipient: {JID}
554        """
555        presence = SubscriptionPresence(recipient=recipient, sender=sender)
556        presence.stanzaType = 'subscribe'
557        self.send(presence.toElement())
558
559
560    def unsubscribe(self, recipient, sender=None):
561        """
562        Send unsubscription request
563
564        @param recipient: Entity to unsubscribe from.
565        @type recipient: {JID}
566        """
567        presence = SubscriptionPresence(recipient=recipient, sender=sender)
568        presence.stanzaType = 'unsubscribe'
569        self.send(presence.toElement())
570
571
572    def subscribed(self, recipient, sender=None):
573        """
574        Send subscription confirmation.
575
576        @param recipient: Entity that subscribed.
577        @type recipient: {JID}
578        """
579        presence = SubscriptionPresence(recipient=recipient, sender=sender)
580        presence.stanzaType = 'subscribed'
581        self.send(presence.toElement())
582
583
584    def unsubscribed(self, recipient, sender=None):
585        """
586        Send unsubscription confirmation.
587
588        @param recipient: Entity that unsubscribed.
589        @type recipient: {JID}
590        """
591        presence = SubscriptionPresence(recipient=recipient, sender=sender)
592        presence.stanzaType = 'unsubscribed'
593        self.send(presence.toElement())
594
595
596    def probe(self, recipient, sender=None):
597        """
598        Send presence probe.
599
600        @param recipient: Entity to be probed.
601        @type recipient: {JID}
602        """
603        presence = ProbePresence(recipient=recipient, sender=sender)
604        self.send(presence.toElement())
605
606
607
608class RosterItem(object):
609    """
610    Roster item.
611
612    This represents one contact from an XMPP contact list known as roster.
613
614    @ivar entity: The JID of the contact.
615    @type entity: L{JID}
616    @ivar name: The associated nickname for this contact.
617    @type name: C{unicode}
618    @ivar subscriptionTo: Subscription state to contact's presence. If C{True},
619                          the roster owner is subscribed to the presence
620                          information of the contact.
621    @type subscriptionTo: C{bool}
622    @ivar subscriptionFrom: Contact's subscription state. If C{True}, the
623                            contact is subscribed to the presence information
624                            of the roster owner.
625    @type subscriptionFrom: C{bool}
626    @ivar pendingOut: Whether the subscription request to this contact is
627        pending.
628    @type pendingOut: C{bool}
629    @ivar groups: Set of groups this contact is categorized in. Groups are
630                  represented by an opaque identifier of type C{unicode}.
631    @type groups: C{set}
632    @ivar approved: Signals pre-approved subscription.
633    @type approved: C{bool}
634    @ivar remove: Signals roster item removal.
635    @type remove: C{bool}
636    """
637
638    __subscriptionStates = {(False, False): None,
639                            (True, False): 'to',
640                            (False, True): 'from',
641                            (True, True): 'both'}
642
643    def __init__(self, entity, subscriptionTo=False, subscriptionFrom=False,
644                       name=u'', groups=None):
645        self.entity = entity
646        self.subscriptionTo = subscriptionTo
647        self.subscriptionFrom = subscriptionFrom
648        self.name = name
649        self.groups = groups or set()
650
651        self.pendingOut = False
652        self.approved = False
653        self.remove = False
654
655
656    def __getJID(self):
657        warnings.warn(
658            "wokkel.xmppim.RosterItem.jid is deprecated. "
659            "Use RosterItem.entity instead.",
660            DeprecationWarning)
661        return self.entity
662
663
664    def __setJID(self, value):
665        warnings.warn(
666            "wokkel.xmppim.RosterItem.jid is deprecated. "
667            "Use RosterItem.entity instead.",
668            DeprecationWarning)
669        self.entity = value
670
671
672    jid = property(__getJID, __setJID, doc="""
673            JID of the contact. Deprecated in favour of C{entity}.""")
674
675
676    def __getAsk(self):
677        warnings.warn(
678            "wokkel.xmppim.RosterItem.ask is deprecated. "
679            "Use RosterItem.pendingOut instead.",
680            DeprecationWarning)
681        return self.pendingOut
682
683
684    def __setAsk(self, value):
685        warnings.warn(
686            "wokkel.xmppim.RosterItem.ask is deprecated. "
687            "Use RosterItem.pendingOut instead.",
688            DeprecationWarning)
689        self.pendingOut = value
690
691
692    ask = property(__getAsk, __setAsk, doc="""
693            Pending out subscription. Deprecated in favour of C{pendingOut}.""")
694
695
696    def toElement(self):
697        element = domish.Element((NS_ROSTER, 'item'))
698        element['jid'] = self.entity.full()
699
700        if self.remove:
701            subscription = 'remove'
702        else:
703            subscription = self.__subscriptionStates[self.subscriptionTo,
704                                                     self.subscriptionFrom]
705
706            if self.pendingOut:
707                element['ask'] = u'subscribe'
708
709            if self.name:
710                element['name'] = self.name
711
712            if self.approved:
713                element['approved'] = u'true'
714
715            if self.groups:
716                for group in self.groups:
717                    element.addElement('group', content=group)
718
719        if subscription:
720            element['subscription'] = subscription
721
722        return element
723
724
725    @classmethod
726    def fromElement(Class, element):
727        entity = JID(element['jid'])
728        item = Class(entity)
729        subscription = element.getAttribute('subscription')
730        if subscription == 'remove':
731            item.remove = True
732        else:
733            item.name = element.getAttribute('name', u'')
734            item.subscriptionTo = subscription in ('to', 'both')
735            item.subscriptionFrom = subscription in ('from', 'both')
736            item.pendingOut = element.getAttribute('ask') == 'subscribe'
737            item.approved = element.getAttribute('approved') in ('true', '1')
738            for subElement in domish.generateElementsQNamed(element.children,
739                                                            'group', NS_ROSTER):
740                item.groups.add(unicode(subElement))
741        return item
742
743
744
745class RosterRequest(Request):
746    """
747    Roster request.
748
749    @ivar item: Roster item to be set or pushed.
750    @type item: L{RosterItem}.
751    """
752    item = None
753
754    def parseRequest(self, element):
755        for child in element.elements(NS_ROSTER, 'item'):
756            self.item = RosterItem.fromElement(child)
757            break
758
759
760    def toElement(self):
761        element = Request.toElement(self)
762        query = element.addElement((NS_ROSTER, 'query'))
763        if self.item:
764            query.addChild(self.item.toElement())
765        return element
766
767
768
769class RosterPushIgnored(Exception):
770    """
771    Raised when this entity doesn't want to accept/trust a roster push.
772
773    To avert presence leaks, a handler can raise L{RosterPushIgnored} when
774    not accepting a roster push (directly or via Deferred). This will
775    result in a C{'service-unavailable'} error being sent in return.
776    """
777
778
779
780class RosterClientProtocol(XMPPHandler, IQHandlerMixin):
781    """
782    Client side XMPP roster protocol.
783
784    The roster can be retrieved using L{getRoster}. Subsequent changes to the
785    roster will be pushed, resulting in calls to L{setReceived} or
786    L{removeReceived}. These methods should be overridden to handle the
787    roster pushes.
788
789    RFC 6121 specifically allows entities other than a user's server to
790    hold a roster for that user. However, how a client should deal with
791    that is currently not yet specfied.
792
793    By default roster pushes from other source. I.e. when C{request.sender}
794    is set but the sender's bare JID is different from the user's bare JID.
795    Set L{allowAnySender} to allow roster pushes from any sender. To
796    avert presence leaks, L{RosterPushIgnored} should then be raised for
797    pushes from untrusted senders.
798
799    @cvar allowAnySender: Flag to allow roster pushes from any sender.
800        C{False} by default.
801    @type allowAnySender: C{boolean}
802    """
803
804    allowAnySender = False
805    iqHandlers = {XPATH_ROSTER_SET: "_onRosterSet"}
806
807
808    def connectionInitialized(self):
809        self.xmlstream.addObserver(XPATH_ROSTER_SET, self.handleRequest)
810
811
812    def getRoster(self):
813        """
814        Retrieve contact list.
815
816        @return: Roster as a mapping from L{JID} to L{RosterItem}.
817        @rtype: L{twisted.internet.defer.Deferred}
818        """
819
820        def processRoster(result):
821            roster = {}
822            for element in domish.generateElementsQNamed(result.query.children,
823                                                         'item', NS_ROSTER):
824                item = RosterItem.fromElement(element)
825                roster[item.entity] = item
826
827            return roster
828
829        request = RosterRequest(stanzaType='get')
830        d = self.request(request)
831        d.addCallback(processRoster)
832        return d
833
834
835    def removeItem(self, entity):
836        """
837        Remove an item from the contact list.
838
839        @param entity: The contact to remove the roster item for.
840        @type entity: L{JID<twisted.words.protocols.jabber.jid.JID>}
841        @rtype: L{twisted.internet.defer.Deferred}
842        """
843        request = RosterRequest(stanzaType='set')
844        request.item = RosterItem(entity)
845        request.item.remove = True
846        return self.request(request)
847
848
849    def _onRosterSet(self, iq):
850        def trapIgnored(failure):
851            failure.trap(RosterPushIgnored)
852            raise error.StanzaError('service-unavailable')
853
854        request = RosterRequest.fromElement(iq)
855
856        if (not self.allowAnySender and
857                request.sender and
858                request.sender.userhostJID() !=
859                self.parent.jid.userhostJID()):
860            d = defer.fail(RosterPushIgnored())
861        elif request.item.remove:
862            d = defer.maybeDeferred(self.removeReceived, request)
863        else:
864            d = defer.maybeDeferred(self.setReceived, request)
865        d.addErrback(trapIgnored)
866        return d
867
868
869    def setReceived(self, request):
870        """
871        Called when a roster push for a new or update item was received.
872
873        @param request: The push request.
874        @type request: L{RosterRequest}
875        """
876        if hasattr(self, 'onRosterSet'):
877            warnings.warn(
878                "wokkel.xmppim.RosterClientProtocol.onRosterSet "
879                "is deprecated. "
880                "Use RosterClientProtocol.setReceived instead.",
881                DeprecationWarning)
882            return defer.maybeDeferred(self.onRosterSet, request.item)
883
884
885    def removeReceived(self, request):
886        """
887        Called when a roster push for the removal of an item was received.
888
889        @param request: The push request.
890        @type request: L{RosterRequest}
891        """
892        if hasattr(self, 'onRosterRemove'):
893            warnings.warn(
894                "wokkel.xmppim.RosterClientProtocol.onRosterRemove "
895                "is deprecated. "
896                "Use RosterClientProtocol.removeReceived instead.",
897                DeprecationWarning)
898            return defer.maybeDeferred(self.onRosterRemove,
899                                       request.item.entity)
900
901
902
903class Message(Stanza):
904    """
905    A message stanza.
906    """
907
908    stanzaKind = 'message'
909
910    childParsers = {
911            (None, 'body'): '_childParser_body',
912            (None, 'subject'): '_childParser_subject',
913            }
914
915    def __init__(self, recipient=None, sender=None, body=None, subject=None):
916        Stanza.__init__(self, recipient, sender)
917        self.body = body
918        self.subject = subject
919
920
921    def _childParser_body(self, element):
922        self.body = unicode(element)
923
924
925    def _childParser_subject(self, element):
926        self.subject = unicode(element)
927
928
929    def toElement(self):
930        element = Stanza.toElement(self)
931
932        if self.body:
933            element.addElement('body', content=self.body)
934        if self.subject:
935            element.addElement('subject', content=self.subject)
936
937        return element
938
939
940
941class MessageProtocol(XMPPHandler):
942    """
943    Generic XMPP subprotocol handler for incoming message stanzas.
944    """
945
946    messageTypes = None, 'normal', 'chat', 'headline', 'groupchat'
947
948    def connectionInitialized(self):
949        self.xmlstream.addObserver("/message", self._onMessage)
950
951    def _onMessage(self, message):
952        if message.handled:
953            return
954
955        messageType = message.getAttribute("type")
956
957        if messageType == 'error':
958            return
959
960        if messageType not in self.messageTypes:
961            message["type"] = 'normal'
962
963        self.onMessage(message)
964
965    def onMessage(self, message):
966        """
967        Called when a message stanza was received.
968        """
Note: See TracBrowser for help on using the repository browser.