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@…>, 14 years ago

abstract role and affiliation requests

File size: 31.1 KB
RevLine 
[107]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"""
[110]12import datetime
[107]13
14from zope.interface import implements
15
[118]16from twisted.internet import defer, reactor
[107]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
[108]22from wokkel.iwokkel import IMUCClient
[107]23
24# Multi User Chat namespaces
[116]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'
[110]33
[116]34NS_DELAY        = 'urn:xmpp:delay'
35NS_JABBER_DELAY = 'jabber:x:delay'
36
[111]37NS_REQUEST  = 'jabber:iq:register'
[107]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
[116]57MUC_ADMIN = IQ_QUERY+'[@xmlns="' + NS_MUC_ADMIN + '"]'
58MUC_OWNER = IQ_QUERY+'[@xmlns="' + NS_MUC_OWNER + '"]'
[107]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
[110]69GROUPCHAT     = MESSAGE +'[@type="groupchat"]/body'
[113]70SUBJECT       = MESSAGE +'[@type="groupchat"]/subject'
[107]71MESSAGE_ERROR = MESSAGE +'[@type="error"]'
72
[110]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
[107]88
[118]89DEFER_TIMEOUT = 30 # basic timeout is 30 seconds
[107]90
[120]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
[121]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
[120]138
139MUC_EXCEPTIONS = {
140    'jid-malformed': JidMalformed,
141    'forbidden': Forbidden,
142    'not-authorized': NotAuthorized,
143    'exception': Exception,
[121]144    'conflict': Conflict,
145    'service-unavailable': ServiceUnavailable,
146    'not-found': NotFound,
[120]147    }
148
[107]149class MUCError(error.StanzaError):
150    """
151    Exception with muc specific condition.
152    """
[120]153    def __init__(self, condition, mucCondition, type='error', feature=None, text=None):
[116]154        appCondition = domish.Element((NS_MUC, mucCondition))
[107]155        if feature:
156            appCondition['feature'] = feature
157        error.StanzaError.__init__(self, condition,
[120]158                                         type=type,
[107]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
[108]180
[110]181class ConfigureRequest(xmlstream.IQ):
182    """
183    Configure MUC room request.
184
[113]185    http://xmpp.org/extensions/xep-0045.html#roomconfig
186
[110]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)
[116]193        q = self.addElement((NS_MUC_OWNER, 'query'))
[110]194        if method == 'set':
195            # build data form
[116]196            form = data_form.Form('submit', formNamespace=NS_MUC_CONFIG)
[110]197            q.addChild(form.toElement())
198           
199            for f in fields:
200                # create a field
201                form.addField(f)
202
203
[111]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}
[112]210
[111]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'       
[116]219            form = data_form.Form(form_type, formNamespace=NS_MUC_REGISTER)
[111]220            q.addChild(form.toElement())       
221           
222            for f in fields:
223                # create a field
224                form.addField(f)
225
[112]226
[119]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):
[112]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):
[119]269        AdminRequest.__init__(self, xs, method)
270
271        i = self.query.addElement('item')
[112]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           
[122]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)
[112]293
[110]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'
[116]302        if isinstance(to, jid.JID):
303            self['to'] = to.userhost()
304        else:
305            self['to'] = to
[110]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)
[112]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)
[110]327           
[112]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
[116]332        x = self.addElement('x', NS_MUC_USER)
[112]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)
[110]340
[112]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
[110]350
[121]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:
[122]381                if key == 'since':
382                    h[key] = a.strftime('%Y%m%dT%H:%M:%S')
383                else:
384                    h[key] = str(a)
[121]385       
386        return h
387
[113]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
[107]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
[114]416        self.status = 0
[110]417
[117]418        self.entity_id = self.entityId()
[107]419               
420        self.roster = {}
421
[117]422    def entityId(self):
423        """
424        """
425        self.entity_id = jid.internJID(self.name+'@'+self.server+'/'+self.nick)
426
427        return self.entity_id
[107]428
[113]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
[114]445    def removeUser(self, user):
446        if self.inRoster(user):
447            del self.roster[user.nick.lower()]
[113]448       
449
[111]450class BasicPresence(xmppim.AvailablePresence):
[107]451    """
452    This behaves like an object providing L{domish.IElement}.
453
454    """
455
[111]456    def __init__(self, to=None, show=None, statuses=None):
457        xmppim.AvailablePresence.__init__(self, to=to, show=show, statuses=statuses)
[107]458        # add muc elements
[116]459        x = self.addElement('x', NS_MUC)
[107]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
[116]473        x = self.addElement('x', NS_MUC_USER)
[107]474        if affiliation:
475            x['affiliation'] = affiliation
476        if role:
477            x['role'] = role
478
[118]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
[107]496
[110]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
[116]512        form = data_form.Form('submit', formNamespace=NS_MUC_REQUEST)
[110]513        form.addField(data_form.Field(var='muc#role',
514                                      value='participant', 
515                                      label='Requested role'))
516        self.addChild(form.toElement())           
517
[111]518class PresenceError(xmppim.Presence):
[107]519    """
520    This behaves like an object providing L{domish.IElement}.
521
522    """
523
[108]524    def __init__(self, error, to=None, frm=None):
[111]525        xmppim.Presence.__init__(self, to, type='error')
[108]526        if frm:
527            self['from'] = frm
[107]528        # add muc elements
[116]529        x = self.addElement('x', NS_MUC)
[120]530       
[107]531        # add error
[120]532        e = error.getElement()
533        self.addChild(e)
[107]534       
535
536class MUCClient(XMPPHandler):
537    """
538    Multi-User chat client protocol.
539    """
540
541    implements(IMUCClient)
542
[110]543    rooms = {}
[107]544
[118]545    timeout = None
546
547    _deferreds = []
548
[107]549    def connectionInitialized(self):
[113]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)
[110]553        self.xmlstream.addObserver(GROUPCHAT, self._onGroupChat)
554        self.xmlstream.addObserver(SUBJECT, self._onSubject)
555        # add history
[107]556
[115]557        self.initialized()
[114]558
[107]559    def _setRoom(self, room):
[114]560        self.rooms[room.entity_id.userhost().lower()] = room
[107]561
562    def _getRoom(self, room_jid):
[114]563        return self.rooms.get(room_jid.userhost().lower())
[107]564
[111]565    def _removeRoom(self, room_jid):
[114]566        if self.rooms.has_key(room_jid.userhost().lower()):
567            del self.rooms[room_jid.userhost().lower()]
[107]568
[113]569
570    def _onUnavailablePresence(self, prs):
571        """
572        """
[118]573
[113]574        if not prs.hasAttribute('from'):
575            return
576        room_jid = jid.internJID(prs.getAttribute('from', ''))
[114]577        self._userLeavesRoom(room_jid)
[113]578
579    def _onPresenceError(self, prs):
580        """
581        """
582        if not prs.hasAttribute('from'):
583            return
584        room_jid = jid.internJID(prs.getAttribute('from', ''))
[114]585        # add an error hook here?
586        self._userLeavesRoom(room_jid)
[120]587       
[121]588    def _getExceptionFromElement(self, stanza):
[120]589        muc_condition = 'exception'
590
[121]591        error = getattr(stanza, 'error', None)
[120]592        if error is not None:
593            for e in error.elements():
594                muc_condition = e.name
595
596        return MUC_EXCEPTIONS[muc_condition]
[113]597
[114]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       
[110]611    def _onXPresence(self, prs):
612        """
613        """
[113]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)
[110]643           
644
645    def _onGroupChat(self, msg):
646        """
647        """
[113]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)
[116]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
[113]663        body  = unicode(msg.body)
664        # grab room
[112]665        if delay is None:
[113]666            self.receivedGroupChat(room, user, body)
[112]667        else:
[113]668            self.receivedHistory(room, user, body, delay['stamp'], frm=delay.getAttribute('from',None))
[110]669
670
671    def _onSubject(self, msg):
672        """
673        """
[113]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))
[110]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
[107]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'])
[108]698       
[107]699        # check for errors
700        if prs.hasAttribute('type') and prs['type'] == 'error':           
[121]701            d.errback(self._getExceptionFromElement(prs))
[108]702        else:   
703            # change the state of the room
704            r = self._getRoom(room_jid)
[110]705            if r is None:
[121]706                raise NotFound
[108]707            r.state = 'joined'
[110]708           
709            # grab status
710            status = getattr(prs.x,'status',None)
711            if status:
712                r.status = status.getAttribute('code', None)
713
[108]714            d.callback(r)
[107]715
[111]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':           
[121]724            d.errback(self._getExceptionFromElement(prs))
[111]725        else:   
726            # change the state of the room
727            r = self._getRoom(room_jid)
728            if r is None:
[121]729                raise NotFound
[111]730            self._removeRoom(room_jid)
731           
732            d.callback(True)
733
[115]734    def initialized(self):
735        """Client is initialized and ready!
736        """
737        pass
738
[113]739    def userJoinedRoom(self, room, user):
740        """User has joined a room
741        """
742        pass
743
[114]744    def userLeftRoom(self, room, user):
745        """User has left a room
746        """
747        pass
748
[113]749
[115]750    def userUpdatedStatus(self, room, user, show, status):
[107]751        """User Presence has been received
752        """
753        pass
754       
[108]755
[113]756    def receivedSubject(self, room, subject):
[110]757        """
758        """
759        pass
760
[112]761
[113]762    def receivedHistory(self, room, user, message, history, frm=None):
[112]763        """
764        """
765        pass
766
767
[108]768    def _cbDisco(self, iq):
769        # grab query
770       
[110]771        return getattr(iq,'query', None)
[108]772       
[118]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
[108]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
[110]809        return iq.send().addBoth(self._cbDisco)
[108]810       
811
[111]812    def configure(self, room_jid, fields=[]):
[110]813        """Configure a room
[117]814
815        @param room_jid: The room jabber/xmpp entity id for the requested configuration form.
816        @type  room_jid: L{jid.JID}
817
[110]818        """
[111]819        request = ConfigureRequest(self.xmlstream, method='set', fields=fields)
[110]820        request['to'] = room_jid
821       
822        return request.send()
823
824    def getConfigureForm(self, room_jid):
[117]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        """
[110]831        request = ConfigureRequest(self.xmlstream)
832        request['to'] = room_jid
833        return request.send()
834
835
[121]836    def join(self, server, room, nick, history = None):
[117]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       
[121]849        @param history: The maximum number of history stanzas you would like.
850
[107]851        """
852        r = Room(room, server, nick, state='joining')
853        self._setRoom(r)
854 
855        p = BasicPresence(to=r.entity_id)
[121]856        if history is not None:
857            p.x.addChild(history.toElement())
858
[118]859        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
[107]860
861        # add observer for joining the room
862        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
[108]863                                          self._joinedRoom, 1, d)
[107]864
865        return d
866   
[119]867    def _changed(self, d, room_jid, prs):
868        """Callback for changing the nick and status.
[117]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
[118]889       
[117]890        r = self._getRoom(room_jid)
891        if r is None:
[121]892            raise NotFound
[117]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()) 
[118]897        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
[117]898
899        # add observer for joining the room
900        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
[119]901                                          self._changed, 1, d, room_jid)
[117]902
903        return d
904       
[107]905
[111]906   
907    def leave(self, room_jid):
908        """
909        """
[112]910        r = self._getRoom(room_jid)
[111]911 
912        p = xmppim.UnavailablePresence(to=r.entity_id)
913
[118]914        d = self.sendDeferred(p, timeout=DEFER_TIMEOUT)
[111]915        # add observer for joining the room
[112]916        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s' and @type='unavailable']" % (r.entity_id.full()), 
[111]917                                          self._leftRoom, 1, d)
918
919        return d
920   
921
[119]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:
[121]939            raise NotFound
[119]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
[111]955
[112]956    def _sendMessage(self, msg, children=None):
[110]957
958        if children:
959            for c in children:
960                msg.addChild(c)
961       
962        self.xmlstream.send(msg)
963
[112]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):
[113]977        """
978        """
[112]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)
[110]987   
[111]988    def register(self, to, fields=[]):
989        iq = RegisterRequest(self.xmlstream, method='set', fields=fields)
990        iq['to'] = to
991        return iq.send()
992
[120]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
[119]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        """
[120]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
[119]1041
[113]1042    def getRegisterForm(self, room):
1043        """
[119]1044
1045        @param room: The room jabber/xmpp entity id for the requested registration form.
1046        @type  room: L{jid.JID}
1047
[113]1048        """
[111]1049        iq = RegisterRequest(self.xmlstream)
[113]1050        iq['to'] = room.userhost()
[111]1051        return iq.send()
1052
[120]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
[110]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
[122]1085
[110]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
[122]1102    def _setAffiliation(self, frm, room_jid, affiliation, reason=None, a_jid=None):
[112]1103        iq = AffiliationRequest(self.xmlstream,
1104                                method='set',
[122]1105                                affiliation=affiliation,
1106                                a_jid=a_jid, 
[112]1107                                reason=reason)
[122]1108        iq['to'] = room_jid.userhost() # this is a room jid, only send to room
[112]1109        iq['from'] = frm.full()
1110        return iq.send()
1111
[122]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
[112]1119        iq['from'] = frm.full()
1120        return iq.send()
[122]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.