source: wokkel/muc.py @ 120:9548851ca5ac

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

implement error conditions

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