source: wokkel/muc.py @ 126:f6c8a769e38e

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

make sure we change status and add a test

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