Ignore:
Timestamp:
Jul 17, 2009, 3:19:40 PM (13 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Convert:
svn:b33ecbfc-034c-dc11-8662-000475d9059e/trunk@186
Message:

Implement a better presence protocol handler PresenceProtocol?.

This handler is not client-only, like PresenceClientProtocol?, and accepts
sender arguments to all methods for outgoing presence. The *Received
methods now expect a single argument that is a parsed representation of the
presence stanza.

Also included is a simple XMPP server example that connects via the
server-to-server protocol, accepting presence requests and echoing incoming
messages for all potential addresses at a domain.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • wokkel/xmppim.py

    r63 r68  
    11# -*- test-case-name: wokkel.test.test_xmppim -*-
    22#
    3 # Copyright (c) 2003-2008 Ralph Meijer
     3# Copyright (c) 2003-2009 Ralph Meijer
    44# See LICENSE for details.
    55
     
    1717
    1818from wokkel.compat import IQ
     19from wokkel.generic import ErrorStanza, Stanza
    1920from wokkel.subprotocols import XMPPHandler
    2021
     
    114115    def _onPresenceUnsubscribe(self, presence):
    115116        self.unsubscribeReceived(JID(presence["from"]))
     117
    116118
    117119    def availableReceived(self, entity, show=None, statuses=None, priority=0):
     
    246248        """
    247249        self.send(Presence(to=entity, type='unsubscribed'))
     250
     251
     252
     253class BasePresence(Stanza):
     254    """
     255    Stanza of kind presence.
     256    """
     257    stanzaKind = 'presence'
     258
     259
     260
     261class AvailabilityPresence(BasePresence):
     262    """
     263    Presence.
     264
     265    This represents availability presence (as opposed to
     266    L{SubscriptionPresence}).
     267
     268    @ivar available: The availability being communicated.
     269    @type available: C{bool}
     270    @ivar show: More specific availability. Can be one of C{'chat'}, C{'away'},
     271                C{'xa'}, C{'dnd'} or C{None}.
     272    @type show: C{str} or C{NoneType}
     273    @ivar statuses: Natural language texts to detail the (un)availability.
     274                    These are represented as a mapping from language code
     275                    (C{str} or C{None}) to the corresponding text (C{unicode}).
     276                    If the key is C{None}, the associated text is in the
     277                    default language.
     278    @type statuses: C{dict}
     279    @ivar priority: Priority level for this resource. Must be between -128 and
     280                    127. Defaults to 0.
     281    @type priority: C{int}
     282    """
     283
     284    childParsers = {(None, 'show'): '_childParser_show',
     285                     (None, 'status'): '_childParser_status',
     286                     (None, 'priority'): '_childParser_priority'}
     287
     288    def __init__(self, recipient=None, sender=None, available=True,
     289                       show=None, status=None, statuses=None, priority=0):
     290        BasePresence.__init__(self, recipient=recipient, sender=sender)
     291        self.available = available
     292        self.show = show
     293        self.statuses = statuses or {}
     294        if status:
     295            self.statuses[None] = status
     296        self.priority = priority
     297
     298
     299    def _childParser_show(self, element):
     300        show = unicode(element)
     301        if show in ('chat', 'away', 'xa', 'dnd'):
     302            self.show = show
     303
     304
     305    def _childParser_status(self, element):
     306        lang = element.getAttribute((NS_XML, 'lang'), None)
     307        text = unicode(element)
     308        self.statuses[lang] = text
     309
     310
     311    def _childParser_priority(self, element):
     312        try:
     313            self.priority = int(unicode(element))
     314        except ValueError:
     315            pass
     316
     317
     318    def parseElement(self, element):
     319        BasePresence.parseElement(self, element)
     320
     321        if self.stanzaType == 'unavailable':
     322            self.available = False
     323
     324
     325    def toElement(self):
     326        if not self.available:
     327            self.stanzaType = 'unavailable'
     328
     329        presence = BasePresence.toElement(self)
     330
     331        if self.available:
     332            if self.show in ('chat', 'away', 'xa', 'dnd'):
     333                presence.addElement('show', content=self.show)
     334            if self.priority != 0:
     335                presence.addElement('priority', content=unicode(self.priority))
     336
     337        for lang, text in self.statuses.iteritems():
     338            status = presence.addElement('status', content=text)
     339            if lang:
     340                status[(NS_XML, 'lang')] = lang
     341
     342        return presence
     343
     344
     345
     346class SubscriptionPresence(BasePresence):
     347    """
     348    Presence subscription request or response.
     349
     350    This kind of presence is used to represent requests for presence
     351    subscription and their replies.
     352
     353    Based on L{BasePresence} and {Stanza}, it just uses the L{stanzaType}
     354    attribute to represent the type of subscription presence. This can be
     355    one of C{'subscribe'}, C{'unsubscribe'}, C{'subscribed'} and
     356    C{'unsubscribed'}.
     357    """
     358
     359
     360
     361class ProbePresence(BasePresence):
     362    """
     363    Presence probe request.
     364    """
     365
     366    stanzaType = 'probe'
     367
     368
     369
     370class PresenceProtocol(XMPPHandler):
     371    """
     372    XMPP Presence protocol.
     373
     374    @cvar presenceTypeParserMap: Maps presence stanza types to their respective
     375        stanza parser classes (derived from L{Stanza}).
     376    @type presenceTypeParserMap: C{dict}
     377    """
     378
     379    presenceTypeParserMap = {
     380                'error': ErrorStanza,
     381                'available': AvailabilityPresence,
     382                'unavailable': AvailabilityPresence,
     383                'subscribe': SubscriptionPresence,
     384                'unsubscribe': SubscriptionPresence,
     385                'subscribed': SubscriptionPresence,
     386                'unsubscribed': SubscriptionPresence,
     387                'probe': ProbePresence,
     388        }
     389
     390    def connectionInitialized(self):
     391        self.xmlstream.addObserver("/presence", self._onPresence)
     392
     393
     394    def _onPresence(self, element):
     395        stanza = Stanza.fromElement(element)
     396
     397        presenceType = stanza.stanzaType or 'available'
     398
     399        try:
     400            parser = self.presenceTypeParserMap[presenceType]
     401        except KeyError:
     402            return
     403
     404        presence = parser.fromElement(element)
     405
     406        try:
     407            handler = getattr(self, '%sReceived' % presenceType)
     408        except AttributeError:
     409            return
     410        else:
     411            handler(presence)
     412
     413
     414    def errorReceived(self, presence):
     415        """
     416        Error presence was received.
     417        """
     418        pass
     419
     420
     421    def availableReceived(self, presence):
     422        """
     423        Available presence was received.
     424        """
     425        pass
     426
     427
     428    def unavailableReceived(self, presence):
     429        """
     430        Unavailable presence was received.
     431        """
     432        pass
     433
     434
     435    def subscribedReceived(self, presence):
     436        """
     437        Subscription approval confirmation was received.
     438        """
     439        pass
     440
     441
     442    def unsubscribedReceived(self, presence):
     443        """
     444        Unsubscription confirmation was received.
     445        """
     446        pass
     447
     448
     449    def subscribeReceived(self, presence):
     450        """
     451        Subscription request was received.
     452        """
     453        pass
     454
     455
     456    def unsubscribeReceived(self, presence):
     457        """
     458        Unsubscription request was received.
     459        """
     460        pass
     461
     462
     463    def probeReceived(self, presence):
     464        """
     465        Probe presence was received.
     466        """
     467        pass
     468
     469
     470    def available(self, recipient=None, show=None, statuses=None, priority=0,
     471                        status=None, sender=None):
     472        """
     473        Send available presence.
     474
     475        @param recipient: Optional Recipient to which the presence should be
     476            sent.
     477        @type recipient: {JID}
     478
     479        @param show: Optional detailed presence information. One of C{'away'},
     480            C{'xa'}, C{'chat'}, C{'dnd'}.
     481        @type show: C{str}
     482
     483        @param statuses: Mapping of natural language descriptions of the
     484           availability status, keyed by the language descriptor. A status
     485           without a language specified, is keyed with C{None}.
     486        @type statuses: C{dict}
     487
     488        @param priority: priority level of the resource.
     489        @type priority: C{int}
     490        """
     491        presence = AvailabilityPresence(recipient=recipient, sender=sender,
     492                                        show=show, statuses=statuses,
     493                                        status=status, priority=priority)
     494        self.send(presence.toElement())
     495
     496
     497    def unavailable(self, recipient=None, statuses=None, sender=None):
     498        """
     499        Send unavailable presence.
     500
     501        @param recipient: Optional entity to which the presence should be sent.
     502        @type recipient: {JID}
     503
     504        @param statuses: dictionary of natural language descriptions of the
     505            availability status, keyed by the language descriptor. A status
     506            without a language specified, is keyed with C{None}.
     507        @type statuses: C{dict}
     508        """
     509        presence = AvailabilityPresence(recipient=recipient, sender=sender,
     510                                        available=False, statuses=statuses)
     511        self.send(presence.toElement())
     512
     513
     514    def subscribe(self, recipient, sender=None):
     515        """
     516        Send subscription request
     517
     518        @param recipient: Entity to subscribe to.
     519        @type recipient: {JID}
     520        """
     521        presence = SubscriptionPresence(recipient=recipient, sender=sender)
     522        presence.stanzaType = 'subscribe'
     523        self.send(presence.toElement())
     524
     525
     526    def unsubscribe(self, recipient, sender=None):
     527        """
     528        Send unsubscription request
     529
     530        @param recipient: Entity to unsubscribe from.
     531        @type recipient: {JID}
     532        """
     533        presence = SubscriptionPresence(recipient=recipient, sender=sender)
     534        presence.stanzaType = 'unsubscribe'
     535        self.send(presence.toElement())
     536
     537
     538    def subscribed(self, recipient, sender=None):
     539        """
     540        Send subscription confirmation.
     541
     542        @param recipient: Entity that subscribed.
     543        @type recipient: {JID}
     544        """
     545        presence = SubscriptionPresence(recipient=recipient, sender=sender)
     546        presence.stanzaType = 'subscribed'
     547        self.send(presence.toElement())
     548
     549
     550    def unsubscribed(self, recipient, sender=None):
     551        """
     552        Send unsubscription confirmation.
     553
     554        @param recipient: Entity that unsubscribed.
     555        @type recipient: {JID}
     556        """
     557        presence = SubscriptionPresence(recipient=recipient, sender=sender)
     558        presence.stanzaType = 'unsubscribed'
     559        self.send(presence.toElement())
     560
     561
     562    def probe(self, recipient, sender=None):
     563        """
     564        Send presence probe.
     565
     566        @param recipient: Entity to be probed.
     567        @type recipient: {JID}
     568        """
     569        presence = ProbePresence(recipient=recipient, sender=sender)
     570        self.send(presence.toElement())
     571
    248572
    249573
Note: See TracChangeset for help on using the changeset viewer.