[107] | 1 | # -*- test-case-name: wokkel.test.test_muc -*- |
---|
| 2 | # |
---|
| 3 | # Copyright (c) 2003-2008 Ralph Meijer |
---|
| 4 | # See LICENSE for details. |
---|
| 5 | |
---|
| 6 | """ |
---|
| 7 | XMPP Multi-User Chat protocol. |
---|
| 8 | |
---|
| 9 | This protocol is specified in |
---|
| 10 | U{XEP-0045<http://www.xmpp.org/extensions/xep-0045.html>}. |
---|
| 11 | """ |
---|
[110] | 12 | import datetime |
---|
[107] | 13 | |
---|
| 14 | from zope.interface import implements |
---|
| 15 | |
---|
[118] | 16 | from twisted.internet import defer, reactor |
---|
[107] | 17 | from twisted.words.protocols.jabber import jid, error, xmlstream |
---|
| 18 | from twisted.words.xish import domish |
---|
| 19 | |
---|
| 20 | from wokkel import disco, data_form, shim, xmppim |
---|
| 21 | from wokkel.subprotocols import IQHandlerMixin, XMPPHandler |
---|
[108] | 22 | from wokkel.iwokkel import IMUCClient |
---|
[107] | 23 | |
---|
| 24 | # Multi User Chat namespaces |
---|
[116] | 25 | NS_MUC = 'http://jabber.org/protocol/muc' |
---|
| 26 | NS_MUC_USER = NS_MUC + '#user' |
---|
| 27 | NS_MUC_ADMIN = NS_MUC + '#admin' |
---|
| 28 | NS_MUC_OWNER = NS_MUC + '#owner' |
---|
| 29 | NS_MUC_ROOMINFO = NS_MUC + '#roominfo' |
---|
| 30 | NS_MUC_CONFIG = NS_MUC + '#roomconfig' |
---|
| 31 | NS_MUC_REQUEST = NS_MUC + '#request' |
---|
| 32 | NS_MUC_REGISTER = NS_MUC + '#register' |
---|
[110] | 33 | |
---|
[116] | 34 | NS_DELAY = 'urn:xmpp:delay' |
---|
| 35 | NS_JABBER_DELAY = 'jabber:x:delay' |
---|
| 36 | |
---|
[111] | 37 | NS_REQUEST = 'jabber:iq:register' |
---|
[107] | 38 | |
---|
| 39 | # ad hoc commands |
---|
| 40 | NS_AD_HOC = "http://jabber.org/protocol/commands" |
---|
| 41 | |
---|
| 42 | |
---|
| 43 | # Iq get and set XPath queries |
---|
| 44 | IQ = '/iq' |
---|
| 45 | IQ_GET = IQ+'[@type="get"]' |
---|
| 46 | IQ_SET = IQ+'[@type="set"]' |
---|
| 47 | |
---|
| 48 | IQ_RESULT = IQ+'[@type="result"]' |
---|
| 49 | IQ_ERROR = IQ+'[@type="error"]' |
---|
| 50 | |
---|
| 51 | IQ_QUERY = IQ+'/query' |
---|
| 52 | IQ_GET_QUERY = IQ_GET + '/query' |
---|
| 53 | IQ_SET_QUERY = IQ_SET + '/query' |
---|
| 54 | |
---|
| 55 | IQ_COMMAND = IQ+'/command' |
---|
| 56 | |
---|
[116] | 57 | MUC_ADMIN = IQ_QUERY+'[@xmlns="' + NS_MUC_ADMIN + '"]' |
---|
| 58 | MUC_OWNER = IQ_QUERY+'[@xmlns="' + NS_MUC_OWNER + '"]' |
---|
[107] | 59 | |
---|
| 60 | MUC_AO = MUC_ADMIN + '|' + MUC_OWNER |
---|
| 61 | |
---|
| 62 | |
---|
| 63 | MESSAGE = '/message' |
---|
| 64 | PRESENCE = '/presence' |
---|
| 65 | |
---|
| 66 | CHAT_BODY = MESSAGE +'[@type="chat"]/body' |
---|
| 67 | CHAT = MESSAGE +'[@type="chat"]' |
---|
| 68 | |
---|
[110] | 69 | GROUPCHAT = MESSAGE +'[@type="groupchat"]/body' |
---|
[113] | 70 | SUBJECT = MESSAGE +'[@type="groupchat"]/subject' |
---|
[107] | 71 | MESSAGE_ERROR = MESSAGE +'[@type="error"]' |
---|
| 72 | |
---|
[110] | 73 | STATUS_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 | |
---|
| 87 | STATUS_CODE_CREATED = 201 |
---|
[107] | 88 | |
---|
[118] | 89 | DEFER_TIMEOUT = 30 # basic timeout is 30 seconds |
---|
[107] | 90 | |
---|
[120] | 91 | class JidMalformed(Exception): |
---|
| 92 | """ |
---|
| 93 | A jid malformed error from the server. |
---|
| 94 | |
---|
| 95 | |
---|
| 96 | """ |
---|
| 97 | condition = 'modify' |
---|
| 98 | mucCondition = 'jid-malformed' |
---|
| 99 | |
---|
| 100 | class NotAuthorized(Exception): |
---|
| 101 | """ |
---|
| 102 | """ |
---|
| 103 | condition = 'auth' |
---|
| 104 | mucCondition = 'not-authorized' |
---|
| 105 | |
---|
| 106 | class RegistrationRequired(Exception): |
---|
| 107 | """ |
---|
| 108 | """ |
---|
| 109 | condition = 'auth' |
---|
| 110 | mucCondition = 'registration-required' |
---|
| 111 | |
---|
| 112 | |
---|
| 113 | class Forbidden(Exception): |
---|
| 114 | """ |
---|
| 115 | """ |
---|
| 116 | condition = 'auth' |
---|
| 117 | mucCondition = 'forbidden' |
---|
| 118 | |
---|
[121] | 119 | class Conflict(Exception): |
---|
| 120 | """ |
---|
| 121 | """ |
---|
| 122 | condition = 'cancel' |
---|
| 123 | mucCondition = 'conflict' |
---|
| 124 | |
---|
| 125 | class NotFound(Exception): |
---|
| 126 | """ |
---|
| 127 | """ |
---|
| 128 | condition = 'cancel' |
---|
| 129 | mucCondition = 'not-found' |
---|
| 130 | |
---|
| 131 | class ServiceUnavailable(Exception): |
---|
| 132 | """ |
---|
| 133 | """ |
---|
| 134 | condition = 'wait' |
---|
| 135 | mucCondition = 'service-unavailable' |
---|
| 136 | |
---|
| 137 | |
---|
[120] | 138 | |
---|
| 139 | MUC_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] | 149 | class 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 | |
---|
| 163 | class 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 | |
---|
| 172 | class 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] | 181 | class 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] | 204 | class 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] | 227 | class 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 | |
---|
| 241 | class 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 | |
---|
| 256 | class 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] | 281 | class 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] | 294 | class 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 | |
---|
| 314 | class 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] | 328 | class 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] | 341 | class 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] | 351 | class 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). |
---|
[123] | 356 | @type maxchars: L{int} |
---|
[121] | 357 | |
---|
| 358 | @ivar maxstanzas: Limit the total number of messages in the history to "X". |
---|
[123] | 359 | @type mazstanzas: L{int} |
---|
[121] | 360 | |
---|
| 361 | @ivar seconds: Send only the messages received in the last "X" seconds. |
---|
[123] | 362 | @type seconds: L{int} |
---|
[121] | 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]). |
---|
[123] | 365 | @type since: L{datetime.datetime} |
---|
[121] | 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] | 388 | class 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] | 403 | class 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] | 450 | class 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 | |
---|
| 462 | class 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] | 479 | class 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] | 497 | class 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 | |
---|
| 506 | class 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] | 518 | class 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 | |
---|
| 536 | class MUCClient(XMPPHandler): |
---|
| 537 | """ |
---|
[123] | 538 | Multi-User chat client protocol. This is a subclass of L{XMPPHandler} and implements L{IMUCCLient}. |
---|
| 539 | |
---|
[107] | 540 | """ |
---|
| 541 | |
---|
| 542 | implements(IMUCClient) |
---|
| 543 | |
---|
[110] | 544 | rooms = {} |
---|
[107] | 545 | |
---|
[118] | 546 | timeout = None |
---|
| 547 | |
---|
| 548 | _deferreds = [] |
---|
| 549 | |
---|
[107] | 550 | def connectionInitialized(self): |
---|
[123] | 551 | """ This method is called when the client has successfully authenticated. |
---|
| 552 | It initializes several xpath events to handle MUC stanzas that come in. |
---|
| 553 | After those are initialized then the method initialized is called to signal that we have finished. |
---|
| 554 | """ |
---|
[113] | 555 | self.xmlstream.addObserver(PRESENCE+"[not(@type) or @type='available']/x", self._onXPresence) |
---|
| 556 | self.xmlstream.addObserver(PRESENCE+"[@type='unavailable']", self._onUnavailablePresence) |
---|
| 557 | self.xmlstream.addObserver(PRESENCE+"[@type='error']", self._onPresenceError) |
---|
[110] | 558 | self.xmlstream.addObserver(GROUPCHAT, self._onGroupChat) |
---|
| 559 | self.xmlstream.addObserver(SUBJECT, self._onSubject) |
---|
| 560 | # add history |
---|
[107] | 561 | |
---|
[115] | 562 | self.initialized() |
---|
[114] | 563 | |
---|
[107] | 564 | def _setRoom(self, room): |
---|
[123] | 565 | """Add a room to the room collection. |
---|
| 566 | """ |
---|
[114] | 567 | self.rooms[room.entity_id.userhost().lower()] = room |
---|
[107] | 568 | |
---|
| 569 | def _getRoom(self, room_jid): |
---|
[123] | 570 | """Grab a room from the room collection. |
---|
| 571 | """ |
---|
[114] | 572 | return self.rooms.get(room_jid.userhost().lower()) |
---|
[107] | 573 | |
---|
[111] | 574 | def _removeRoom(self, room_jid): |
---|
[123] | 575 | """Delete a room from the room collection. |
---|
| 576 | """ |
---|
[114] | 577 | if self.rooms.has_key(room_jid.userhost().lower()): |
---|
| 578 | del self.rooms[room_jid.userhost().lower()] |
---|
[107] | 579 | |
---|
[113] | 580 | |
---|
| 581 | def _onUnavailablePresence(self, prs): |
---|
[123] | 582 | """ This method is called when the stanza matches the xpath observer. |
---|
| 583 | The client has received a presence stanza with the 'type' attribute of unavailable. |
---|
| 584 | It means a user has exited a MUC room. |
---|
[113] | 585 | """ |
---|
[118] | 586 | |
---|
[113] | 587 | if not prs.hasAttribute('from'): |
---|
| 588 | return |
---|
| 589 | room_jid = jid.internJID(prs.getAttribute('from', '')) |
---|
[114] | 590 | self._userLeavesRoom(room_jid) |
---|
[113] | 591 | |
---|
| 592 | def _onPresenceError(self, prs): |
---|
[123] | 593 | """This method is called when a presence stanza with the 'type' attribute of error. |
---|
| 594 | There are various reasons for receiving a presence error and it means that the user has left the room. |
---|
[113] | 595 | """ |
---|
| 596 | if not prs.hasAttribute('from'): |
---|
| 597 | return |
---|
| 598 | room_jid = jid.internJID(prs.getAttribute('from', '')) |
---|
[114] | 599 | # add an error hook here? |
---|
| 600 | self._userLeavesRoom(room_jid) |
---|
[120] | 601 | |
---|
[121] | 602 | def _getExceptionFromElement(self, stanza): |
---|
[123] | 603 | # find an exception based on the error stanza |
---|
[120] | 604 | muc_condition = 'exception' |
---|
| 605 | |
---|
[121] | 606 | error = getattr(stanza, 'error', None) |
---|
[120] | 607 | if error is not None: |
---|
| 608 | for e in error.elements(): |
---|
| 609 | muc_condition = e.name |
---|
| 610 | |
---|
| 611 | return MUC_EXCEPTIONS[muc_condition] |
---|
[113] | 612 | |
---|
[114] | 613 | def _userLeavesRoom(self, room_jid): |
---|
[123] | 614 | # when a user leaves a room we need to update it |
---|
[114] | 615 | room = self._getRoom(room_jid) |
---|
| 616 | if room is None: |
---|
| 617 | # not in the room yet |
---|
| 618 | return |
---|
| 619 | # check if user is in roster |
---|
| 620 | user = room.getUser(room_jid.resource) |
---|
| 621 | if user is None: |
---|
| 622 | return |
---|
| 623 | if room.inRoster(user): |
---|
| 624 | room.removeUser(user) |
---|
| 625 | self.userLeftRoom(room, user) |
---|
| 626 | |
---|
[110] | 627 | def _onXPresence(self, prs): |
---|
[123] | 628 | """ A muc presence has been received. |
---|
[110] | 629 | """ |
---|
[113] | 630 | if not prs.hasAttribute('from'): |
---|
| 631 | return |
---|
| 632 | room_jid = jid.internJID(prs.getAttribute('from', '')) |
---|
| 633 | |
---|
| 634 | status = getattr(prs, 'status', None) |
---|
| 635 | show = getattr(prs, 'show', None) |
---|
| 636 | |
---|
| 637 | # grab room |
---|
| 638 | room = self._getRoom(room_jid) |
---|
| 639 | if room is None: |
---|
| 640 | # not in the room yet |
---|
| 641 | return |
---|
| 642 | |
---|
| 643 | # check if user is in roster |
---|
| 644 | user = room.getUser(room_jid.resource) |
---|
| 645 | if user is None: # create a user that does not exist |
---|
| 646 | user = User(room_jid.resource) |
---|
| 647 | |
---|
| 648 | |
---|
| 649 | if room.inRoster(user): |
---|
| 650 | # we changed status or nick |
---|
| 651 | muc_status = getattr(prs.x, 'status', None) |
---|
| 652 | if muc_status: |
---|
| 653 | code = muc_status.getAttribute('code', 0) |
---|
| 654 | else: |
---|
| 655 | self.userUpdatedStatus(room, user, show, status) |
---|
| 656 | else: |
---|
| 657 | room.addUser(user) |
---|
| 658 | self.userJoinedRoom(room, user) |
---|
[110] | 659 | |
---|
| 660 | |
---|
| 661 | def _onGroupChat(self, msg): |
---|
| 662 | """ |
---|
| 663 | """ |
---|
[113] | 664 | if not msg.hasAttribute('from'): |
---|
| 665 | # need to return an error here |
---|
| 666 | return |
---|
| 667 | room_jid = jid.internJID(msg.getAttribute('from', '')) |
---|
| 668 | |
---|
| 669 | room = self._getRoom(room_jid) |
---|
| 670 | if room is None: |
---|
| 671 | # not in the room yet |
---|
| 672 | return |
---|
| 673 | user = room.getUser(room_jid.resource) |
---|
[116] | 674 | delay = None |
---|
| 675 | # need to check for delay and x stanzas for delay namespace for backwards compatability |
---|
| 676 | for e in msg.elements(): |
---|
| 677 | if e.uri == NS_DELAY or e.uri == NS_JABBER_DELAY: |
---|
| 678 | delay = e |
---|
[113] | 679 | body = unicode(msg.body) |
---|
| 680 | # grab room |
---|
[112] | 681 | if delay is None: |
---|
[113] | 682 | self.receivedGroupChat(room, user, body) |
---|
[112] | 683 | else: |
---|
[113] | 684 | self.receivedHistory(room, user, body, delay['stamp'], frm=delay.getAttribute('from',None)) |
---|
[110] | 685 | |
---|
| 686 | |
---|
| 687 | def _onSubject(self, msg): |
---|
| 688 | """ |
---|
| 689 | """ |
---|
[113] | 690 | if not msg.hasAttribute('from'): |
---|
| 691 | return |
---|
| 692 | room_jid = jid.internJID(msg['from']) |
---|
| 693 | |
---|
| 694 | # grab room |
---|
| 695 | room = self._getRoom(room_jid) |
---|
| 696 | if room is None: |
---|
| 697 | # not in the room yet |
---|
| 698 | return |
---|
| 699 | |
---|
| 700 | self.receivedSubject(room_jid, unicode(msg.subject)) |
---|
[110] | 701 | |
---|
| 702 | |
---|
| 703 | def _makeTimeStamp(self, stamp=None): |
---|
| 704 | if stamp is None: |
---|
| 705 | stamp = datetime.datetime.now() |
---|
| 706 | |
---|
| 707 | return stamp.strftime('%Y%m%dT%H:%M:%S') |
---|
| 708 | |
---|
[107] | 709 | |
---|
| 710 | def _joinedRoom(self, d, prs): |
---|
| 711 | """We have presence that says we joined a room. |
---|
| 712 | """ |
---|
| 713 | room_jid = jid.internJID(prs['from']) |
---|
[108] | 714 | |
---|
[107] | 715 | # check for errors |
---|
| 716 | if prs.hasAttribute('type') and prs['type'] == 'error': |
---|
[121] | 717 | d.errback(self._getExceptionFromElement(prs)) |
---|
[108] | 718 | else: |
---|
| 719 | # change the state of the room |
---|
| 720 | r = self._getRoom(room_jid) |
---|
[110] | 721 | if r is None: |
---|
[121] | 722 | raise NotFound |
---|
[108] | 723 | r.state = 'joined' |
---|
[110] | 724 | |
---|
| 725 | # grab status |
---|
| 726 | status = getattr(prs.x,'status',None) |
---|
| 727 | if status: |
---|
| 728 | r.status = status.getAttribute('code', None) |
---|
| 729 | |
---|
[108] | 730 | d.callback(r) |
---|
[107] | 731 | |
---|
[111] | 732 | |
---|
| 733 | def _leftRoom(self, d, prs): |
---|
| 734 | """We have presence that says we joined a room. |
---|
| 735 | """ |
---|
| 736 | room_jid = jid.internJID(prs['from']) |
---|
| 737 | |
---|
| 738 | # check for errors |
---|
| 739 | if prs.hasAttribute('type') and prs['type'] == 'error': |
---|
[121] | 740 | d.errback(self._getExceptionFromElement(prs)) |
---|
[111] | 741 | else: |
---|
| 742 | # change the state of the room |
---|
| 743 | r = self._getRoom(room_jid) |
---|
| 744 | if r is None: |
---|
[121] | 745 | raise NotFound |
---|
[111] | 746 | self._removeRoom(room_jid) |
---|
| 747 | |
---|
| 748 | d.callback(True) |
---|
| 749 | |
---|
[115] | 750 | def initialized(self): |
---|
| 751 | """Client is initialized and ready! |
---|
| 752 | """ |
---|
| 753 | pass |
---|
| 754 | |
---|
[113] | 755 | def userJoinedRoom(self, room, user): |
---|
| 756 | """User has joined a room |
---|
| 757 | """ |
---|
| 758 | pass |
---|
| 759 | |
---|
[114] | 760 | def userLeftRoom(self, room, user): |
---|
| 761 | """User has left a room |
---|
| 762 | """ |
---|
| 763 | pass |
---|
| 764 | |
---|
[113] | 765 | |
---|
[115] | 766 | def userUpdatedStatus(self, room, user, show, status): |
---|
[107] | 767 | """User Presence has been received |
---|
| 768 | """ |
---|
| 769 | pass |
---|
| 770 | |
---|
[108] | 771 | |
---|
[113] | 772 | def receivedSubject(self, room, subject): |
---|
[110] | 773 | """ |
---|
| 774 | """ |
---|
| 775 | pass |
---|
| 776 | |
---|
[112] | 777 | |
---|
[113] | 778 | def receivedHistory(self, room, user, message, history, frm=None): |
---|
[112] | 779 | """ |
---|
| 780 | """ |
---|
| 781 | pass |
---|
| 782 | |
---|
| 783 | |
---|
[108] | 784 | def _cbDisco(self, iq): |
---|
| 785 | # grab query |
---|
| 786 | |
---|
[110] | 787 | return getattr(iq,'query', None) |
---|
[108] | 788 | |
---|
[118] | 789 | |
---|
| 790 | def sendDeferred(self, obj, timeout): |
---|
| 791 | """ Send data or a domish element, adding a deferred with a timeout. |
---|
| 792 | """ |
---|
| 793 | d = defer.Deferred() |
---|
| 794 | self._deferreds.append(d) |
---|
| 795 | |
---|
| 796 | |
---|
| 797 | def onTimeout(): |
---|
| 798 | i = 0 |
---|
| 799 | for xd in self._deferreds: |
---|
| 800 | if d == xd: |
---|
| 801 | self._deferreds.pop(i) |
---|
| 802 | d.errback(xmlstream.TimeoutError("Timeout waiting for response.")) |
---|
| 803 | i += 1 |
---|
| 804 | |
---|
| 805 | call = reactor.callLater(timeout, onTimeout) |
---|
| 806 | |
---|
| 807 | def cancelTimeout(result): |
---|
| 808 | if call.active(): |
---|
| 809 | call.cancel() |
---|
| 810 | |
---|
| 811 | return result |
---|
| 812 | |
---|
| 813 | d.addBoth(cancelTimeout) |
---|
| 814 | |
---|
| 815 | self.xmlstream.send(obj) |
---|
| 816 | return d |
---|
| 817 | |
---|
[108] | 818 | def disco(self, entity, type='info'): |
---|
| 819 | """Send disco queries to a XMPP entity |
---|
| 820 | """ |
---|
| 821 | |
---|
| 822 | iq = disco.DiscoRequest(self.xmlstream, disco.NS_INFO, 'get') |
---|
| 823 | iq['to'] = entity |
---|
| 824 | |
---|
[110] | 825 | return iq.send().addBoth(self._cbDisco) |
---|
[108] | 826 | |
---|
| 827 | |
---|
[111] | 828 | def configure(self, room_jid, fields=[]): |
---|
[110] | 829 | """Configure a room |
---|
[117] | 830 | |
---|
| 831 | @param room_jid: The room jabber/xmpp entity id for the requested configuration form. |
---|
| 832 | @type room_jid: L{jid.JID} |
---|
| 833 | |
---|
[110] | 834 | """ |
---|
[111] | 835 | request = ConfigureRequest(self.xmlstream, method='set', fields=fields) |
---|
[110] | 836 | request['to'] = room_jid |
---|
| 837 | |
---|
| 838 | return request.send() |
---|
| 839 | |
---|
| 840 | def getConfigureForm(self, room_jid): |
---|
[117] | 841 | """Grab the configuration form from the room. This sends an iq request to the room. |
---|
| 842 | |
---|
| 843 | @param room_jid: The room jabber/xmpp entity id for the requested configuration form. |
---|
| 844 | @type room_jid: L{jid.JID} |
---|
| 845 | |
---|
| 846 | """ |
---|
[110] | 847 | request = ConfigureRequest(self.xmlstream) |
---|
| 848 | request['to'] = room_jid |
---|
| 849 | return request.send() |
---|
| 850 | |
---|
| 851 | |
---|
[121] | 852 | def join(self, server, room, nick, history = None): |
---|
[117] | 853 | """ Join a MUC room by sending presence to it. Returns a defered that is called when |
---|
| 854 | the entity is in the room or an error has occurred. |
---|
| 855 | |
---|
| 856 | @param server: The server where the room is located. |
---|
| 857 | @type server: L{unicode} |
---|
| 858 | |
---|
| 859 | @param room: The room name the entity is joining. |
---|
| 860 | @type room: L{unicode} |
---|
| 861 | |
---|
| 862 | @param nick: The nick name for the entitity joining the room. |
---|
| 863 | @type nick: L{unicode} |
---|
| 864 | |
---|
[121] | 865 | @param history: The maximum number of history stanzas you would like. |
---|
| 866 | |
---|
[107] | 867 | """ |
---|
| 868 | r = Room(room, server, nick, state='joining') |
---|
| 869 | self._setRoom(r) |
---|
| 870 | |
---|
| 871 | p = BasicPresence(to=r.entity_id) |
---|
[121] | 872 | if history is not None: |
---|
| 873 | p.x.addChild(history.toElement()) |
---|
| 874 | |
---|
[118] | 875 | d = self.sendDeferred(p, timeout=DEFER_TIMEOUT) |
---|
[107] | 876 | |
---|
| 877 | # add observer for joining the room |
---|
| 878 | self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), |
---|
[108] | 879 | self._joinedRoom, 1, d) |
---|
[107] | 880 | |
---|
| 881 | return d |
---|
| 882 | |
---|
[119] | 883 | def _changed(self, d, room_jid, prs): |
---|
| 884 | """Callback for changing the nick and status. |
---|
[117] | 885 | """ |
---|
| 886 | |
---|
| 887 | r = self._getRoom(room_jid) |
---|
| 888 | |
---|
| 889 | d.callback(r) |
---|
| 890 | |
---|
| 891 | |
---|
| 892 | def nick(self, room_jid, new_nick): |
---|
| 893 | """ Change an entities nick name in a MUC room. |
---|
| 894 | |
---|
| 895 | See: http://xmpp.org/extensions/xep-0045.html#changenick |
---|
| 896 | |
---|
| 897 | @param room_jid: The room jabber/xmpp entity id for the requested configuration form. |
---|
| 898 | @type room_jid: L{jid.JID} |
---|
| 899 | |
---|
| 900 | @param new_nick: The nick name for the entitity joining the room. |
---|
| 901 | @type new_nick: L{unicode} |
---|
| 902 | |
---|
| 903 | """ |
---|
| 904 | |
---|
[118] | 905 | |
---|
[117] | 906 | r = self._getRoom(room_jid) |
---|
| 907 | if r is None: |
---|
[121] | 908 | raise NotFound |
---|
[117] | 909 | r.nick = new_nick # change the nick |
---|
| 910 | # create presence |
---|
| 911 | # make sure we call the method to generate the new entity xmpp id |
---|
| 912 | p = BasicPresence(to=r.entityId()) |
---|
[118] | 913 | d = self.sendDeferred(p, timeout=DEFER_TIMEOUT) |
---|
[117] | 914 | |
---|
| 915 | # add observer for joining the room |
---|
| 916 | self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), |
---|
[119] | 917 | self._changed, 1, d, room_jid) |
---|
[117] | 918 | |
---|
| 919 | return d |
---|
| 920 | |
---|
[107] | 921 | |
---|
[111] | 922 | |
---|
| 923 | def leave(self, room_jid): |
---|
[123] | 924 | """Leave a MUC room. |
---|
| 925 | |
---|
| 926 | See: http://xmpp.org/extensions/xep-0045.html#exit |
---|
| 927 | |
---|
| 928 | @param room_jid: The room entity id you want to exit. |
---|
| 929 | @type room_jid: L{jid.JID} |
---|
| 930 | |
---|
[111] | 931 | """ |
---|
[112] | 932 | r = self._getRoom(room_jid) |
---|
[111] | 933 | |
---|
| 934 | p = xmppim.UnavailablePresence(to=r.entity_id) |
---|
| 935 | |
---|
[118] | 936 | d = self.sendDeferred(p, timeout=DEFER_TIMEOUT) |
---|
[111] | 937 | # add observer for joining the room |
---|
[112] | 938 | self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s' and @type='unavailable']" % (r.entity_id.full()), |
---|
[111] | 939 | self._leftRoom, 1, d) |
---|
| 940 | |
---|
| 941 | return d |
---|
| 942 | |
---|
| 943 | |
---|
[119] | 944 | def status(self, room_jid, show=None, status=None): |
---|
| 945 | """Change user status. |
---|
| 946 | |
---|
| 947 | See: http://xmpp.org/extensions/xep-0045.html#changepres |
---|
| 948 | |
---|
| 949 | @param room_jid: The room jabber/xmpp entity id for the requested configuration form. |
---|
| 950 | @type room_jid: L{jid.JID} |
---|
| 951 | |
---|
| 952 | @param show: The availability of the entity. Common values are xa, available, etc |
---|
| 953 | @type show: L{unicode} |
---|
| 954 | |
---|
| 955 | @param show: The current status of the entity. |
---|
| 956 | @type show: L{unicode} |
---|
| 957 | |
---|
| 958 | """ |
---|
| 959 | r = self._getRoom(room_jid) |
---|
| 960 | if r is None: |
---|
[121] | 961 | raise NotFound |
---|
[119] | 962 | |
---|
| 963 | p = BasicPresence(to=r.entityId()) |
---|
| 964 | if status is not None: |
---|
| 965 | p.addElement('status', None, status) |
---|
| 966 | |
---|
| 967 | if show is not None: |
---|
| 968 | p.addElement('show', None, show) |
---|
| 969 | |
---|
| 970 | d = self.sendDeferred(p, timeout=DEFER_TIMEOUT) |
---|
| 971 | |
---|
| 972 | # add observer for joining the room |
---|
| 973 | self.xmlstream.addOnetimeObserver(PRESENCE+"[@from='%s']" % (r.entity_id.full()), |
---|
| 974 | self._changed, 1, d, room_jid) |
---|
| 975 | |
---|
| 976 | return d |
---|
[111] | 977 | |
---|
[112] | 978 | def _sendMessage(self, msg, children=None): |
---|
[110] | 979 | |
---|
| 980 | if children: |
---|
| 981 | for c in children: |
---|
| 982 | msg.addChild(c) |
---|
| 983 | |
---|
| 984 | self.xmlstream.send(msg) |
---|
| 985 | |
---|
[112] | 986 | def groupChat(self, to, message, children=None): |
---|
| 987 | """Send a groupchat message |
---|
| 988 | """ |
---|
| 989 | msg = GroupChat(to, body=message) |
---|
| 990 | |
---|
| 991 | self._sendMessage(msg, children=children) |
---|
| 992 | |
---|
| 993 | def chat(self, to, message, children=None): |
---|
| 994 | msg = PrivateChat(to, body=message) |
---|
| 995 | |
---|
| 996 | self._sendMessage(msg, children=children) |
---|
| 997 | |
---|
| 998 | def invite(self, to, reason=None, full_jid=None): |
---|
[113] | 999 | """ |
---|
| 1000 | """ |
---|
[112] | 1001 | msg = InviteMessage(to, reason=reason, full_jid=full_jid) |
---|
| 1002 | self._sendMessage(msg) |
---|
| 1003 | |
---|
| 1004 | |
---|
| 1005 | def password(self, to, password): |
---|
| 1006 | p = PasswordPresence(to, password) |
---|
| 1007 | |
---|
| 1008 | self.xmlstream.send(p) |
---|
[110] | 1009 | |
---|
[111] | 1010 | def register(self, to, fields=[]): |
---|
| 1011 | iq = RegisterRequest(self.xmlstream, method='set', fields=fields) |
---|
| 1012 | iq['to'] = to |
---|
| 1013 | return iq.send() |
---|
| 1014 | |
---|
[120] | 1015 | |
---|
| 1016 | def _getAffiliationList(self, room_jid, affiliation): |
---|
| 1017 | iq = AffiliationRequest(self.xmlstream, |
---|
| 1018 | method='get', |
---|
| 1019 | affiliation=affiliation, |
---|
| 1020 | ) |
---|
| 1021 | iq['to'] = room_jid.full() |
---|
| 1022 | return iq.send() |
---|
| 1023 | |
---|
| 1024 | |
---|
[123] | 1025 | def _getRoleList(self, room_jid, role): |
---|
| 1026 | iq = RoleRequest(self.xmlstream, |
---|
| 1027 | method='get', |
---|
| 1028 | role=role, |
---|
| 1029 | ) |
---|
| 1030 | iq['to'] = room_jid.full() |
---|
| 1031 | return iq.send() |
---|
| 1032 | |
---|
| 1033 | |
---|
| 1034 | def self._setAffiliationList(self, affiliation, room_jid, iq): |
---|
| 1035 | r = self._getRoom(room_jid) |
---|
| 1036 | if r is not None: |
---|
| 1037 | affiliation_list = [] |
---|
| 1038 | setattr(r, affiliation, []) |
---|
| 1039 | |
---|
| 1040 | for item in iq.query.elements(): |
---|
| 1041 | nick = item.getAttribute('nick', None) |
---|
| 1042 | entity = item.getAttribute('jid', None) |
---|
| 1043 | u = None |
---|
| 1044 | if nick is None and entity is None: |
---|
| 1045 | raise Exception, 'bad attributes in item list' |
---|
| 1046 | if nick is not None: |
---|
| 1047 | u = room.getUser(nick) |
---|
| 1048 | if u is None: |
---|
| 1049 | u = User(nick, user_jid=jid.internJID(entity)) |
---|
| 1050 | u.affiliation = 'member' |
---|
| 1051 | |
---|
| 1052 | affiliation_list.append(u) |
---|
| 1053 | |
---|
| 1054 | setattr(r, affiliation, affiliation_list) |
---|
| 1055 | return r |
---|
[120] | 1056 | |
---|
[119] | 1057 | def getMemberList(self, room_jid): |
---|
| 1058 | """ Get a member list from a room. |
---|
| 1059 | |
---|
| 1060 | @param room_jid: The room jabber/xmpp entity id for the requested member list. |
---|
| 1061 | @type room_jid: L{jid.JID} |
---|
| 1062 | |
---|
| 1063 | """ |
---|
[123] | 1064 | d = self._getAffiliationList(room_jid, 'member') |
---|
| 1065 | d.addCallback(self._setAffiliationList, 'members', room_jid) |
---|
| 1066 | return d |
---|
[120] | 1067 | |
---|
| 1068 | def getAdminList(self, room_jid): |
---|
| 1069 | """ Get an admin list from a room. |
---|
| 1070 | |
---|
| 1071 | @param room_jid: The room jabber/xmpp entity id for the requested member list. |
---|
| 1072 | @type room_jid: L{jid.JID} |
---|
| 1073 | |
---|
| 1074 | """ |
---|
[123] | 1075 | d = self._getAffiliationList(room_jid, 'admin') |
---|
| 1076 | d.addCallback(self._setAffiliationList, 'members', room_jid) |
---|
| 1077 | return d |
---|
[120] | 1078 | |
---|
| 1079 | def getBanList(self, room_jid): |
---|
| 1080 | """ Get an outcast list from a room. |
---|
| 1081 | |
---|
| 1082 | @param room_jid: The room jabber/xmpp entity id for the requested member list. |
---|
| 1083 | @type room_jid: L{jid.JID} |
---|
| 1084 | |
---|
| 1085 | """ |
---|
[123] | 1086 | d = self._getAffiliationList(room_jid, 'outcast') |
---|
| 1087 | d.addCallback(self._setAffiliationList, 'members', room_jid) |
---|
| 1088 | return d |
---|
[120] | 1089 | |
---|
| 1090 | def getOwnerList(self, room_jid): |
---|
| 1091 | """ Get an owner list from a room. |
---|
| 1092 | |
---|
| 1093 | @param room_jid: The room jabber/xmpp entity id for the requested member list. |
---|
| 1094 | @type room_jid: L{jid.JID} |
---|
| 1095 | |
---|
| 1096 | """ |
---|
[123] | 1097 | d = self._getAffiliationList(room_jid, 'owner') |
---|
| 1098 | d.addCallback(self._setAffiliationList, 'members', room_jid) |
---|
| 1099 | return d |
---|
[119] | 1100 | |
---|
[113] | 1101 | def getRegisterForm(self, room): |
---|
| 1102 | """ |
---|
[119] | 1103 | |
---|
| 1104 | @param room: The room jabber/xmpp entity id for the requested registration form. |
---|
| 1105 | @type room: L{jid.JID} |
---|
| 1106 | |
---|
[113] | 1107 | """ |
---|
[111] | 1108 | iq = RegisterRequest(self.xmlstream) |
---|
[113] | 1109 | iq['to'] = room.userhost() |
---|
[111] | 1110 | return iq.send() |
---|
| 1111 | |
---|
[120] | 1112 | def destroy(self, room_jid, reason=None): |
---|
| 1113 | """ Destroy a room. |
---|
| 1114 | |
---|
| 1115 | @param room_jid: The room jabber/xmpp entity id. |
---|
| 1116 | @type room_jid: L{jid.JID} |
---|
| 1117 | |
---|
| 1118 | """ |
---|
| 1119 | def destroyed(iq): |
---|
| 1120 | self._removeRoom(room_jid) |
---|
| 1121 | return True |
---|
| 1122 | |
---|
| 1123 | iq = OwnerRequest(self.xmlstream, method='set') |
---|
| 1124 | d = iq.query.addElement('destroy') |
---|
| 1125 | d['jid'] = room_jid.userhost() |
---|
| 1126 | if reason is not None: |
---|
| 1127 | d.addElement('reason', None, reason) |
---|
| 1128 | |
---|
| 1129 | return iq.send().addCallback(destroyed) |
---|
| 1130 | |
---|
[110] | 1131 | def subject(self, to, subject): |
---|
| 1132 | """ |
---|
| 1133 | """ |
---|
| 1134 | msg = GroupChat(to, subject=subject) |
---|
| 1135 | self.xmlstream.send(msg) |
---|
| 1136 | |
---|
| 1137 | def voice(self, to): |
---|
| 1138 | """ |
---|
| 1139 | """ |
---|
| 1140 | msg = MessageVoice(to=to) |
---|
| 1141 | self.xmlstream.send(msg) |
---|
| 1142 | |
---|
| 1143 | |
---|
[122] | 1144 | |
---|
[110] | 1145 | def history(self, to, message_list): |
---|
| 1146 | """ |
---|
| 1147 | """ |
---|
| 1148 | |
---|
| 1149 | for m in message_list: |
---|
| 1150 | m['type'] = 'groupchat' |
---|
| 1151 | mto = m['to'] |
---|
| 1152 | frm = m.getAttribute('from', None) |
---|
| 1153 | m['to'] = to |
---|
| 1154 | |
---|
| 1155 | d = m.addElement('delay', NS_DELAY) |
---|
| 1156 | d['stamp'] = self._makeTimeStamp() |
---|
| 1157 | d['from'] = mto |
---|
| 1158 | |
---|
| 1159 | self.xmlstream.send(m) |
---|
| 1160 | |
---|
[122] | 1161 | def _setAffiliation(self, frm, room_jid, affiliation, reason=None, a_jid=None): |
---|
[112] | 1162 | iq = AffiliationRequest(self.xmlstream, |
---|
| 1163 | method='set', |
---|
[122] | 1164 | affiliation=affiliation, |
---|
| 1165 | a_jid=a_jid, |
---|
[112] | 1166 | reason=reason) |
---|
[122] | 1167 | iq['to'] = room_jid.userhost() # this is a room jid, only send to room |
---|
[112] | 1168 | iq['from'] = frm.full() |
---|
| 1169 | return iq.send() |
---|
| 1170 | |
---|
[122] | 1171 | def _setRole(self, frm, room_jid, a_jid=None, reason=None, role='none'): |
---|
| 1172 | iq = RoleRequest(self.xmlstream, |
---|
| 1173 | method='set', |
---|
| 1174 | a_jid=a_jid, |
---|
| 1175 | role=role, |
---|
| 1176 | reason=reason) |
---|
| 1177 | iq['to'] = room_jid.userhost() # this is a room jid, only send to room |
---|
[112] | 1178 | iq['from'] = frm.full() |
---|
| 1179 | return iq.send() |
---|
[122] | 1180 | |
---|
[123] | 1181 | def _cbRequest(self, room_jid, iq): |
---|
| 1182 | r = self._getRoom(room_jid) |
---|
| 1183 | if r is None: |
---|
| 1184 | raise NotFound |
---|
| 1185 | |
---|
| 1186 | return r |
---|
| 1187 | |
---|
[122] | 1188 | def grantVoice(self, frm, room_jid, reason=None): |
---|
[123] | 1189 | return self._setRole(frm, room_jid, role='participant', reason=reason) |
---|
[122] | 1190 | |
---|
[123] | 1191 | def grantVisitor(self, frm, room_jid, reason=None): |
---|
| 1192 | return self._setRole(frm, room_jid, role='visitor', reason=reason) |
---|
| 1193 | |
---|
| 1194 | def grantModerator(self, frm, room_jid, reason=None): |
---|
| 1195 | return self._setRole(frm, room_jid, role='moderator', reason=reason) |
---|
[122] | 1196 | |
---|
| 1197 | def ban(self, to, ban_jid, frm, reason=None): |
---|
| 1198 | return self._setAffiliation(frm, to, 'outcast', a_jid=ban_jid, reason=reason) |
---|
| 1199 | |
---|
| 1200 | def kick(self, to, kick_jid, frm, reason=None): |
---|
| 1201 | return self._setAffiliation(frm, to, 'none', a_jid=kick_jid, reason=reason) |
---|
| 1202 | |
---|