source: wokkel/muc.py @ 121:e74ad91e5c6f

wokkel-muc-client-support-24
Last change on this file since 121:e74ad91e5c6f was 121:e74ad91e5c6f, checked in by Christopher Zorn <tofu@…>, 13 years ago

add some more exeptions and implement history options

File size: 30.1 KB
Line 
1# -*- test-case-name: wokkel.test.test_muc -*-
2#
3# Copyright (c) 2003-2008 Ralph Meijer
4# See LICENSE for details.
5
6"""
7XMPP Multi-User Chat protocol.
8
9This protocol is specified in
10U{XEP-0045<http://www.xmpp.org/extensions/xep-0045.html>}.
11"""
12import datetime
13
14from zope.interface import implements
15
16from twisted.internet import defer, reactor
17from twisted.words.protocols.jabber import jid, error, xmlstream
18from twisted.words.xish import domish
19
20from wokkel import disco, data_form, shim, xmppim
21from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
22from wokkel.iwokkel import IMUCClient
23
24# Multi User Chat namespaces
25NS_MUC          = 'http://jabber.org/protocol/muc'
26NS_MUC_USER     = NS_MUC + '#user'
27NS_MUC_ADMIN    = NS_MUC + '#admin'
28NS_MUC_OWNER    = NS_MUC + '#owner'
29NS_MUC_ROOMINFO = NS_MUC + '#roominfo'
30NS_MUC_CONFIG   = NS_MUC + '#roomconfig'
31NS_MUC_REQUEST  = NS_MUC + '#request'
32NS_MUC_REGISTER = NS_MUC + '#register'
33
34NS_DELAY        = 'urn:xmpp:delay'
35NS_JABBER_DELAY = 'jabber:x:delay'
36
37NS_REQUEST  = 'jabber:iq:register'
38
39# ad hoc commands
40NS_AD_HOC       = "http://jabber.org/protocol/commands"
41
42
43# Iq get and set XPath queries
44IQ     = '/iq'
45IQ_GET = IQ+'[@type="get"]'
46IQ_SET = IQ+'[@type="set"]'
47
48IQ_RESULT = IQ+'[@type="result"]'
49IQ_ERROR  = IQ+'[@type="error"]'
50
51IQ_QUERY     = IQ+'/query'
52IQ_GET_QUERY = IQ_GET + '/query'
53IQ_SET_QUERY = IQ_SET + '/query'
54
55IQ_COMMAND   = IQ+'/command'
56
57MUC_ADMIN = IQ_QUERY+'[@xmlns="' + NS_MUC_ADMIN + '"]'
58MUC_OWNER = IQ_QUERY+'[@xmlns="' + NS_MUC_OWNER + '"]'
59
60MUC_AO = MUC_ADMIN + '|' + MUC_OWNER
61
62
63MESSAGE  = '/message'
64PRESENCE = '/presence'
65
66CHAT_BODY = MESSAGE +'[@type="chat"]/body'
67CHAT      = MESSAGE +'[@type="chat"]'
68
69GROUPCHAT     = MESSAGE +'[@type="groupchat"]/body'
70SUBJECT       = MESSAGE +'[@type="groupchat"]/subject'
71MESSAGE_ERROR = MESSAGE +'[@type="error"]'
72
73STATUS_CODES = { # see http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes
74    100:
75        {'name':'fulljid',
76         'stanza':'presence',
77         
78         },
79    201: 
80        {'name':'created', 
81         'stanza': 'presence',
82         'context':'Entering a room',
83         'purpose':'Inform user that a new room has been created'
84         },   
85}
86
87STATUS_CODE_CREATED = 201
88
89DEFER_TIMEOUT = 30 # basic timeout is 30 seconds
90
91class JidMalformed(Exception):
92    """
93    A jid malformed error from the server.
94
95   
96    """
97    condition    = 'modify'
98    mucCondition = 'jid-malformed'
99   
100class NotAuthorized(Exception):
101    """
102    """
103    condition    = 'auth'
104    mucCondition = 'not-authorized'
105
106class RegistrationRequired(Exception):
107    """
108    """
109    condition    = 'auth'
110    mucCondition = 'registration-required'
111
112
113class Forbidden(Exception):
114    """
115    """
116    condition    = 'auth'
117    mucCondition = 'forbidden'
118
119class Conflict(Exception):
120    """
121    """
122    condition    = 'cancel'
123    mucCondition = 'conflict'
124
125class NotFound(Exception):
126    """
127    """
128    condition    = 'cancel'
129    mucCondition = 'not-found'
130
131class ServiceUnavailable(Exception):
132    """
133    """
134    condition    = 'wait'
135    mucCondition = 'service-unavailable'
136
137
138
139MUC_EXCEPTIONS = {
140    'jid-malformed': JidMalformed,
141    'forbidden': Forbidden,
142    'not-authorized': NotAuthorized,
143    'exception': Exception,
144    'conflict': Conflict,
145    'service-unavailable': ServiceUnavailable,
146    'not-found': NotFound,
147    }
148
149class MUCError(error.StanzaError):
150    """
151    Exception with muc specific condition.
152    """
153    def __init__(self, condition, mucCondition, type='error', feature=None, text=None):
154        appCondition = domish.Element((NS_MUC, mucCondition))
155        if feature:
156            appCondition['feature'] = feature
157        error.StanzaError.__init__(self, condition,
158                                         type=type,
159                                         text=text,
160                                         appCondition=appCondition)
161
162
163class BadRequest(MUCError):
164    """
165    Bad request stanza error.
166    """
167    def __init__(self, mucCondition=None, text=None):
168        MUCError.__init__(self, 'bad-request', mucCondition, text)
169
170
171
172class Unsupported(MUCError):
173    def __init__(self, feature, text=None):
174        MUCError.__init__(self, 'feature-not-implemented',
175                          'unsupported',
176                          feature,
177                          text)
178
179
180
181class ConfigureRequest(xmlstream.IQ):
182    """
183    Configure MUC room request.
184
185    http://xmpp.org/extensions/xep-0045.html#roomconfig
186
187    @ivar method: Type attribute of the IQ request. Either C{'set'} or C{'get'}
188    @type method: C{str}
189    """
190
191    def __init__(self, xs, method='get', fields=[]):
192        xmlstream.IQ.__init__(self, xs, method)
193        q = self.addElement((NS_MUC_OWNER, 'query'))
194        if method == 'set':
195            # build data form
196            form = data_form.Form('submit', formNamespace=NS_MUC_CONFIG)
197            q.addChild(form.toElement())
198           
199            for f in fields:
200                # create a field
201                form.addField(f)
202
203
204class RegisterRequest(xmlstream.IQ):
205    """
206    Register room request.
207
208    @ivar method: Type attribute of the IQ request. Either C{'set'} or C{'get'}
209    @type method: C{str}
210
211    """
212
213    def __init__(self, xs, method='get', fields=[]):
214        xmlstream.IQ.__init__(self, xs, method)
215        q = self.addElement((NS_REQUEST, 'query'))
216        if method == 'set':
217            # build data form
218            form_type = 'submit'       
219            form = data_form.Form(form_type, formNamespace=NS_MUC_REGISTER)
220            q.addChild(form.toElement())       
221           
222            for f in fields:
223                # create a field
224                form.addField(f)
225
226
227class AdminRequest(xmlstream.IQ):
228    """
229    A basic admin iq request
230
231    @ivar method: Type attribute of the IQ request. Either C{'set'} or C{'get'}
232    @type method: C{str}
233
234    """
235
236    def __init__(self, xs, method='get'):
237        xmlstream.IQ.__init__(self, xs, method)
238        q = self.addElement((NS_MUC_ADMIN, 'query'))
239
240
241class OwnerRequest(xmlstream.IQ):
242    """
243    A basic owner iq request
244
245    @ivar method: Type attribute of the IQ request. Either C{'set'} or C{'get'}
246    @type method: C{str}
247
248    """
249
250    def __init__(self, xs, method='get'):
251        xmlstream.IQ.__init__(self, xs, method)
252        q = self.addElement((NS_MUC_OWNER, 'query'))
253
254   
255
256class AffiliationRequest(AdminRequest):
257    """
258    Register room request.
259
260    @ivar method: Type attribute of the IQ request. Either C{'set'} or C{'get'}
261    @type method: C{str}
262
263    @ivar affiliation: The affiliation type to send to room.
264    @type affiliation: C{str}
265
266    """
267
268    def __init__(self, xs, method='get', affiliation='none', a_jid=None, reason=None):
269        AdminRequest.__init__(self, xs, method)
270
271        i = self.query.addElement('item')
272
273        i['affiliation'] = affiliation
274        if a_jid:
275            i['jid'] = a_jid.full()
276           
277        if reason:
278            i.addElement('reason', None, reason)
279
280           
281       
282
283class GroupChat(domish.Element):
284    """
285    """
286    def __init__(self, to, body=None, subject=None, frm=None):
287        """To needs to be a string
288        """
289        domish.Element.__init__(self, (None, 'message'))
290        self['type'] = 'groupchat'
291        if isinstance(to, jid.JID):
292            self['to'] = to.userhost()
293        else:
294            self['to'] = to
295        if frm:
296            self['from'] = frm
297        if body:
298            self.addElement('body',None, body)
299        if subject:
300            self.addElement('subject',None, subject)
301
302
303class PrivateChat(domish.Element):
304    """
305    """
306    def __init__(self, to, body=None, frm=None):
307        """To needs to be a string
308        """
309        domish.Element.__init__(self, (None, 'message'))
310        self['type'] = 'chat'
311        self['to']   = to
312        if frm:
313            self['from'] = frm
314        if body:
315            self.addElement('body',None, body)
316           
317class InviteMessage(PrivateChat):
318    def __init__(self, to, reason=None, full_jid=None, body=None, frm=None, password=None):
319        PrivateChat.__init__(self, to, body=body, frm=frm)
320        del self['type'] # remove type
321        x = self.addElement('x', NS_MUC_USER)
322        invite = x.addElement('invite')
323        if full_jid:
324            invite['to'] = full_jid
325        if reason:
326            invite.addElement('reason', None, reason)
327        if password:
328            invite.addElement('password', None, password)
329
330class HistoryMessage(GroupChat):
331    """
332    """
333    def __init__(self, to, stamp, body=None, subject=None, frm=None, h_frm=None):
334        GroupChat.__init__(self, to, body=body, subject=subject, frm=frm)
335        d = self.addElement('delay', NS_DELAY)
336        d['stamp'] = stamp
337        if h_frm:
338            d['from'] = h_frm
339
340class HistoryOptions(object):
341    """A history configuration object.
342
343    @ivar maxchars: Limit the total number of characters in the history to "X"
344                    (where the character count is the characters of the complete XML stanzas, not only their XML character data).
345    @itype maxchars: L{int}
346               
347    @ivar  maxstanzas:  Limit the total number of messages in the history to "X".
348    @itype mazstanzas: L{int}
349
350    @ivar  seconds: Send only the messages received in the last "X" seconds.
351    @itype seconds: L{int}
352
353    @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]).
354    @itype since: L{datetime.datetime}
355
356    """
357    attributes = ['maxchars', 'maxstanzas', 'seconds', 'since']
358
359    def __init__(self, maxchars=None, maxstanzas=None, seconds=None, since=None):
360        self.maxchars   = maxchars
361        self.maxstanzas = maxstanzas
362        self.seconds    = seconds
363        self.since      = since
364
365    def toElement(self):
366        h = domish.Element((None, 'history'))
367        for key in self.attributes:
368            a = getattr(self, key, a)
369            if a is not None:
370                h[key] = str(a)
371       
372        return h
373
374class User(object):
375    """
376    A user/entity in a multi-user chat room.
377    """
378   
379    def __init__(self, nick, user_jid=None):
380        self.nick = nick
381        self.user_jid = user_jid
382        self.affiliation = 'none'
383        self.role = 'none'
384       
385        self.status = None
386        self.show   = None
387
388
389class Room(object):
390    """
391    A Multi User Chat Room
392    """
393
394   
395    def __init__(self, name, server, nick, state=None):
396        """
397        """
398        self.state  = state
399        self.name   = name
400        self.server = server
401        self.nick   = nick
402        self.status = 0
403
404        self.entity_id = self.entityId()
405               
406        self.roster = {}
407
408    def entityId(self):
409        """
410        """
411        self.entity_id = jid.internJID(self.name+'@'+self.server+'/'+self.nick)
412
413        return self.entity_id
414
415    def addUser(self, user):
416        """
417        """
418        self.roster[user.nick.lower()] = user
419
420    def inRoster(self, user):
421        """
422        """
423
424        return self.roster.has_key(user.nick.lower())
425
426    def getUser(self, nick):
427        """
428        """
429        return self.roster.get(nick.lower())
430
431    def removeUser(self, user):
432        if self.inRoster(user):
433            del self.roster[user.nick.lower()]
434       
435
436class BasicPresence(xmppim.AvailablePresence):
437    """
438    This behaves like an object providing L{domish.IElement}.
439
440    """
441
442    def __init__(self, to=None, show=None, statuses=None):
443        xmppim.AvailablePresence.__init__(self, to=to, show=show, statuses=statuses)
444        # add muc elements
445        x = self.addElement('x', NS_MUC)
446
447
448class UserPresence(xmppim.Presence):
449    """
450    This behaves like an object providing L{domish.IElement}.
451
452    """
453
454    def __init__(self, to=None, type=None, frm=None, affiliation=None, role=None):
455        xmppim.Presence.__init__(self, to, type)
456        if frm:
457            self['from'] = frm
458        # add muc elements
459        x = self.addElement('x', NS_MUC_USER)
460        if affiliation:
461            x['affiliation'] = affiliation
462        if role:
463            x['role'] = role
464
465class UnavailableUserPresence(xmppim.UnavailablePresence):
466    """
467    This behaves like an object providing L{domish.IElement}.
468
469    """
470
471    def __init__(self, to=None, type=None, frm=None, affiliation=None, role=None):
472        xmppim.UnavailablePresence.__init__(self, to, type)
473        if frm:
474            self['from'] = frm
475        # add muc elements
476        x = self.addElement('x', NS_MUC_USER)
477        if affiliation:
478            x['affiliation'] = affiliation
479        if role:
480            x['role'] = role
481
482
483class PasswordPresence(BasicPresence):
484    """
485    """
486    def __init__(self, to, password):
487        BasicPresence.__init__(self, to)
488       
489        self.x.addElement('password', None, password)
490
491
492class MessageVoice(GroupChat):
493    """
494    """
495    def __init__(self, to=None, frm=None):
496        GroupChat.__init__(self, to=to, frm=frm)
497        # build data form
498        form = data_form.Form('submit', formNamespace=NS_MUC_REQUEST)
499        form.addField(data_form.Field(var='muc#role',
500                                      value='participant', 
501                                      label='Requested role'))
502        self.addChild(form.toElement())           
503
504class PresenceError(xmppim.Presence):
505    """
506    This behaves like an object providing L{domish.IElement}.
507
508    """
509
510    def __init__(self, error, to=None, frm=None):
511        xmppim.Presence.__init__(self, to, type='error')
512        if frm:
513            self['from'] = frm
514        # add muc elements
515        x = self.addElement('x', NS_MUC)
516       
517        # add error
518        e = error.getElement()
519        self.addChild(e)
520       
521
522class MUCClient(XMPPHandler):
523    """
524    Multi-User chat client protocol.
525    """
526
527    implements(IMUCClient)
528
529    rooms = {}
530
531    timeout = None
532
533    _deferreds = []
534
535    def connectionInitialized(self):
536        self.xmlstream.addObserver(PRESENCE+"[not(@type) or @type='available']/x", self._onXPresence)
537        self.xmlstream.addObserver(PRESENCE+"[@type='unavailable']", self._onUnavailablePresence)
538        self.xmlstream.addObserver(PRESENCE+"[@type='error']", self._onPresenceError)
539        self.xmlstream.addObserver(GROUPCHAT, self._onGroupChat)
540        self.xmlstream.addObserver(SUBJECT, self._onSubject)
541        # add history
542
543        self.initialized()
544
545    def _setRoom(self, room):
546        self.rooms[room.entity_id.userhost().lower()] = room
547
548    def _getRoom(self, room_jid):
549        return self.rooms.get(room_jid.userhost().lower())
550
551    def _removeRoom(self, room_jid):
552        if self.rooms.has_key(room_jid.userhost().lower()):
553            del self.rooms[room_jid.userhost().lower()]
554
555
556    def _onUnavailablePresence(self, prs):
557        """
558        """
559
560        if not prs.hasAttribute('from'):
561            return
562        room_jid = jid.internJID(prs.getAttribute('from', ''))
563        self._userLeavesRoom(room_jid)
564
565    def _onPresenceError(self, prs):
566        """
567        """
568        if not prs.hasAttribute('from'):
569            return
570        room_jid = jid.internJID(prs.getAttribute('from', ''))
571        # add an error hook here?
572        self._userLeavesRoom(room_jid)
573       
574    def _getExceptionFromElement(self, stanza):
575        muc_condition = 'exception'
576
577        error = getattr(stanza, 'error', None)
578        if error is not None:
579            for e in error.elements():
580                muc_condition = e.name
581
582        return MUC_EXCEPTIONS[muc_condition]
583
584    def _userLeavesRoom(self, room_jid):
585        room = self._getRoom(room_jid)
586        if room is None:
587            # not in the room yet
588            return
589        # check if user is in roster
590        user = room.getUser(room_jid.resource)
591        if user is None:
592            return
593        if room.inRoster(user):
594            room.removeUser(user)
595            self.userLeftRoom(room, user)
596       
597    def _onXPresence(self, prs):
598        """
599        """
600        if not prs.hasAttribute('from'):
601            return
602        room_jid = jid.internJID(prs.getAttribute('from', ''))
603           
604        status = getattr(prs, 'status', None)
605        show   = getattr(prs, 'show', None)
606       
607        # grab room
608        room = self._getRoom(room_jid)
609        if room is None:
610            # not in the room yet
611            return
612
613        # check if user is in roster
614        user = room.getUser(room_jid.resource)
615        if user is None: # create a user that does not exist
616            user = User(room_jid.resource)
617           
618       
619        if room.inRoster(user):
620            # we changed status or nick
621            muc_status = getattr(prs.x, 'status', None)
622            if muc_status:
623                code = muc_status.getAttribute('code', 0)
624            else:
625                self.userUpdatedStatus(room, user, show, status)
626        else:           
627            room.addUser(user)
628            self.userJoinedRoom(room, user)
629           
630
631    def _onGroupChat(self, msg):
632        """
633        """
634        if not msg.hasAttribute('from'):
635            # need to return an error here
636            return
637        room_jid = jid.internJID(msg.getAttribute('from', ''))
638
639        room = self._getRoom(room_jid)
640        if room is None:
641            # not in the room yet
642            return
643        user = room.getUser(room_jid.resource)
644        delay = None
645        # need to check for delay and x stanzas for delay namespace for backwards compatability
646        for e in msg.elements():
647            if e.uri == NS_DELAY or e.uri == NS_JABBER_DELAY:
648                delay = e
649        body  = unicode(msg.body)
650        # grab room
651        if delay is None:
652            self.receivedGroupChat(room, user, body)
653        else:
654            self.receivedHistory(room, user, body, delay['stamp'], frm=delay.getAttribute('from',None))
655
656
657    def _onSubject(self, msg):
658        """
659        """
660        if not msg.hasAttribute('from'):
661            return
662        room_jid = jid.internJID(msg['from'])
663
664        # grab room
665        room = self._getRoom(room_jid)
666        if room is None:
667            # not in the room yet
668            return
669
670        self.receivedSubject(room_jid, unicode(msg.subject))
671
672
673    def _makeTimeStamp(self, stamp=None):
674        if stamp is None:
675            stamp = datetime.datetime.now()
676           
677        return stamp.strftime('%Y%m%dT%H:%M:%S')
678
679
680    def _joinedRoom(self, d, prs):
681        """We have presence that says we joined a room.
682        """
683        room_jid = jid.internJID(prs['from'])
684       
685        # check for errors
686        if prs.hasAttribute('type') and prs['type'] == 'error':           
687            d.errback(self._getExceptionFromElement(prs))
688        else:   
689            # change the state of the room
690            r = self._getRoom(room_jid)
691            if r is None:
692                raise NotFound
693            r.state = 'joined'
694           
695            # grab status
696            status = getattr(prs.x,'status',None)
697            if status:
698                r.status = status.getAttribute('code', None)
699
700            d.callback(r)
701
702
703    def _leftRoom(self, d, prs):
704        """We have presence that says we joined a room.
705        """
706        room_jid = jid.internJID(prs['from'])
707       
708        # check for errors
709        if prs.hasAttribute('type') and prs['type'] == 'error':           
710            d.errback(self._getExceptionFromElement(prs))
711        else:   
712            # change the state of the room
713            r = self._getRoom(room_jid)
714            if r is None:
715                raise NotFound
716            self._removeRoom(room_jid)
717           
718            d.callback(True)
719
720    def initialized(self):
721        """Client is initialized and ready!
722        """
723        pass
724
725    def userJoinedRoom(self, room, user):
726        """User has joined a room
727        """
728        pass
729
730    def userLeftRoom(self, room, user):
731        """User has left a room
732        """
733        pass
734
735
736    def userUpdatedStatus(self, room, user, show, status):
737        """User Presence has been received
738        """
739        pass
740       
741
742    def receivedSubject(self, room, subject):
743        """
744        """
745        pass
746
747
748    def receivedHistory(self, room, user, message, history, frm=None):
749        """
750        """
751        pass
752
753
754    def _cbDisco(self, iq):
755        # grab query
756       
757        return getattr(iq,'query', None)
758       
759
760    def sendDeferred(self,  obj, timeout):
761        """ Send data or a domish element, adding a deferred with a timeout.
762        """
763        d = defer.Deferred()
764        self._deferreds.append(d)
765
766
767        def onTimeout():
768            i = 0
769            for xd in self._deferreds:
770                if d == xd:
771                    self._deferreds.pop(i)
772                    d.errback(xmlstream.TimeoutError("Timeout waiting for response."))
773                i += 1
774
775        call = reactor.callLater(timeout, onTimeout)
776       
777        def cancelTimeout(result):
778            if call.active():
779                call.cancel()
780
781            return result
782
783        d.addBoth(cancelTimeout)
784
785        self.xmlstream.send(obj)
786        return d
787
788    def disco(self, entity, type='info'):
789        """Send disco queries to a XMPP entity
790        """
791
792        iq = disco.DiscoRequest(self.xmlstream, disco.NS_INFO, 'get')
793        iq['to'] = entity
794
795        return iq.send().addBoth(self._cbDisco)
796       
797
798    def configure(self, room_jid, fields=[]):
799        """Configure a room
800
801        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
802        @type  room_jid: L{jid.JID}
803
804        """
805        request = ConfigureRequest(self.xmlstream, method='set', fields=fields)
806        request['to'] = room_jid
807       
808        return request.send()
809
810    def getConfigureForm(self, room_jid):
811        """Grab the configuration form from the room. This sends an iq request to the room.
812
813        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
814        @type  room_jid: L{jid.JID}
815
816        """
817        request = ConfigureRequest(self.xmlstream)
818        request['to'] = room_jid
819        return request.send()
820
821
822    def join(self, server, room, nick, history = None):
823        """ Join a MUC room by sending presence to it. Returns a defered that is called when
824        the entity is in the room or an error has occurred.
825       
826        @param server: The server where the room is located.
827        @type  server: L{unicode}
828
829        @param room: The room name the entity is joining.
830        @type  room: L{unicode}
831
832        @param nick: The nick name for the entitity joining the room.
833        @type  nick: L{unicode}
834       
835        @param history: The maximum number of history stanzas you would like.
836
837        """
838        r = Room(room, server, nick, state='joining')
839        self._setRoom(r)
840 
841        p = BasicPresence(to=r.entity_id)
842        if history is not None:
843            p.x.addChild(history.toElement())
844
845        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
846
847        # add observer for joining the room
848        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
849                                          self._joinedRoom, 1, d)
850
851        return d
852   
853    def _changed(self, d, room_jid, prs):
854        """Callback for changing the nick and status.
855        """
856
857        r = self._getRoom(room_jid)
858
859        d.callback(r)
860
861
862    def nick(self, room_jid, new_nick):
863        """ Change an entities nick name in a MUC room.
864       
865        See: http://xmpp.org/extensions/xep-0045.html#changenick
866
867        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
868        @type  room_jid: L{jid.JID}
869
870        @param new_nick: The nick name for the entitity joining the room.
871        @type  new_nick: L{unicode}
872       
873        """
874
875       
876        r = self._getRoom(room_jid)
877        if r is None:
878            raise NotFound
879        r.nick = new_nick # change the nick
880        # create presence
881        # make sure we call the method to generate the new entity xmpp id
882        p = BasicPresence(to=r.entityId()) 
883        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
884
885        # add observer for joining the room
886        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
887                                          self._changed, 1, d, room_jid)
888
889        return d
890       
891
892   
893    def leave(self, room_jid):
894        """
895        """
896        r = self._getRoom(room_jid)
897 
898        p = xmppim.UnavailablePresence(to=r.entity_id)
899
900        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
901        # add observer for joining the room
902        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s' and @type='unavailable']" % (r.entity_id.full()), 
903                                          self._leftRoom, 1, d)
904
905        return d
906   
907
908    def status(self, room_jid, show=None, status=None):
909        """Change user status.
910
911        See: http://xmpp.org/extensions/xep-0045.html#changepres
912
913        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
914        @type  room_jid: L{jid.JID}
915
916        @param show: The availability of the entity. Common values are xa, available, etc
917        @type  show: L{unicode}
918
919        @param show: The current status of the entity.
920        @type  show: L{unicode}
921
922        """
923        r = self._getRoom(room_jid)
924        if r is None:
925            raise NotFound
926
927        p = BasicPresence(to=r.entityId()) 
928        if status is not None:
929            p.addElement('status', None, status)
930           
931        if show is not None:
932            p.addElement('show', None, show)
933           
934        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
935
936        # add observer for joining the room
937        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
938                                          self._changed, 1, d, room_jid)
939
940        return d
941
942    def _sendMessage(self, msg, children=None):
943
944        if children:
945            for c in children:
946                msg.addChild(c)
947       
948        self.xmlstream.send(msg)
949
950    def groupChat(self, to, message, children=None):
951        """Send a groupchat message
952        """
953        msg = GroupChat(to, body=message)
954       
955        self._sendMessage(msg, children=children)
956
957    def chat(self, to, message, children=None):
958        msg = PrivateChat(to, body=message)
959
960        self._sendMessage(msg, children=children)
961       
962    def invite(self, to, reason=None, full_jid=None):
963        """
964        """
965        msg = InviteMessage(to, reason=reason, full_jid=full_jid)
966        self._sendMessage(msg)
967
968
969    def password(self, to, password):
970        p = PasswordPresence(to, password)
971
972        self.xmlstream.send(p)
973   
974    def register(self, to, fields=[]):
975        iq = RegisterRequest(self.xmlstream, method='set', fields=fields)
976        iq['to'] = to
977        return iq.send()
978
979
980    def _getAffiliationList(self, room_jid, affiliation):
981        iq = AffiliationRequest(self.xmlstream,
982                                method='get',
983                                affiliation=affiliation, 
984                                )
985        iq['to'] = room_jid.full()
986        return iq.send()       
987
988
989
990    def getMemberList(self, room_jid):
991        """ Get a member list from a room.
992
993        @param room_jid: The room jabber/xmpp entity id for the requested member list.
994        @type  room_jid: L{jid.JID}
995
996        """
997        return self._getAffiliationList(room_jid, 'member')
998
999
1000    def getAdminList(self, room_jid):
1001        """ Get an admin list from a room.
1002
1003        @param room_jid: The room jabber/xmpp entity id for the requested member list.
1004        @type  room_jid: L{jid.JID}
1005
1006        """
1007        return self._getAffiliationList(room_jid, 'admin')
1008
1009    def getBanList(self, room_jid):
1010        """ Get an outcast list from a room.
1011
1012        @param room_jid: The room jabber/xmpp entity id for the requested member list.
1013        @type  room_jid: L{jid.JID}
1014
1015        """
1016        return self._getAffiliationList(room_jid, 'outcast')
1017
1018    def getOwnerList(self, room_jid):
1019        """ Get an owner list from a room.
1020
1021        @param room_jid: The room jabber/xmpp entity id for the requested member list.
1022        @type  room_jid: L{jid.JID}
1023
1024        """
1025        return self._getAffiliationList(room_jid, 'owner')
1026
1027
1028    def getRegisterForm(self, room):
1029        """
1030
1031        @param room: The room jabber/xmpp entity id for the requested registration form.
1032        @type  room: L{jid.JID}
1033
1034        """
1035        iq = RegisterRequest(self.xmlstream)
1036        iq['to'] = room.userhost()
1037        return iq.send()
1038
1039    def destroy(self, room_jid, reason=None):
1040        """ Destroy a room.
1041       
1042        @param room_jid: The room jabber/xmpp entity id.
1043        @type  room_jid: L{jid.JID}
1044       
1045        """
1046        def destroyed(iq):
1047            self._removeRoom(room_jid)
1048            return True
1049
1050        iq = OwnerRequest(self.xmlstream, method='set')
1051        d  = iq.query.addElement('destroy')
1052        d['jid'] = room_jid.userhost()
1053        if reason is not None:
1054            d.addElement('reason', None, reason)
1055
1056        return iq.send().addCallback(destroyed)
1057
1058    def subject(self, to, subject):
1059        """
1060        """
1061        msg = GroupChat(to, subject=subject)
1062        self.xmlstream.send(msg)
1063
1064    def voice(self, to):
1065        """
1066        """
1067        msg = MessageVoice(to=to)
1068        self.xmlstream.send(msg)
1069
1070
1071    def history(self, to, message_list):
1072        """
1073        """
1074       
1075        for m in message_list:
1076            m['type'] = 'groupchat'
1077            mto = m['to']
1078            frm = m.getAttribute('from', None)
1079            m['to'] = to
1080
1081            d = m.addElement('delay', NS_DELAY)
1082            d['stamp'] = self._makeTimeStamp()
1083            d['from'] = mto
1084
1085            self.xmlstream.send(m)
1086
1087    def ban(self, to, ban_jid, frm, reason=None):
1088       
1089        iq = AffiliationRequest(self.xmlstream,
1090                                method='set',
1091                                affiliation='outcast', 
1092                                a_jid=ban_jid, 
1093                                reason=reason)
1094        iq['to'] = to.userhost() # this is a room jid, only send to room
1095        iq['from'] = frm.full()
1096        return iq.send()
1097
1098
1099    def kick(self, to, kick_jid, frm, reason=None):
1100       
1101        iq = AffiliationRequest(self.xmlstream,
1102                                method='set',
1103                                a_jid=kick_jid, 
1104                                reason=reason)
1105        iq['to'] = to.userhost() # this is a room jid, only send to room
1106        iq['from'] = frm.full()
1107        return iq.send()
Note: See TracBrowser for help on using the repository browser.