source: wokkel/muc.py @ 122:1c21cf10bb60

wokkel-muc-client-support-24
Last change on this file since 122:1c21cf10bb60 was 122:1c21cf10bb60, checked in by Christopher Zorn <tofu@…>, 11 years ago

abstract role and affiliation requests

File size: 31.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           
281class RoleRequest(AdminRequest):
282    def __init__(self, xs, method='get', role='none', a_jid=None, reason=None):
283        AdminRequest.__init__(self, xs, method)
284
285        i = self.query.addElement('item')
286
287        i['role'] = role
288        if a_jid:
289            i['jid'] = a_jid.full()
290           
291        if reason:
292            i.addElement('reason', None, reason)
293
294class GroupChat(domish.Element):
295    """
296    """
297    def __init__(self, to, body=None, subject=None, frm=None):
298        """To needs to be a string
299        """
300        domish.Element.__init__(self, (None, 'message'))
301        self['type'] = 'groupchat'
302        if isinstance(to, jid.JID):
303            self['to'] = to.userhost()
304        else:
305            self['to'] = to
306        if frm:
307            self['from'] = frm
308        if body:
309            self.addElement('body',None, body)
310        if subject:
311            self.addElement('subject',None, subject)
312
313
314class PrivateChat(domish.Element):
315    """
316    """
317    def __init__(self, to, body=None, frm=None):
318        """To needs to be a string
319        """
320        domish.Element.__init__(self, (None, 'message'))
321        self['type'] = 'chat'
322        self['to']   = to
323        if frm:
324            self['from'] = frm
325        if body:
326            self.addElement('body',None, body)
327           
328class InviteMessage(PrivateChat):
329    def __init__(self, to, reason=None, full_jid=None, body=None, frm=None, password=None):
330        PrivateChat.__init__(self, to, body=body, frm=frm)
331        del self['type'] # remove type
332        x = self.addElement('x', NS_MUC_USER)
333        invite = x.addElement('invite')
334        if full_jid:
335            invite['to'] = full_jid
336        if reason:
337            invite.addElement('reason', None, reason)
338        if password:
339            invite.addElement('password', None, password)
340
341class HistoryMessage(GroupChat):
342    """
343    """
344    def __init__(self, to, stamp, body=None, subject=None, frm=None, h_frm=None):
345        GroupChat.__init__(self, to, body=body, subject=subject, frm=frm)
346        d = self.addElement('delay', NS_DELAY)
347        d['stamp'] = stamp
348        if h_frm:
349            d['from'] = h_frm
350
351class HistoryOptions(object):
352    """A history configuration object.
353
354    @ivar maxchars: Limit the total number of characters in the history to "X"
355                    (where the character count is the characters of the complete XML stanzas, not only their XML character data).
356    @itype maxchars: L{int}
357               
358    @ivar  maxstanzas:  Limit the total number of messages in the history to "X".
359    @itype mazstanzas: L{int}
360
361    @ivar  seconds: Send only the messages received in the last "X" seconds.
362    @itype seconds: L{int}
363
364    @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]).
365    @itype since: L{datetime.datetime}
366
367    """
368    attributes = ['maxchars', 'maxstanzas', 'seconds', 'since']
369
370    def __init__(self, maxchars=None, maxstanzas=None, seconds=None, since=None):
371        self.maxchars   = maxchars
372        self.maxstanzas = maxstanzas
373        self.seconds    = seconds
374        self.since      = since
375
376    def toElement(self):
377        h = domish.Element((None, 'history'))
378        for key in self.attributes:
379            a = getattr(self, key, a)
380            if a is not None:
381                if key == 'since':
382                    h[key] = a.strftime('%Y%m%dT%H:%M:%S')
383                else:
384                    h[key] = str(a)
385       
386        return h
387
388class User(object):
389    """
390    A user/entity in a multi-user chat room.
391    """
392   
393    def __init__(self, nick, user_jid=None):
394        self.nick = nick
395        self.user_jid = user_jid
396        self.affiliation = 'none'
397        self.role = 'none'
398       
399        self.status = None
400        self.show   = None
401
402
403class Room(object):
404    """
405    A Multi User Chat Room
406    """
407
408   
409    def __init__(self, name, server, nick, state=None):
410        """
411        """
412        self.state  = state
413        self.name   = name
414        self.server = server
415        self.nick   = nick
416        self.status = 0
417
418        self.entity_id = self.entityId()
419               
420        self.roster = {}
421
422    def entityId(self):
423        """
424        """
425        self.entity_id = jid.internJID(self.name+'@'+self.server+'/'+self.nick)
426
427        return self.entity_id
428
429    def addUser(self, user):
430        """
431        """
432        self.roster[user.nick.lower()] = user
433
434    def inRoster(self, user):
435        """
436        """
437
438        return self.roster.has_key(user.nick.lower())
439
440    def getUser(self, nick):
441        """
442        """
443        return self.roster.get(nick.lower())
444
445    def removeUser(self, user):
446        if self.inRoster(user):
447            del self.roster[user.nick.lower()]
448       
449
450class BasicPresence(xmppim.AvailablePresence):
451    """
452    This behaves like an object providing L{domish.IElement}.
453
454    """
455
456    def __init__(self, to=None, show=None, statuses=None):
457        xmppim.AvailablePresence.__init__(self, to=to, show=show, statuses=statuses)
458        # add muc elements
459        x = self.addElement('x', NS_MUC)
460
461
462class UserPresence(xmppim.Presence):
463    """
464    This behaves like an object providing L{domish.IElement}.
465
466    """
467
468    def __init__(self, to=None, type=None, frm=None, affiliation=None, role=None):
469        xmppim.Presence.__init__(self, to, type)
470        if frm:
471            self['from'] = frm
472        # add muc elements
473        x = self.addElement('x', NS_MUC_USER)
474        if affiliation:
475            x['affiliation'] = affiliation
476        if role:
477            x['role'] = role
478
479class UnavailableUserPresence(xmppim.UnavailablePresence):
480    """
481    This behaves like an object providing L{domish.IElement}.
482
483    """
484
485    def __init__(self, to=None, type=None, frm=None, affiliation=None, role=None):
486        xmppim.UnavailablePresence.__init__(self, to, type)
487        if frm:
488            self['from'] = frm
489        # add muc elements
490        x = self.addElement('x', NS_MUC_USER)
491        if affiliation:
492            x['affiliation'] = affiliation
493        if role:
494            x['role'] = role
495
496
497class PasswordPresence(BasicPresence):
498    """
499    """
500    def __init__(self, to, password):
501        BasicPresence.__init__(self, to)
502       
503        self.x.addElement('password', None, password)
504
505
506class MessageVoice(GroupChat):
507    """
508    """
509    def __init__(self, to=None, frm=None):
510        GroupChat.__init__(self, to=to, frm=frm)
511        # build data form
512        form = data_form.Form('submit', formNamespace=NS_MUC_REQUEST)
513        form.addField(data_form.Field(var='muc#role',
514                                      value='participant', 
515                                      label='Requested role'))
516        self.addChild(form.toElement())           
517
518class PresenceError(xmppim.Presence):
519    """
520    This behaves like an object providing L{domish.IElement}.
521
522    """
523
524    def __init__(self, error, to=None, frm=None):
525        xmppim.Presence.__init__(self, to, type='error')
526        if frm:
527            self['from'] = frm
528        # add muc elements
529        x = self.addElement('x', NS_MUC)
530       
531        # add error
532        e = error.getElement()
533        self.addChild(e)
534       
535
536class MUCClient(XMPPHandler):
537    """
538    Multi-User chat client protocol.
539    """
540
541    implements(IMUCClient)
542
543    rooms = {}
544
545    timeout = None
546
547    _deferreds = []
548
549    def connectionInitialized(self):
550        self.xmlstream.addObserver(PRESENCE+"[not(@type) or @type='available']/x", self._onXPresence)
551        self.xmlstream.addObserver(PRESENCE+"[@type='unavailable']", self._onUnavailablePresence)
552        self.xmlstream.addObserver(PRESENCE+"[@type='error']", self._onPresenceError)
553        self.xmlstream.addObserver(GROUPCHAT, self._onGroupChat)
554        self.xmlstream.addObserver(SUBJECT, self._onSubject)
555        # add history
556
557        self.initialized()
558
559    def _setRoom(self, room):
560        self.rooms[room.entity_id.userhost().lower()] = room
561
562    def _getRoom(self, room_jid):
563        return self.rooms.get(room_jid.userhost().lower())
564
565    def _removeRoom(self, room_jid):
566        if self.rooms.has_key(room_jid.userhost().lower()):
567            del self.rooms[room_jid.userhost().lower()]
568
569
570    def _onUnavailablePresence(self, prs):
571        """
572        """
573
574        if not prs.hasAttribute('from'):
575            return
576        room_jid = jid.internJID(prs.getAttribute('from', ''))
577        self._userLeavesRoom(room_jid)
578
579    def _onPresenceError(self, prs):
580        """
581        """
582        if not prs.hasAttribute('from'):
583            return
584        room_jid = jid.internJID(prs.getAttribute('from', ''))
585        # add an error hook here?
586        self._userLeavesRoom(room_jid)
587       
588    def _getExceptionFromElement(self, stanza):
589        muc_condition = 'exception'
590
591        error = getattr(stanza, 'error', None)
592        if error is not None:
593            for e in error.elements():
594                muc_condition = e.name
595
596        return MUC_EXCEPTIONS[muc_condition]
597
598    def _userLeavesRoom(self, room_jid):
599        room = self._getRoom(room_jid)
600        if room is None:
601            # not in the room yet
602            return
603        # check if user is in roster
604        user = room.getUser(room_jid.resource)
605        if user is None:
606            return
607        if room.inRoster(user):
608            room.removeUser(user)
609            self.userLeftRoom(room, user)
610       
611    def _onXPresence(self, prs):
612        """
613        """
614        if not prs.hasAttribute('from'):
615            return
616        room_jid = jid.internJID(prs.getAttribute('from', ''))
617           
618        status = getattr(prs, 'status', None)
619        show   = getattr(prs, 'show', None)
620       
621        # grab room
622        room = self._getRoom(room_jid)
623        if room is None:
624            # not in the room yet
625            return
626
627        # check if user is in roster
628        user = room.getUser(room_jid.resource)
629        if user is None: # create a user that does not exist
630            user = User(room_jid.resource)
631           
632       
633        if room.inRoster(user):
634            # we changed status or nick
635            muc_status = getattr(prs.x, 'status', None)
636            if muc_status:
637                code = muc_status.getAttribute('code', 0)
638            else:
639                self.userUpdatedStatus(room, user, show, status)
640        else:           
641            room.addUser(user)
642            self.userJoinedRoom(room, user)
643           
644
645    def _onGroupChat(self, msg):
646        """
647        """
648        if not msg.hasAttribute('from'):
649            # need to return an error here
650            return
651        room_jid = jid.internJID(msg.getAttribute('from', ''))
652
653        room = self._getRoom(room_jid)
654        if room is None:
655            # not in the room yet
656            return
657        user = room.getUser(room_jid.resource)
658        delay = None
659        # need to check for delay and x stanzas for delay namespace for backwards compatability
660        for e in msg.elements():
661            if e.uri == NS_DELAY or e.uri == NS_JABBER_DELAY:
662                delay = e
663        body  = unicode(msg.body)
664        # grab room
665        if delay is None:
666            self.receivedGroupChat(room, user, body)
667        else:
668            self.receivedHistory(room, user, body, delay['stamp'], frm=delay.getAttribute('from',None))
669
670
671    def _onSubject(self, msg):
672        """
673        """
674        if not msg.hasAttribute('from'):
675            return
676        room_jid = jid.internJID(msg['from'])
677
678        # grab room
679        room = self._getRoom(room_jid)
680        if room is None:
681            # not in the room yet
682            return
683
684        self.receivedSubject(room_jid, unicode(msg.subject))
685
686
687    def _makeTimeStamp(self, stamp=None):
688        if stamp is None:
689            stamp = datetime.datetime.now()
690           
691        return stamp.strftime('%Y%m%dT%H:%M:%S')
692
693
694    def _joinedRoom(self, d, prs):
695        """We have presence that says we joined a room.
696        """
697        room_jid = jid.internJID(prs['from'])
698       
699        # check for errors
700        if prs.hasAttribute('type') and prs['type'] == 'error':           
701            d.errback(self._getExceptionFromElement(prs))
702        else:   
703            # change the state of the room
704            r = self._getRoom(room_jid)
705            if r is None:
706                raise NotFound
707            r.state = 'joined'
708           
709            # grab status
710            status = getattr(prs.x,'status',None)
711            if status:
712                r.status = status.getAttribute('code', None)
713
714            d.callback(r)
715
716
717    def _leftRoom(self, d, prs):
718        """We have presence that says we joined a room.
719        """
720        room_jid = jid.internJID(prs['from'])
721       
722        # check for errors
723        if prs.hasAttribute('type') and prs['type'] == 'error':           
724            d.errback(self._getExceptionFromElement(prs))
725        else:   
726            # change the state of the room
727            r = self._getRoom(room_jid)
728            if r is None:
729                raise NotFound
730            self._removeRoom(room_jid)
731           
732            d.callback(True)
733
734    def initialized(self):
735        """Client is initialized and ready!
736        """
737        pass
738
739    def userJoinedRoom(self, room, user):
740        """User has joined a room
741        """
742        pass
743
744    def userLeftRoom(self, room, user):
745        """User has left a room
746        """
747        pass
748
749
750    def userUpdatedStatus(self, room, user, show, status):
751        """User Presence has been received
752        """
753        pass
754       
755
756    def receivedSubject(self, room, subject):
757        """
758        """
759        pass
760
761
762    def receivedHistory(self, room, user, message, history, frm=None):
763        """
764        """
765        pass
766
767
768    def _cbDisco(self, iq):
769        # grab query
770       
771        return getattr(iq,'query', None)
772       
773
774    def sendDeferred(self,  obj, timeout):
775        """ Send data or a domish element, adding a deferred with a timeout.
776        """
777        d = defer.Deferred()
778        self._deferreds.append(d)
779
780
781        def onTimeout():
782            i = 0
783            for xd in self._deferreds:
784                if d == xd:
785                    self._deferreds.pop(i)
786                    d.errback(xmlstream.TimeoutError("Timeout waiting for response."))
787                i += 1
788
789        call = reactor.callLater(timeout, onTimeout)
790       
791        def cancelTimeout(result):
792            if call.active():
793                call.cancel()
794
795            return result
796
797        d.addBoth(cancelTimeout)
798
799        self.xmlstream.send(obj)
800        return d
801
802    def disco(self, entity, type='info'):
803        """Send disco queries to a XMPP entity
804        """
805
806        iq = disco.DiscoRequest(self.xmlstream, disco.NS_INFO, 'get')
807        iq['to'] = entity
808
809        return iq.send().addBoth(self._cbDisco)
810       
811
812    def configure(self, room_jid, fields=[]):
813        """Configure a room
814
815        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
816        @type  room_jid: L{jid.JID}
817
818        """
819        request = ConfigureRequest(self.xmlstream, method='set', fields=fields)
820        request['to'] = room_jid
821       
822        return request.send()
823
824    def getConfigureForm(self, room_jid):
825        """Grab the configuration form from the room. This sends an iq request to the room.
826
827        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
828        @type  room_jid: L{jid.JID}
829
830        """
831        request = ConfigureRequest(self.xmlstream)
832        request['to'] = room_jid
833        return request.send()
834
835
836    def join(self, server, room, nick, history = None):
837        """ Join a MUC room by sending presence to it. Returns a defered that is called when
838        the entity is in the room or an error has occurred.
839       
840        @param server: The server where the room is located.
841        @type  server: L{unicode}
842
843        @param room: The room name the entity is joining.
844        @type  room: L{unicode}
845
846        @param nick: The nick name for the entitity joining the room.
847        @type  nick: L{unicode}
848       
849        @param history: The maximum number of history stanzas you would like.
850
851        """
852        r = Room(room, server, nick, state='joining')
853        self._setRoom(r)
854 
855        p = BasicPresence(to=r.entity_id)
856        if history is not None:
857            p.x.addChild(history.toElement())
858
859        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
860
861        # add observer for joining the room
862        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
863                                          self._joinedRoom, 1, d)
864
865        return d
866   
867    def _changed(self, d, room_jid, prs):
868        """Callback for changing the nick and status.
869        """
870
871        r = self._getRoom(room_jid)
872
873        d.callback(r)
874
875
876    def nick(self, room_jid, new_nick):
877        """ Change an entities nick name in a MUC room.
878       
879        See: http://xmpp.org/extensions/xep-0045.html#changenick
880
881        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
882        @type  room_jid: L{jid.JID}
883
884        @param new_nick: The nick name for the entitity joining the room.
885        @type  new_nick: L{unicode}
886       
887        """
888
889       
890        r = self._getRoom(room_jid)
891        if r is None:
892            raise NotFound
893        r.nick = new_nick # change the nick
894        # create presence
895        # make sure we call the method to generate the new entity xmpp id
896        p = BasicPresence(to=r.entityId()) 
897        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
898
899        # add observer for joining the room
900        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
901                                          self._changed, 1, d, room_jid)
902
903        return d
904       
905
906   
907    def leave(self, room_jid):
908        """
909        """
910        r = self._getRoom(room_jid)
911 
912        p = xmppim.UnavailablePresence(to=r.entity_id)
913
914        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
915        # add observer for joining the room
916        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s' and @type='unavailable']" % (r.entity_id.full()), 
917                                          self._leftRoom, 1, d)
918
919        return d
920   
921
922    def status(self, room_jid, show=None, status=None):
923        """Change user status.
924
925        See: http://xmpp.org/extensions/xep-0045.html#changepres
926
927        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
928        @type  room_jid: L{jid.JID}
929
930        @param show: The availability of the entity. Common values are xa, available, etc
931        @type  show: L{unicode}
932
933        @param show: The current status of the entity.
934        @type  show: L{unicode}
935
936        """
937        r = self._getRoom(room_jid)
938        if r is None:
939            raise NotFound
940
941        p = BasicPresence(to=r.entityId()) 
942        if status is not None:
943            p.addElement('status', None, status)
944           
945        if show is not None:
946            p.addElement('show', None, show)
947           
948        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
949
950        # add observer for joining the room
951        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
952                                          self._changed, 1, d, room_jid)
953
954        return d
955
956    def _sendMessage(self, msg, children=None):
957
958        if children:
959            for c in children:
960                msg.addChild(c)
961       
962        self.xmlstream.send(msg)
963
964    def groupChat(self, to, message, children=None):
965        """Send a groupchat message
966        """
967        msg = GroupChat(to, body=message)
968       
969        self._sendMessage(msg, children=children)
970
971    def chat(self, to, message, children=None):
972        msg = PrivateChat(to, body=message)
973
974        self._sendMessage(msg, children=children)
975       
976    def invite(self, to, reason=None, full_jid=None):
977        """
978        """
979        msg = InviteMessage(to, reason=reason, full_jid=full_jid)
980        self._sendMessage(msg)
981
982
983    def password(self, to, password):
984        p = PasswordPresence(to, password)
985
986        self.xmlstream.send(p)
987   
988    def register(self, to, fields=[]):
989        iq = RegisterRequest(self.xmlstream, method='set', fields=fields)
990        iq['to'] = to
991        return iq.send()
992
993
994    def _getAffiliationList(self, room_jid, affiliation):
995        iq = AffiliationRequest(self.xmlstream,
996                                method='get',
997                                affiliation=affiliation, 
998                                )
999        iq['to'] = room_jid.full()
1000        return iq.send()       
1001
1002
1003
1004    def getMemberList(self, room_jid):
1005        """ Get a member list from a room.
1006
1007        @param room_jid: The room jabber/xmpp entity id for the requested member list.
1008        @type  room_jid: L{jid.JID}
1009
1010        """
1011        return self._getAffiliationList(room_jid, 'member')
1012
1013
1014    def getAdminList(self, room_jid):
1015        """ Get an admin list from a room.
1016
1017        @param room_jid: The room jabber/xmpp entity id for the requested member list.
1018        @type  room_jid: L{jid.JID}
1019
1020        """
1021        return self._getAffiliationList(room_jid, 'admin')
1022
1023    def getBanList(self, room_jid):
1024        """ Get an outcast list from a room.
1025
1026        @param room_jid: The room jabber/xmpp entity id for the requested member list.
1027        @type  room_jid: L{jid.JID}
1028
1029        """
1030        return self._getAffiliationList(room_jid, 'outcast')
1031
1032    def getOwnerList(self, room_jid):
1033        """ Get an owner list from a room.
1034
1035        @param room_jid: The room jabber/xmpp entity id for the requested member list.
1036        @type  room_jid: L{jid.JID}
1037
1038        """
1039        return self._getAffiliationList(room_jid, 'owner')
1040
1041
1042    def getRegisterForm(self, room):
1043        """
1044
1045        @param room: The room jabber/xmpp entity id for the requested registration form.
1046        @type  room: L{jid.JID}
1047
1048        """
1049        iq = RegisterRequest(self.xmlstream)
1050        iq['to'] = room.userhost()
1051        return iq.send()
1052
1053    def destroy(self, room_jid, reason=None):
1054        """ Destroy a room.
1055       
1056        @param room_jid: The room jabber/xmpp entity id.
1057        @type  room_jid: L{jid.JID}
1058       
1059        """
1060        def destroyed(iq):
1061            self._removeRoom(room_jid)
1062            return True
1063
1064        iq = OwnerRequest(self.xmlstream, method='set')
1065        d  = iq.query.addElement('destroy')
1066        d['jid'] = room_jid.userhost()
1067        if reason is not None:
1068            d.addElement('reason', None, reason)
1069
1070        return iq.send().addCallback(destroyed)
1071
1072    def subject(self, to, subject):
1073        """
1074        """
1075        msg = GroupChat(to, subject=subject)
1076        self.xmlstream.send(msg)
1077
1078    def voice(self, to):
1079        """
1080        """
1081        msg = MessageVoice(to=to)
1082        self.xmlstream.send(msg)
1083
1084
1085
1086    def history(self, to, message_list):
1087        """
1088        """
1089       
1090        for m in message_list:
1091            m['type'] = 'groupchat'
1092            mto = m['to']
1093            frm = m.getAttribute('from', None)
1094            m['to'] = to
1095
1096            d = m.addElement('delay', NS_DELAY)
1097            d['stamp'] = self._makeTimeStamp()
1098            d['from'] = mto
1099
1100            self.xmlstream.send(m)
1101
1102    def _setAffiliation(self, frm, room_jid, affiliation, reason=None, a_jid=None):
1103        iq = AffiliationRequest(self.xmlstream,
1104                                method='set',
1105                                affiliation=affiliation,
1106                                a_jid=a_jid, 
1107                                reason=reason)
1108        iq['to'] = room_jid.userhost() # this is a room jid, only send to room
1109        iq['from'] = frm.full()
1110        return iq.send()
1111
1112    def _setRole(self, frm, room_jid, a_jid=None, reason=None, role='none'):
1113        iq = RoleRequest(self.xmlstream,
1114                         method='set',
1115                         a_jid=a_jid,
1116                         role=role,
1117                         reason=reason)
1118        iq['to'] = room_jid.userhost() # this is a room jid, only send to room
1119        iq['from'] = frm.full()
1120        return iq.send()
1121
1122    def grantVoice(self, frm, room_jid, reason=None):
1123        return self._setAffiliation(frm, room_jid, 'participant', reason=reason)
1124
1125
1126    def ban(self, to, ban_jid, frm, reason=None):
1127        return self._setAffiliation(frm, to, 'outcast', a_jid=ban_jid, reason=reason)
1128
1129
1130    def kick(self, to, kick_jid, frm, reason=None):       
1131        return self._setAffiliation(frm, to, 'none', a_jid=kick_jid, reason=reason)
1132       
Note: See TracBrowser for help on using the repository browser.