source: wokkel/muc.py @ 108:50e3c368f2f3

wokkel-muc-client-support-24
Last change on this file since 108:50e3c368f2f3 was 108:50e3c368f2f3, checked in by Christopher Zorn <tofu@…>, 14 years ago

add basic errors and disco stuff, still need iq and message parts re #24

File size: 5.6 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"""
12
13from zope.interface import implements
14
15from twisted.internet import defer
16from twisted.words.protocols.jabber import jid, error, xmlstream
17from twisted.words.xish import domish
18
19from wokkel import disco, data_form, shim, xmppim
20from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
21from wokkel.iwokkel import IMUCClient
22
23# Multi User Chat namespaces
24NS          = 'http://jabber.org/protocol/muc'
25NS_USER     = NS + '#user'
26NS_ADMIN    = NS + '#admin'
27NS_OWNER    = NS + '#owner'
28NS_ROOMINFO = NS + '#roominfo'
29NS_CONFIG   = NS + '#roomconfig'
30
31# ad hoc commands
32NS_AD_HOC       = "http://jabber.org/protocol/commands"
33
34
35# Iq get and set XPath queries
36IQ     = '/iq'
37IQ_GET = IQ+'[@type="get"]'
38IQ_SET = IQ+'[@type="set"]'
39
40IQ_RESULT = IQ+'[@type="result"]'
41IQ_ERROR  = IQ+'[@type="error"]'
42
43IQ_QUERY     = IQ+'/query'
44IQ_GET_QUERY = IQ_GET + '/query'
45IQ_SET_QUERY = IQ_SET + '/query'
46
47IQ_COMMAND   = IQ+'/command'
48
49MUC_ADMIN = IQ_QUERY+'[@xmlns="' + NS_ADMIN + '"]'
50MUC_OWNER = IQ_QUERY+'[@xmlns="' + NS_OWNER + '"]'
51
52MUC_AO = MUC_ADMIN + '|' + MUC_OWNER
53
54
55MESSAGE  = '/message'
56PRESENCE = '/presence'
57
58CHAT_BODY = MESSAGE +'[@type="chat"]/body'
59CHAT      = MESSAGE +'[@type="chat"]'
60
61GROUPCHAT    = MESSAGE +'[@type="groupchat"]'
62MESSAGE_ERROR = MESSAGE +'[@type="error"]'
63
64
65
66class MUCError(error.StanzaError):
67    """
68    Exception with muc specific condition.
69    """
70    def __init__(self, condition, mucCondition, feature=None, text=None):
71        appCondition = domish.Element((NS, mucCondition))
72        if feature:
73            appCondition['feature'] = feature
74        error.StanzaError.__init__(self, condition,
75                                         text=text,
76                                         appCondition=appCondition)
77
78
79class BadRequest(MUCError):
80    """
81    Bad request stanza error.
82    """
83    def __init__(self, mucCondition=None, text=None):
84        MUCError.__init__(self, 'bad-request', mucCondition, text)
85
86
87
88class Unsupported(MUCError):
89    def __init__(self, feature, text=None):
90        MUCError.__init__(self, 'feature-not-implemented',
91                          'unsupported',
92                          feature,
93                          text)
94
95
96
97class Room(object):
98    """
99    A Multi User Chat Room
100    """
101
102   
103    def __init__(self, name, server, nick, state=None):
104        """
105        """
106        self.state  = state
107        self.name   = name
108        self.server = server
109        self.nick   = nick
110       
111        self.entity_id = jid.internJID(name+'@'+server+'/'+nick)
112               
113        self.roster = {}
114
115       
116
117class BasicPresence(xmppim.Presence):
118    """
119    This behaves like an object providing L{domish.IElement}.
120
121    """
122
123    def __init__(self, to=None, type=None):
124        xmppim.Presence.__init__(self, to, type)
125       
126        # add muc elements
127        x = self.addElement('x', NS)
128
129
130class UserPresence(xmppim.Presence):
131    """
132    This behaves like an object providing L{domish.IElement}.
133
134    """
135
136    def __init__(self, to=None, type=None, frm=None, affiliation=None, role=None):
137        xmppim.Presence.__init__(self, to, type)
138        if frm:
139            self['from'] = frm
140        # add muc elements
141        x = self.addElement('x', NS_USER)
142        if affiliation:
143            x['affiliation'] = affiliation
144        if role:
145            x['role'] = role
146
147
148class PresenceError(BasicPresence):
149    """
150    This behaves like an object providing L{domish.IElement}.
151
152    """
153
154    def __init__(self, error, to=None, frm=None):
155        BasicPresence.__init__(self, to, type='error')
156        if frm:
157            self['from'] = frm
158        # add muc elements
159        x = self.addElement('x', NS)
160        # add error
161        self.addChild(error)
162       
163
164class MUCClient(XMPPHandler):
165    """
166    Multi-User chat client protocol.
167    """
168
169    implements(IMUCClient)
170
171
172    def connectionInitialized(self):
173        self.rooms = {}
174       
175
176    def _setRoom(self, room):
177        self.rooms[room.entity_id.full().lower()] = room
178
179    def _getRoom(self, room_jid):
180        return self.rooms.get(room_jid.full().lower())
181
182
183
184    def _joinedRoom(self, d, prs):
185        """We have presence that says we joined a room.
186        """
187        room_jid = jid.internJID(prs['from'])
188       
189        # check for errors
190        if prs.hasAttribute('type') and prs['type'] == 'error':           
191            d.errback(prs)
192        else:   
193            # change the state of the room
194            r = self._getRoom(room_jid)
195            r.state = 'joined'
196            d.callback(r)
197
198    def userPresence(self, prs):
199        """User Presence has been received
200        """
201        pass
202       
203
204    def _cbDisco(self, iq):
205        # grab query
206       
207        return iq.query
208       
209    def disco(self, entity, type='info'):
210        """Send disco queries to a XMPP entity
211        """
212
213        iq = disco.DiscoRequest(self.xmlstream, disco.NS_INFO, 'get')
214        iq['to'] = entity
215
216        return iq.send().addCallback(self._cbDisco)
217       
218
219    def join(self, server, room, nick):
220        """
221        """
222        d = defer.Deferred()
223        r = Room(room, server, nick, state='joining')
224        self._setRoom(r)
225 
226        p = BasicPresence(to=r.entity_id)
227        # p['from'] = self.jid.full()
228        self.xmlstream.send(p)
229
230        # add observer for joining the room
231        self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), 
232                                          self._joinedRoom, 1, d)
233
234        return d
235   
236
Note: See TracBrowser for help on using the repository browser.