Changeset 141:3a9bff4e7807


Ignore:
Timestamp:
Aug 4, 2011, 9:38:24 PM (9 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
wokkel-muc-client-support-24
Message:

Use descendants from wokkel.generic.Stanza in messages and presence.

  • Use wokkel.xmppim.AvailabilityPresence as a basis for presence stanzas.
  • Split wokkel.xmppim.PresenceProtocol and Base MUCClient on BasePresenceProtocol
  • Add new wokkel.xmppim.Message as the basis for message stanzas.
  • `receivedGroupChat
  • Change order of parameters for invite, as reason is optional.
  • Use datetimes everywhere where timestamps are needed.
Location:
wokkel
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • wokkel/iwokkel.py

    r134 r141  
    792792
    793793
    794     def receivedHistory(room, user, message, history, frm=None):
     794    def receivedHistory(room, user, message):
    795795        """
    796796        Past messages from a chat room has been received. This occurs when you join a room.
    797 
    798 
    799797        """
    800798
  • wokkel/muc.py

    r140 r141  
    1111"""
    1212import datetime
     13from dateutil.tz import tzutc
    1314
    1415from zope.interface import implements
     
    1819from twisted.words.xish import domish
    1920
    20 from wokkel import data_form, xmppim
     21from wokkel import data_form, generic, xmppim
     22from wokkel.delay import Delay, DelayMixin
    2123from wokkel.subprotocols import XMPPHandler
    2224from wokkel.iwokkel import IMUCClient
     
    3234NS_MUC_REGISTER = NS_MUC + '#register'
    3335
    34 NS_DELAY = 'urn:xmpp:delay'
    35 NS_JABBER_DELAY = 'jabber:x:delay'
    36 
    3736NS_REQUEST = 'jabber:iq:register'
    3837
     
    179178
    180179
    181 class GroupChat(domish.Element):
    182     """
    183         A message stanza of type groupchat. Used to send a message to a MUC room that will be
    184     broadcasted to people in the room.
    185     """
    186     def __init__(self, to, body=None, subject=None, frm=None):
    187         """
    188         """
    189         domish.Element.__init__(self, (None, 'message'))
    190         self['type'] = 'groupchat'
    191         if isinstance(to, jid.JID):
    192             self['to'] = to.userhost()
    193         else:
    194             self['to'] = to
    195         if frm:
    196             self['from'] = frm
    197         if body:
    198             self.addElement('body',None, body)
    199         if subject:
    200             self.addElement('subject',None, subject)
    201 
    202 
    203 
    204 class PrivateChat(domish.Element):
    205     """
    206         A chat message.
    207     """
    208     def __init__(self, to, body=None, frm=None):
    209         """
    210         Initialize the L{PrivateChat}
    211 
    212         @param to: The xmpp entity you are sending this chat to.
    213         @type to: C{unicode} or L{jid.JID}
    214 
    215         @param body: The message you are sending.
    216         @type body: C{unicode}
    217 
    218         @param frm: The entity that is sending the message.
    219         @param frm: C{unicode}
    220         """
    221         domish.Element.__init__(self, (None, 'message'))
    222         self['type'] = 'chat'
    223         if isinstance(to, jid.JID):
    224             self['to'] = to.full()
    225         else:
    226             self['to'] = to
    227         if frm:
    228             self['from'] = frm
    229         if body:
    230             self.addElement('body',None, body)
    231 
    232 
    233 
    234 class InviteMessage(domish.Element):
    235     def __init__(self, roomJID, invitee, reason=None):
    236         domish.Element.__init__(self, (None, 'message'))
    237         self['to'] = unicode(roomJID)
    238         x = self.addElement('x', NS_MUC_USER)
    239         invite = x.addElement('invite')
    240         invite['to'] = invitee
    241         if reason:
    242             invite.addElement('reason', None, reason)
    243 
    244 
    245 
    246 class HistoryMessage(GroupChat):
    247     """
    248         A history message for MUC groupchat history.
    249     """
    250     def __init__(self, to, stamp, body=None, subject=None, frm=None, h_frm=None):
    251         GroupChat.__init__(self, to, body=body, subject=subject, frm=frm)
    252         d = self.addElement('delay', NS_DELAY)
    253         d['stamp'] = stamp
    254         if h_frm:
    255             d['from'] = h_frm
     180class GroupChat(xmppim.Message, DelayMixin):
     181    """
     182    A groupchat message.
     183    """
     184
     185    stanzaType = 'groupchat'
     186
     187    def toElement(self, legacyDelay=False):
     188        """
     189        Render into a domish Element.
     190
     191        @param legacyDelay: If C{True} send the delayed delivery information
     192        in legacy format.
     193        """
     194        element = xmppim.Message.toElement(self)
     195
     196        if self.delay:
     197            element.addChild(self.delay.toElement())
     198
     199        return element
     200
     201
     202
     203class PrivateChat(xmppim.Message):
     204    """
     205    A chat message.
     206    """
     207
     208    stanzaType = 'chat'
     209
     210
     211
     212class InviteMessage(xmppim.Message):
     213
     214    def __init__(self, recipient=None, sender=None, invitee=None, reason=None):
     215        xmppim.Message.__init__(self, recipient, sender)
     216        self.invitee = invitee
     217        self.reason = reason
     218
     219
     220    def toElement(self):
     221        element = xmppim.Message.toElement(self)
     222
     223        child = element.addElement((NS_MUC_USER, 'x'))
     224        child.addElement('invite')
     225        child.invite['to'] = self.invitee.full()
     226
     227        if self.reason:
     228            child.invite.addElement('reason', content=self.reason)
     229
     230        return element
    256231
    257232
     
    259234class HistoryOptions(object):
    260235    """
    261         A history configuration object.
     236    A history configuration object.
    262237
    263238    @ivar maxchars: Limit the total number of characters in the history to "X"
    264                     (where the character count is the characters of the complete XML stanzas, not only their XML character data).
    265     @type maxchars: L{int}
    266 
    267     @ivar  maxstanzas:  Limit the total number of messages in the history to "X".
    268     @type mazstanzas: L{int}
    269 
    270     @ivar  seconds: Send only the messages received in the last "X" seconds.
    271     @type seconds: L{int}
    272 
    273     @ivar  since: Send only the messages received since the datetime specified (which MUST conform to the DateTime profile specified in XMPP Date and Time Profiles [14]).
     239        (where the character count is the characters of the complete XML
     240        stanzas, not only their XML character data).
     241    @type maxchars: C{int}
     242
     243    @ivar maxstanzas: Limit the total number of messages in the history to "X".
     244    @type mazstanzas: C{int}
     245
     246    @ivar seconds: Send only the messages received in the last "X" seconds.
     247    @type seconds: C{int}
     248
     249    @ivar since: Send only the messages received since the datetime specified.
     250        Note that this must be an offset-aware instance.
    274251    @type since: L{datetime.datetime}
    275252    """
    276253    attributes = ['maxchars', 'maxstanzas', 'seconds', 'since']
    277254
    278     def __init__(self, maxchars=None, maxstanzas=None, seconds=None, since=None):
     255    def __init__(self, maxchars=None, maxstanzas=None, seconds=None,
     256                       since=None):
    279257        self.maxchars = maxchars
    280258        self.maxstanzas = maxstanzas
     
    287265        Returns a L{domish.Element} representing the xml for the history options.
    288266        """
    289         h = domish.Element((None, 'history'))
     267        element = domish.Element((NS_MUC, 'history'))
     268
    290269        for key in self.attributes:
    291             a = getattr(self, key, None)
    292             if a is not None:
     270            value = getattr(self, key, None)
     271            if value is not None:
    293272                if key == 'since':
    294                     h[key] = a.strftime('%Y%m%dT%H:%M:%S')
     273                    stamp = value.astimezone(tzutc())
     274                    element[key] = stamp.strftime('%Y%m%dT%H:%M:%SZ')
    295275                else:
    296                     h[key] = str(a)
    297 
    298         return h
     276                    element[key] = str(value)
     277
     278        return element
    299279
    300280
     
    318298class Room(object):
    319299    """
    320     A Multi User Chat Room. An in memory object representing a MUC room.
     300    A Multi User Chat Room.
     301
     302    An in memory object representing a MUC room from the perspective of
     303    a client.
     304
     305    @ivar roomIdentifier: The Room ID of the MUC room.
     306    @type roomIdentifier: C{unicode}
     307
     308    @ivar service: The server where the MUC room is located.
     309    @type service: C{unicode}
     310
     311    @ivar nick: The nick name for the client in this room.
     312    @type nick: C{unicode}
     313
     314    @ivar state: The status code of the room.
     315    @type state: L{int}
     316
     317    @ivar occupantJID: The JID of the occupant in the room. Generated from roomIdentifier, service, and nick.
     318    @type occupantJID: L{jid.JID}
    321319    """
    322320
     
    325323        """
    326324        Initialize the room.
    327 
    328         @ivar roomIdentifier: The Room ID of the MUC room.
    329         @type roomIdentifier: C{unicode}
    330 
    331         @ivar service: The server where the MUC room is located.
    332         @type service: C{unicode}
    333 
    334         @ivar nick: The nick name for the client in this room.
    335         @type nick: C{unicode}
    336 
    337         @ivar state: The status code of the room.
    338         @type state: L{int}
    339 
    340         @ivar occupantJID: The JID of the occupant in the room. Generated from roomIdentifier, service, and nick.
    341         @type occupantJID: L{jid.JID}
    342         """
    343         self.state = state
     325        """
    344326        self.roomIdentifier = roomIdentifier
    345327        self.service = service
     328        self.setNick(nick)
     329        self.state = state
     330
     331        self.status = 0
     332
     333        self.roster = {}
     334
     335
     336    def setNick(self, nick):
     337        self.occupantJID = jid.internJID(u"%s@%s/%s" % (self.roomIdentifier,
     338                                                        self.service,
     339                                                        nick))
    346340        self.nick = nick
    347         self.status = 0
    348 
    349         self.occupantJID = self.refreshOccupantJID()
    350 
    351         self.roster = {}
    352 
    353 
    354     def refreshOccupantJID(self):
    355         """
    356         Refreshes the Room JID.
    357 
    358         This method should be called whenever roomIdentifier, service, or nick
    359         change.
    360         """
    361         self.occupantJID = jid.internJID(u"%s@%s/%s" % (self.roomIdentifier,
    362                                                     self.service,
    363                                                     self.nick))
    364         return self.occupantJID
    365341
    366342
     
    372348        @type user: L{User}
    373349        """
    374         self.roster[user.nick.lower()] = user
     350        self.roster[user.nick] = user
    375351
    376352
     
    383359        """
    384360
    385         return self.roster.has_key(user.nick.lower())
     361        return user.nick in self.roster
    386362
    387363
     
    393369        @type nick: C{unicode}
    394370        """
    395         return self.roster.get(nick.lower())
     371        return self.roster.get(nick)
    396372
    397373
     
    404380        """
    405381        if self.inRoster(user):
    406             del self.roster[user.nick.lower()]
    407 
    408 
    409 
    410 class BasicPresence(xmppim.AvailablePresence):
    411     """
    412     This behaves like an object providing L{domish.IElement}.
    413     """
    414 
    415     def __init__(self, to=None, show=None, statuses=None):
    416         xmppim.AvailablePresence.__init__(self, to=to, show=show, statuses=statuses)
    417         # add muc elements
    418         x = self.addElement('x', NS_MUC)
    419 
    420 
    421 
    422 class UserPresence(xmppim.Presence):
    423     """
    424     This behaves like an object providing L{domish.IElement}.
    425     """
    426 
    427     def __init__(self, to=None, type=None, frm=None, affiliation=None, role=None):
    428         xmppim.Presence.__init__(self, to, type)
    429         if frm:
    430             self['from'] = frm
    431         # add muc elements
    432         x = self.addElement('x', NS_MUC_USER)
    433         if affiliation:
    434             x['affiliation'] = affiliation
    435         if role:
    436             x['role'] = role
    437 
    438 
    439 
    440 class UnavailableUserPresence(xmppim.UnavailablePresence):
    441     """
    442     This behaves like an object providing L{domish.IElement}.
    443     """
    444 
    445     def __init__(self, to=None, type=None, frm=None, affiliation=None, role=None):
    446         xmppim.UnavailablePresence.__init__(self, to, type)
    447         if frm:
    448             self['from'] = frm
    449         # add muc elements
    450         x = self.addElement('x', NS_MUC_USER)
    451         if affiliation:
    452             x['affiliation'] = affiliation
    453         if role:
    454             x['role'] = role
    455 
    456 
    457 
    458 class PasswordPresence(BasicPresence):
    459     """
    460         This behaves like an object providing L{domish.IElement}.
    461     """
    462     def __init__(self, to, password):
    463         BasicPresence.__init__(self, to)
    464 
    465         self.x.addElement('password', None, password)
    466 
    467 
    468 
    469 class MessageVoice(GroupChat):
    470     """
    471         This behaves like an object providing L{domish.IElement}.
    472     """
    473     def __init__(self, to=None, frm=None):
    474         GroupChat.__init__(self, to=to, frm=frm)
     382            del self.roster[user.nick]
     383
     384
     385
     386class BasicPresence(xmppim.AvailabilityPresence):
     387    """
     388    Availability presence sent from MUC client to service.
     389
     390    @type history: L{HistoryOptions}
     391    """
     392    history = None
     393    password = None
     394
     395    def toElement(self):
     396        element = xmppim.AvailabilityPresence.toElement(self)
     397
     398        muc = element.addElement((NS_MUC, 'x'))
     399        if self.password:
     400            muc.addElement('password', content=self.password)
     401        if self.history:
     402            muc.addChild(self.history.toElement())
     403
     404        return element
     405
     406
     407
     408class UserPresence(xmppim.AvailabilityPresence):
     409    """
     410    Availability presence sent from MUC service to client.
     411    """
     412
     413    statusCode = None
     414
     415    childParsers = {(NS_MUC_USER, 'x'): '_childParser_mucUser'}
     416
     417    def _childParser_mucUser(self, element):
     418        for child in element.elements():
     419            if child.uri != NS_MUC_USER:
     420                continue
     421            elif child.name == 'status':
     422                self.statusCode = child.getAttribute('code')
     423            # TODO: item, destroy
     424
     425
     426
     427class VoiceRequest(xmppim.Message):
     428    """
     429    Voice request message.
     430    """
     431
     432    def toElement(self):
     433        element = xmppim.Message.toElement(self)
     434
    475435        # build data form
    476436        form = data_form.Form('submit', formNamespace=NS_MUC_REQUEST)
     
    478438                                      value='participant',
    479439                                      label='Requested role'))
    480         self.addChild(form.toElement())
    481 
    482 
    483 
    484 class MUCClient(XMPPHandler):
     440        element.addChild(form.toElement())
     441
     442        return element
     443
     444
     445
     446class MUCClient(xmppim.BasePresenceProtocol):
    485447    """
    486448    Multi-User Chat client protocol.
     
    504466        self._deferreds = []
    505467
     468    presenceTypeParserMap = {
     469                'error': generic.ErrorStanza,
     470                'available': UserPresence,
     471                'unavailable': UserPresence,
     472                }
    506473
    507474    def connectionInitialized(self):
    508475        """
    509         This method is called when the client has successfully authenticated.
    510         It initializes several xpath events to handle MUC stanzas that come in.
    511         After those are initialized then the method initialized is called to signal that we have finished.
    512         """
    513         self.xmlstream.addObserver(PRESENCE+"[not(@type) or @type='available']/x", self._onXPresence)
    514         self.xmlstream.addObserver(PRESENCE+"[@type='unavailable']", self._onUnavailablePresence)
    515         self.xmlstream.addObserver(PRESENCE+"[@type='error']", self._onPresenceError)
     476        Called when the XML stream has been initialized.
     477
     478        It initializes several XPath events to handle MUC stanzas that come in.
     479        After those are initialized the method L{initialized} is called to
     480        signal that we have finished.
     481        """
     482        xmppim.BasePresenceProtocol.connectionInitialized(self)
     483
    516484        self.xmlstream.addObserver(GROUPCHAT, self._onGroupChat)
    517485        self.xmlstream.addObserver(SUBJECT, self._onSubject)
    518         # add history
     486        # TODO: add history
    519487
    520488        self.initialized()
     
    540508        This uses the Room ID and service parts of the given JID to look up
    541509        the L{Room} instance associated with it.
     510
     511        @type occupantJID: L{jid.JID}
    542512        """
    543513        roomJID = occupantJID.userhostJID()
     
    554524
    555525
    556     def _onUnavailablePresence(self, prs):
    557         """
    558         This method is called when the stanza matches the xpath observer.
    559         The client has received a presence stanza with the 'type' attribute of unavailable.
    560         It means a user has exited a MUC room.
    561         """
    562 
    563         if not prs.hasAttribute('from'):
    564             return
    565         occupantJID = jid.internJID(prs.getAttribute('from', ''))
    566         self._userLeavesRoom(occupantJID)
    567 
    568 
    569     def _onPresenceError(self, prs):
    570         """
    571         This method is called when a presence stanza with the 'type' attribute of error.
    572         There are various reasons for receiving a presence error and it means that the user has left the room.
    573         """
    574         if not prs.hasAttribute('from'):
    575             return
    576         occupantJID = jid.internJID(prs.getAttribute('from', ''))
    577         # add an error hook here?
    578         self._userLeavesRoom(occupantJID)
     526    def unavailableReceived(self, presence):
     527        """
     528        Unavailable presence was received.
     529
     530        If this was received from a MUC room occupant JID, that occupant has
     531        left the room.
     532        """
     533
     534        occupantJID = presence.sender
     535
     536        if occupantJID:
     537            self._userLeavesRoom(occupantJID)
     538
     539
     540    def errorReceived(self, presence):
     541        """
     542        Error presence was received.
     543
     544        If this was received from a MUC room occupant JID, we conclude the
     545        occupant has left the room.
     546        """
     547        occupantJID = presence.sender
     548
     549        if occupantJID:
     550            self._userLeavesRoom(occupantJID)
    579551
    580552    def _userLeavesRoom(self, occupantJID):
     
    593565
    594566
    595     def _onXPresence(self, prs):
    596         """
    597         A muc presence has been received.
    598         """
    599         if not prs.hasAttribute('from'):
     567    def availableReceived(self, presence):
     568        """
     569        Available presence was received.
     570        """
     571
     572        occupantJID = presence.sender
     573
     574        if not occupantJID:
    600575            return
    601         occupantJID = jid.internJID(prs.getAttribute('from', ''))
    602 
    603         status = getattr(prs, 'status', None)
    604         show = getattr(prs, 'show', None)
    605576
    606577        # grab room
     
    609580            # not in the room yet
    610581            return
    611         user = self._changeUserStatus(room, occupantJID, status, show)
     582
     583        user = self._changeUserStatus(room, occupantJID, presence.status,
     584                                      presence.show)
    612585
    613586        if room.inRoster(user):
    614587            # we changed status or nick
    615             muc_status = getattr(prs.x, 'status', None)
    616             if muc_status:
    617                 code = muc_status.getAttribute('code', 0)
     588            if presence.statusCode:
     589                room.status = presence.statusCode # XXX
    618590            else:
    619                 self.userUpdatedStatus(room, user, show, status)
     591                self.userUpdatedStatus(room, user, presence.show,
     592                                       presence.status)
    620593        else:
    621594            room.addUser(user)
     
    623596
    624597
    625     def _onGroupChat(self, msg):
     598    def _onGroupChat(self, element):
    626599        """
    627600        A group chat message has been received from a MUC room.
     
    629602        There are a few event methods that may get called here. receviedGroupChat and receivedHistory
    630603        """
    631         if not msg.hasAttribute('from'):
    632             # need to return an error here
     604        message = GroupChat.fromElement(element)
     605
     606        occupantJID = message.sender
     607
     608        if not occupantJID:
     609            # need to return an error here XXX
    633610            return
    634         occupantJID = jid.internJID(msg.getAttribute('from', ''))
    635611
    636612        room = self._getRoom(occupantJID)
     
    638614            # not in the room yet
    639615            return
    640         user = room.getUser(occupantJID.resource)
    641         delay = None
    642         # need to check for delay and x stanzas for delay namespace for backwards compatability
    643         for e in msg.elements():
    644             if e.uri == NS_DELAY or e.uri == NS_JABBER_DELAY:
    645                 delay = e
    646         body = unicode(msg.body)
    647         # grab room
    648         if delay is None:
    649             self.receivedGroupChat(room, user, body)
     616
     617        if occupantJID.resource:
     618            user = room.getUser(occupantJID.resource)
    650619        else:
    651             self.receivedHistory(room, user, body, delay['stamp'], frm=delay.getAttribute('from',None))
     620            # This message is from the room itself.
     621            user = None
     622
     623        if message.delay is None:
     624            self.receivedGroupChat(room, user, message)
     625        else:
     626            self.receivedHistory(room, user, message)
    652627
    653628
     
    748723        @type room: L{Room}
    749724
    750         @param user: The user that joined the MUC room.
     725        @param user: The user that left the MUC room.
    751726        @type user: L{User}
    752727        """
     
    772747
    773748
    774     def receivedHistory(self, room, user, message, history, frm=None):
    775         """
    776         This method will need to be modified inorder for clients to
    777         do something when this event occurs.
     749    def receivedGroupChat(self, room, user, message):
     750        """
     751        A groupchat message was received.
     752
     753        @param room: The room the message was received from.
     754        @type room: L{Room}
     755
     756        @param user: The user that sent the message, or C{None} if it was a
     757            message from the room itself.
     758        @type user: L{User}
     759
     760        @param message: The message.
     761        @type message: L{GroupChat}
    778762        """
    779763        pass
    780764
    781765
    782     def _cbDisco(self, iq):
    783         # grab query
    784 
    785         return getattr(iq,'query', None)
     766    def receivedHistory(self, room, user, message):
     767        """
     768        A groupchat message from the room's discussion history was received.
     769
     770        This is identical to L{receivedGroupChat}, with the delayed delivery
     771        information (timestamp and original sender) in C{message.delay}. For
     772        anonymous rooms, C{message.delay.sender} is the room's address.
     773
     774        @param room: The room the message was received from.
     775        @type room: L{Room}
     776
     777        @param user: The user that sent the message, or C{None} if it was a
     778            message from the room itself.
     779        @type user: L{User}
     780
     781        @param message: The message.
     782        @type message: L{GroupChat}
     783        """
     784        pass
    786785
    787786
     
    873872        self._addRoom(room)
    874873
    875         p = BasicPresence(to=room.occupantJID)
    876         if history is not None:
    877             p.x.addChild(history.toElement())
    878 
    879         d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
     874        presence = BasicPresence(recipient=room.occupantJID)
     875        if history:
     876            presence.history = HistoryOptions(maxstanzas=history)
     877
     878        d = self.sendDeferred(presence.toElement(), timeout=DEFER_TIMEOUT)
    880879
    881880        # add observer for joining the room
     
    919918
    920919
    921     def nick(self, roomJID, new_nick):
    922         """
    923         Change an entities nick name in a MUC room.
     920    def nick(self, roomJID, nick):
     921        """
     922        Change an entity's nick name in a MUC room.
    924923
    925924        See: http://xmpp.org/extensions/xep-0045.html#changenick
     
    928927        @type roomJID: L{jid.JID}
    929928
    930         @param new_nick: The nick name for the entitity joining the room.
    931         @type new_nick: C{unicode}
     929        @param nick: The new nick name within the room.
     930        @type nick: C{unicode}
    932931        """
    933932
     
    936935
    937936        # Change the nickname
    938         room.nick = new_nick
    939         room.refreshOccupantJID()
     937        room.setNick(nick)
    940938
    941939        # Create presence
    942         p = BasicPresence(to=room.occupantJID)
    943         d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
     940        presence = BasicPresence(recipient=room.occupantJID)
     941        d = self.sendDeferred(presence.toElement(), timeout=DEFER_TIMEOUT)
    944942
    945943        # Add observer for joining the room
     
    961959        room = self._getRoom(occupantJID)
    962960
    963         p = xmppim.UnavailablePresence(to=room.occupantJID)
    964 
    965         d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
     961        presence = xmppim.AvailabilityPresence(recipient=room.occupantJID,
     962                                               available=False)
     963
     964        d = self.sendDeferred(presence.toElement(), timeout=DEFER_TIMEOUT)
    966965        # add observer for joining the room
    967966        query = PRESENCE + u"[@from='%s' and @type='unavailable']" % (room.occupantJID)
     
    988987        room = self._getRoom(occupantJID)
    989988
    990         p = BasicPresence(to=room.occupantJID)
    991         if status is not None:
    992             p.addElement('status', None, status)
    993 
    994         if show is not None:
    995             p.addElement('show', None, show)
    996 
    997         d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
     989        presence = BasicPresence(recipient=room.occupantJID,
     990                                 show=show, status=status)
     991
     992        d = self.sendDeferred(presence.toElement(), timeout=DEFER_TIMEOUT)
    998993
    999994        # add observer for joining the room
     
    1004999
    10051000
    1006     def _sendMessage(self, msg, children=None):
     1001    def _sendMessage(self, message, children=None):
    10071002        """
    10081003        Send a message.
    10091004        """
     1005        element = message.toElement()
    10101006        if children:
    10111007            for child in children:
    1012                 msg.addChild(child)
    1013 
    1014         self.xmlstream.send(msg)
    1015 
    1016 
    1017     def groupChat(self, to, message, children=None):
     1008                element.addChild(child)
     1009
     1010        self.xmlstream.send(element)
     1011
     1012
     1013    def groupChat(self, roomJID, body, children=None):
    10181014        """
    10191015        Send a groupchat message.
    10201016        """
    1021         msg = GroupChat(to, body=message)
    1022 
    1023         self._sendMessage(msg, children=children)
    1024 
    1025 
    1026     def chat(self, occupantJID, message, children=None):
     1017        message = GroupChat(recipient=roomJID, body=body)
     1018        self._sendMessage(message, children=children)
     1019
     1020
     1021    def chat(self, occupantJID, body, children=None):
    10271022        """
    10281023        Send a private chat message to a user in a MUC room.
     
    10331028        @type occupantJID: L{jid.JID}
    10341029        """
    1035 
    1036         msg = PrivateChat(occupantJID, body=message)
    1037 
    1038         self._sendMessage(msg, children=children)
    1039 
    1040 
    1041     def invite(self, roomJID, reason=None, invitee=None):
     1030        message = PrivateChat(recipient=occupantJID, body=body)
     1031        self._sendMessage(message, children=children)
     1032
     1033
     1034    def invite(self, roomJID, invitee, reason=None):
    10421035        """
    10431036        Invite a xmpp entity to a MUC room.
     
    10471040        @param roomJID: The bare JID of the room.
    10481041        @type roomJID: L{jid.JID}
     1042
     1043        @param invitee: The entity that is being invited.
     1044        @type invitee: L{jid.JID}
    10491045
    10501046        @param reason: The reason for the invite.
    10511047        @type reason: C{unicode}
    1052 
    1053         @param invitee: The entity that is being invited.
    1054         @type invitee: L{jid.JID}
    1055         """
    1056         msg = InviteMessage(roomJID, reason=reason, invitee=invitee)
    1057         self._sendMessage(msg)
     1048        """
     1049        message = InviteMessage(recipient=roomJID, invitee=invitee,
     1050                                reason=reason)
     1051        self._sendMessage(message)
    10581052
    10591053
     
    10701064        @type password: C{unicode}
    10711065        """
    1072         p = PasswordPresence(roomJID, password)
    1073 
    1074         self.xmlstream.send(p)
     1066        presence = BasicPresence(roomJID)
     1067        presence.password = password
     1068
     1069        self.xmlstream.send(presence.toElement())
    10751070
    10761071
     
    11901185
    11911186
    1192     def getRegisterForm(self, room):
     1187    def getRegisterForm(self, roomJID):
    11931188        """
    11941189        Grab the registration form for a MUC room.
     
    11981193        """
    11991194        iq = RegisterRequest(self.xmlstream)
    1200         iq['to'] = room.userhost()
     1195        iq['to'] = roomJID.userhost()
    12011196        return iq.send()
    12021197
     
    12561251        @type roomJID: L{jid.JID}
    12571252        """
    1258         msg = MessageVoice(to=roomJID.userhostJID())
    1259         self.xmlstream.send(msg)
     1253        message = VoiceRequest(recipient=roomJID)
     1254        self.xmlstream.send(message.toElement())
    12601255
    12611256
     
    12811276            stanza['type'] = 'groupchat'
    12821277
    1283             delay = stanza.addElement('delay', NS_DELAY)
    1284             delay['stamp'] = message['timestamp']
    1285 
    1286             sender = stanza.getAttribute('from', None)
     1278            delay = Delay(stamp=message['timestamp'])
     1279
     1280            sender = stanza.getAttribute('from')
    12871281            if sender is not None:
    1288                 delay['from'] = sender
     1282                delay.sender = jid.JID(sender)
     1283
     1284            stanza.addChild(delay.toElement())
    12891285
    12901286            stanza['to'] = roomJID.userhost()
  • wokkel/test/test_muc.py

    r139 r141  
    55Tests for L{wokkel.muc}
    66"""
     7
     8from datetime import datetime
     9from dateutil.tz import tzutc
    710
    811from zope.interface import verify
     
    116119
    117120        def groupChat(room, user, message):
    118             self.assertEquals('test', message, "Wrong group chat message")
     121            self.assertEquals('test', message.body, "Wrong group chat message")
    119122            self.assertEquals(self.test_room, room.roomIdentifier,
    120123                              'Wrong room name')
     
    343346
    344347
    345         def historyReceived(room, user, body, stamp, frm=None):
    346             self.assertEquals('test', body, "wrong message body")
    347             self.assertTrue(stamp, 'Does not have a history stamp')
     348        def historyReceived(room, user, message):
     349            self.assertEquals('test', message.body, "wrong message body")
     350            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
     351            self.assertEquals(stamp, message.delay.stamp,
     352                             'Does not have a history stamp')
    348353
    349354        d, self.protocol.receivedHistory = calledAsync(historyReceived)
     
    365370        msg.addElement('thread', None, thread)
    366371
    367         archive.append({'stanza': msg, 'timestamp': '2002-10-13T23:58:37Z'})
     372        archive.append({'stanza': msg,
     373                        'timestamp': datetime(2002, 10, 13, 23, 58, 37,
     374                                              tzinfo=tzutc())})
    368375
    369376        msg = domish.Element((None, 'message'))
     
    373380        msg.addElement('thread', None, thread)
    374381
    375         archive.append({'stanza': msg, 'timestamp': '2002-10-13T23:58:43Z'})
     382        archive.append({'stanza': msg,
     383                        'timestamp': datetime(2002, 10, 13, 23, 58, 43,
     384                                              tzinfo=tzutc())})
    376385
    377386        self.protocol.history(self.room_jid, archive)
     
    392401        invitee = JID('test@jabber.org')
    393402
    394         self.protocol.invite(bareRoomJID, 'This is a test', invitee)
     403        self.protocol.invite(bareRoomJID, invitee, 'This is a test')
    395404
    396405        msg = self.stub.output[-1]
  • wokkel/xmppim.py

    r96 r141  
    297297
    298298
     299    def __get_status(self):
     300        if None in self.statuses:
     301            return self.statuses[None]
     302        elif self.statuses:
     303            for status in self.status.itervalues():
     304                return status
     305        else:
     306            return None
     307
     308    status = property(__get_status)
     309
     310
    299311    def _childParser_show(self, element):
    300312        show = unicode(element)
     
    368380
    369381
    370 class PresenceProtocol(XMPPHandler):
    371     """
    372     XMPP Presence protocol.
     382class BasePresenceProtocol(XMPPHandler):
     383    """
     384    XMPP Presence base protocol handler.
     385
     386    This class is the base for protocol handlers that receive presence
     387    stanzas. Listening to all incoming presence stanzas, it extracts the
     388    stanza's type and looks up a matching stanza parser and calls the
     389    associated method. The method's name is the type + C{Received}. E.g.
     390    C{availableReceived}. See L{PresenceProtocol} for a complete example.
    373391
    374392    @cvar presenceTypeParserMap: Maps presence stanza types to their respective
     
    376394    @type presenceTypeParserMap: C{dict}
    377395    """
     396
     397    presenceTypeParserMap = {}
     398
     399    def connectionInitialized(self):
     400        self.xmlstream.addObserver("/presence", self._onPresence)
     401
     402
     403
     404    def _onPresence(self, element):
     405        """
     406        Called when a presence stanza has been received.
     407        """
     408        stanza = Stanza.fromElement(element)
     409
     410        presenceType = stanza.stanzaType or 'available'
     411
     412        try:
     413            parser = self.presenceTypeParserMap[presenceType]
     414        except KeyError:
     415            return
     416
     417        presence = parser.fromElement(element)
     418
     419        try:
     420            handler = getattr(self, '%sReceived' % presenceType)
     421        except AttributeError:
     422            return
     423        else:
     424            handler(presence)
     425
     426
     427
     428class PresenceProtocol(BasePresenceProtocol):
    378429
    379430    presenceTypeParserMap = {
     
    386437                'unsubscribed': SubscriptionPresence,
    387438                '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)
     439                }
    412440
    413441
     
    700728        """
    701729
     730
     731
     732class Message(Stanza):
     733    """
     734    A message stanza.
     735    """
     736
     737    stanzaKind = 'message'
     738
     739    childParsers = {
     740            (None, 'body'): '_childParser_body',
     741            (None, 'subject'): '_childParser_subject',
     742            }
     743
     744    def __init__(self, recipient=None, sender=None, body=None, subject=None):
     745        Stanza.__init__(self, recipient, sender)
     746        self.body = body
     747        self.subject = subject
     748
     749
     750    def _childParser_body(self, element):
     751        self.body = unicode(element)
     752
     753
     754    def _childParser_subject(self, element):
     755        self.subject = unicode(element)
     756
     757
     758    def toElement(self):
     759        element = Stanza.toElement(self)
     760
     761        if self.body:
     762            element.addElement('body', content=self.body)
     763        if self.subject:
     764            element.addElement('subject', content=self.subject)
     765
     766        return element
     767
     768
     769
    702770class MessageProtocol(XMPPHandler):
    703771    """
Note: See TracChangeset for help on using the changeset viewer.