Mercurial > libervia-backend
comparison src/plugins/plugin_xep_0045.py @ 1963:a2bc5089c2eb
backend, frontends: message refactoring (huge commit):
/!\ several features are temporarily disabled, like notifications in frontends
next step in refactoring, with the following changes:
- jp: updated jp message to follow changes in backend/bridge
- jp: added --lang, --subject, --subject_lang, and --type options to jp message + fixed unicode handling for jid
- quick_frontend (QuickApp, QuickChat):
- follow backend changes
- refactored chat, message are now handled in OrderedDict and uid are kept so they can be updated
- Message and Occupant classes handle metadata, so frontend just have to display them
- Primitivus (Chat):
- follow backend/QuickFrontend changes
- info & standard messages are handled in the same MessageWidget class
- improved/simplified handling of messages, removed update() method
- user joined/left messages are merged when next to each other
- a separator is shown when message is received while widget is out of focus, so user can quickly see the new messages
- affiliation/role are shown (in a basic way for now) in occupants panel
- removed "/me" messages handling, as it will be done by a backend plugin
- message language is displayed when available (only one language per message for now)
- fixed :history and :search commands
- core (constants): new constants for messages type, XML namespace, entity type
- core: *Message methods renamed to follow new code sytle (e.g. sendMessageToBridge => messageSendToBridge)
- core (messages handling): fixed handling of language
- core (messages handling): mes_data['from'] and ['to'] are now jid.JID
- core (core.xmpp): reorganised message methods, added getNick() method to client.roster
- plugin text commands: fixed plugin and adapted to new messages behaviour. client is now used in arguments instead of profile
- plugins: added information for cancellation reason in CancelError calls
- plugin XEP-0045: various improvments, but this plugin still need work:
- trigger is used to avoid message already handled by the plugin to be handled a second time
- changed the way to handle history, the last message from DB is checked and we request only messages since this one, in seconds (thanks Poezio folks :))
- subject reception is waited before sending the roomJoined signal, this way we are sure that everything including history is ready
- cmd_* method now follow the new convention with client instead of profile
- roomUserJoined and roomUserLeft messages are removed, the events are now handled with info message with a "ROOM_USER_JOINED" info subtype
- probably other forgotten stuffs :p
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 20 Jun 2016 18:41:53 +0200 |
parents | 2daf7b4c6756 |
children | 200cd707a46d |
comparison
equal
deleted
inserted
replaced
1962:a45235d8dc93 | 1963:a2bc5089c2eb |
---|---|
21 from sat.core.constants import Const as C | 21 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 log = getLogger(__name__) | 23 log = getLogger(__name__) |
24 from twisted.internet import defer | 24 from twisted.internet import defer |
25 from twisted.words.protocols.jabber import jid | 25 from twisted.words.protocols.jabber import jid |
26 from dateutil.tz import tzutc | |
26 | 27 |
27 from sat.core import exceptions | 28 from sat.core import exceptions |
28 from sat.memory import memory | 29 from sat.memory import memory |
29 | 30 |
31 import calendar | |
32 import time | |
30 import uuid | 33 import uuid |
31 import copy | 34 import copy |
32 | 35 |
33 from wokkel import muc, disco, iwokkel | 36 from wokkel import muc, disco, iwokkel |
34 from sat.tools import xml_tools | 37 from sat.tools import xml_tools |
48 "description": _("""Implementation of Multi-User Chat""") | 51 "description": _("""Implementation of Multi-User Chat""") |
49 } | 52 } |
50 | 53 |
51 NS_MUC = 'http://jabber.org/protocol/muc' | 54 NS_MUC = 'http://jabber.org/protocol/muc' |
52 AFFILIATIONS = ('owner', 'admin', 'member', 'none', 'outcast') | 55 AFFILIATIONS = ('owner', 'admin', 'member', 'none', 'outcast') |
56 ROOM_USER_JOINED = 'ROOM_USER_JOINED' | |
57 ROOM_USER_LEFT = 'ROOM_USER_LEFT' | |
58 OCCUPANT_KEYS = ('nick', 'entity', 'affiliation', 'role') | |
59 ENTITY_TYPE_MUC = "MUC" | |
53 | 60 |
54 CONFIG_SECTION = u'plugin muc' | 61 CONFIG_SECTION = u'plugin muc' |
55 | 62 |
56 default_conf = {"default_muc": u'sat@chat.jabberfr.org'} | 63 default_conf = {"default_muc": u'sat@chat.jabberfr.org'} |
57 | 64 |
71 # TODO: this plugin is messy, need a big cleanup/refactoring | 78 # TODO: this plugin is messy, need a big cleanup/refactoring |
72 | 79 |
73 def __init__(self, host): | 80 def __init__(self, host): |
74 log.info(_("Plugin XEP_0045 initialization")) | 81 log.info(_("Plugin XEP_0045 initialization")) |
75 self.host = host | 82 self.host = host |
76 self.clients = {} | 83 self.clients = {} # FIXME: should be moved to profile's client |
77 self._sessions = memory.Sessions() | 84 self._sessions = memory.Sessions() |
78 host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join, async=True) | 85 host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join, async=True) |
79 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self.mucNick) | 86 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self.mucNick) |
80 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self.mucLeave, async=True) | 87 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self.mucLeave, async=True) |
81 host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sass)', method=self.getRoomsJoined) | 88 host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self.getRoomsJoined) |
82 host.bridge.addMethod("getRoomsSubjects", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjects) | 89 host.bridge.addMethod("getRoomsSubjects", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjects) |
83 host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName) | 90 host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName) |
84 host.bridge.addMethod("configureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async=True) | 91 host.bridge.addMethod("configureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async=True) |
85 host.bridge.addMethod("getDefaultMUC", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC) | 92 host.bridge.addMethod("getDefaultMUC", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC) |
86 host.bridge.addSignal("roomJoined", ".plugin", signature='sasss') # args: room_jid, room_nicks, user_nick, profile | 93 host.bridge.addSignal("roomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile |
87 host.bridge.addSignal("roomLeft", ".plugin", signature='ss') # args: room_jid, profile | 94 host.bridge.addSignal("roomLeft", ".plugin", signature='ss') # args: room_jid, profile |
88 host.bridge.addSignal("roomUserJoined", ".plugin", signature='ssa{ss}s') # args: room_jid, user_nick, user_data, profile | |
89 host.bridge.addSignal("roomUserLeft", ".plugin", signature='ssa{ss}s') # args: room_jid, user_nick, user_data, profile | |
90 host.bridge.addSignal("roomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile | 95 host.bridge.addSignal("roomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile |
91 host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile | 96 host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile |
92 self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True) | 97 self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True) |
93 host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) | 98 host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) |
94 try: | 99 try: |
96 self.host.plugins[C.TEXT_CMDS].addWhoIsCb(self._whois, 100) | 101 self.host.plugins[C.TEXT_CMDS].addWhoIsCb(self._whois, 100) |
97 except KeyError: | 102 except KeyError: |
98 log.info(_("Text commands not available")) | 103 log.info(_("Text commands not available")) |
99 | 104 |
100 host.trigger.add("presence_available", self.presenceTrigger) | 105 host.trigger.add("presence_available", self.presenceTrigger) |
106 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=1000000) | |
101 | 107 |
102 def profileConnected(self, profile): | 108 def profileConnected(self, profile): |
103 def assign_service(service): | 109 def assign_service(service): |
104 client = self.host.getClient(profile) | 110 client = self.host.getClient(profile) |
105 client.muc_service = service | 111 client.muc_service = service |
106 return self.getMUCService(profile=profile).addCallback(assign_service) | 112 return self.getMUCService(profile=profile).addCallback(assign_service) |
113 | |
114 def MessageReceivedTrigger(self, client, message_elt, post_treat): | |
115 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | |
116 if message_elt.subject or message_elt.delay: | |
117 return False | |
118 from_jid = jid.JID(message_elt['from']) | |
119 room_jid = from_jid.userhostJID() | |
120 if room_jid in self.clients[client.profile].joined_rooms: | |
121 room = self.clients[client.profile].joined_rooms[room_jid] | |
122 if not room._room_ok: | |
123 log.warning(u"Received non delayed message in a room before its initialisation: {}".format(message_elt.toXml())) | |
124 room._cache.append(message_elt) | |
125 return False | |
126 else: | |
127 log.warning(u"Received groupchat message for a room which has not been joined, ignoring it: {}".format(message_elt.toXml())) | |
128 return False | |
129 return True | |
107 | 130 |
108 def checkClient(self, profile): | 131 def checkClient(self, profile): |
109 """Check if the profile is connected and has used the MUC feature. | 132 """Check if the profile is connected and has used the MUC feature. |
110 | 133 |
111 If profile was using MUC feature but is now disconnected, remove it from the client list. | 134 If profile was using MUC feature but is now disconnected, remove it from the client list. |
130 raise exceptions.ProfileUnknownError("Unknown or disconnected profile") | 153 raise exceptions.ProfileUnknownError("Unknown or disconnected profile") |
131 if room_jid not in self.clients[profile].joined_rooms: | 154 if room_jid not in self.clients[profile].joined_rooms: |
132 raise UnknownRoom("This room has not been joined") | 155 raise UnknownRoom("This room has not been joined") |
133 return profile | 156 return profile |
134 | 157 |
135 def __room_joined(self, room, profile): | 158 def _joinCb(self, room, profile): |
136 """Called when the user is in the requested room""" | 159 """Called when the user is in the requested room""" |
137 | |
138 def _sendBridgeSignal(ignore=None): | |
139 self.host.bridge.roomJoined(room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick, profile) | |
140 | |
141 self.clients[profile].joined_rooms[room.roomJID] = room | |
142 if room.locked: | 160 if room.locked: |
143 # FIXME: the current behaviour is to create an instant room | 161 # FIXME: the current behaviour is to create an instant room |
144 # and send the signal only when the room is unlocked | 162 # and send the signal only when the room is unlocked |
145 # a proper configuration management should be done | 163 # a proper configuration management should be done |
146 print "room locked !" | 164 print "room locked !" |
147 self.clients[profile].configure(room.roomJID, {}).addCallbacks(_sendBridgeSignal, lambda x: log.error(_(u'Error while configuring the room'))) | 165 d = self.clients[profile].configure(room.roomJID, {}) |
148 else: | 166 d.addErrback(lambda dummy: log.error(_(u'Error while configuring the room'))) |
149 _sendBridgeSignal() | |
150 return room | 167 return room |
151 | 168 |
152 def __err_joining_room(self, failure, room_jid, nick, history_options, password, profile): | 169 def _joinEb(self, failure, room_jid, nick, password, profile): |
153 """Called when something is going wrong when joining the room""" | 170 """Called when something is going wrong when joining the room""" |
154 if hasattr(failure.value, "condition") and failure.value.condition == 'conflict': | 171 if hasattr(failure.value, "condition") and failure.value.condition == 'conflict': |
155 # we have a nickname conflict, we try again with "_" suffixed to current nickname | 172 # we have a nickname conflict, we try again with "_" suffixed to current nickname |
156 nick += '_' | 173 nick += '_' |
157 return self.clients[profile].join(room_jid, nick, history_options, password).addCallbacks(self.__room_joined, self.__err_joining_room, callbackKeywords={'profile': profile}, errbackArgs=[room_jid, nick, history_options, password, profile]) | 174 return self.clients[profile].join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, callbackKeywords={'profile': profile}, errbackArgs=[room_jid, nick, password, profile]) |
158 mess = D_("Error while joining the room %s" % room_jid.userhost()) | 175 mess = D_("Error while joining the room %s" % room_jid.userhost()) |
159 try: | 176 try: |
160 mess += " with condition '%s'" % failure.value.condition | 177 mess += " with condition '%s'" % failure.value.condition |
161 except AttributeError: | 178 except AttributeError: |
162 pass | 179 pass |
163 log.error(mess) | 180 log.error(mess) |
164 self.host.bridge.newAlert(mess, D_("Group chat error"), "ERROR", profile) | 181 self.host.bridge.newAlert(mess, D_("Group chat error"), "ERROR", profile) |
165 raise failure | 182 raise failure |
166 | 183 |
184 @staticmethod | |
185 def _getOccupants(room): | |
186 """Get occupants of a room in a form suitable for bridge""" | |
187 return {u.nick: {k:unicode(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in room.roster.values()} | |
188 | |
167 def isRoom(self, entity_bare, profile_key): | 189 def isRoom(self, entity_bare, profile_key): |
168 """Tell if a bare entity is a MUC room. | 190 """Tell if a bare entity is a MUC room. |
169 | 191 |
170 @param entity_bare (jid.JID): bare entity | 192 @param entity_bare (jid.JID): bare entity |
171 @param profile_key (unicode): %(doc_profile_key)s | 193 @param profile_key (unicode): %(doc_profile_key)s |
179 profile = self.host.memory.getProfileName(profile_key) | 201 profile = self.host.memory.getProfileName(profile_key) |
180 result = [] | 202 result = [] |
181 if not self.checkClient(profile): | 203 if not self.checkClient(profile): |
182 return result | 204 return result |
183 for room in self.clients[profile].joined_rooms.values(): | 205 for room in self.clients[profile].joined_rooms.values(): |
184 result.append((room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick)) | 206 if room._room_ok: |
207 result.append((room.roomJID.userhost(), self._getOccupants(room), room.nick, room.subject)) | |
185 return result | 208 return result |
186 | 209 |
187 def getRoomNick(self, room_jid, profile_key=C.PROF_KEY_NONE): | 210 def getRoomNick(self, room_jid, profile_key=C.PROF_KEY_NONE): |
188 """return nick used in room by user | 211 """return nick used in room by user |
189 | 212 |
299 profile = self.getProfileAssertInRoom(room_jid, profile) | 322 profile = self.getProfileAssertInRoom(room_jid, profile) |
300 return self.clients[profile].joined_rooms[room_jid].inRoster(muc.User(nick)) | 323 return self.clients[profile].joined_rooms[room_jid].inRoster(muc.User(nick)) |
301 | 324 |
302 def getRoomsSubjects(self, profile_key=C.PROF_KEY_NONE): | 325 def getRoomsSubjects(self, profile_key=C.PROF_KEY_NONE): |
303 """Return received subjects of rooms""" | 326 """Return received subjects of rooms""" |
327 # FIXME: to be removed | |
304 profile = self.host.memory.getProfileName(profile_key) | 328 profile = self.host.memory.getProfileName(profile_key) |
305 if not self.checkClient(profile): | 329 if not self.checkClient(profile): |
306 return [] | 330 return [] |
307 return self.clients[profile].rec_subjects.values() | 331 return self.clients[profile].rec_subjects.values() |
308 | 332 |
353 | 377 |
354 @return: unicode | 378 @return: unicode |
355 """ | 379 """ |
356 return self.host.memory.getConfig(CONFIG_SECTION, 'default_muc', default_conf['default_muc']) | 380 return self.host.memory.getConfig(CONFIG_SECTION, 'default_muc', default_conf['default_muc']) |
357 | 381 |
358 def join(self, room_jid, nick, options, profile_key=C.PROF_KEY_NONE): | 382 def join(self, client, room_jid, nick, options): |
359 def _errDeferred(exc_obj=Exception, txt='Error while joining room'): | 383 def _errDeferred(exc_obj=Exception, txt='Error while joining room'): |
360 d = defer.Deferred() | 384 d = defer.Deferred() |
361 d.errback(exc_obj(txt)) | 385 d.errback(exc_obj(txt)) |
362 return d | 386 return d |
363 | 387 |
364 profile = self.host.memory.getProfileName(profile_key) | 388 if room_jid in self.clients[client.profile].joined_rooms: |
365 if not self.checkClient(profile): | 389 log.warning(_(u'%(profile)s is already in room %(room_jid)s') % {'profile': client.profile, 'room_jid': room_jid.userhost()}) |
366 return _errDeferred() | |
367 if room_jid in self.clients[profile].joined_rooms: | |
368 log.warning(_(u'%(profile)s is already in room %(room_jid)s') % {'profile': profile, 'room_jid': room_jid.userhost()}) | |
369 return _errDeferred(AlreadyJoinedRoom, D_(u"The room has already been joined")) | 390 return _errDeferred(AlreadyJoinedRoom, D_(u"The room has already been joined")) |
370 log.info(_(u"[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile': profile, 'room': room_jid.userhost(), 'nick': nick}) | 391 log.info(_(u"[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile': client.profile, 'room': room_jid.userhost(), 'nick': nick}) |
371 | 392 |
372 if "history" in options: | |
373 history_limit = int(options["history"]) | |
374 else: | |
375 history_limit = int(self.host.memory.getParamA(C.HISTORY_LIMIT, 'General', profile_key=profile)) | |
376 # http://xmpp.org/extensions/xep-0045.html#enter-managehistory | |
377 history_options = muc.HistoryOptions(maxStanzas=history_limit) | |
378 password = options["password"] if "password" in options else None | 393 password = options["password"] if "password" in options else None |
379 | 394 |
380 return self.clients[profile].join(room_jid, nick, history_options, password).addCallbacks(self.__room_joined, self.__err_joining_room, callbackKeywords={'profile': profile}, errbackArgs=[room_jid, nick, history_options, password, profile]) | 395 return self.clients[client.profile].join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, callbackKeywords={'profile': client.profile}, errbackArgs=[room_jid, nick, password, client.profile]) |
381 # FIXME: how to set the cancel method on the Deferred created by wokkel? | |
382 # This happens when the room is not reachable, e.g. no internet connection: | |
383 # > /usr/local/lib/python2.7/dist-packages/twisted/internet/defer.py(480)_startRunCallbacks() | |
384 # -> raise AlreadyCalledError(extra) | |
385 | 396 |
386 def _join(self, room_jid_s, nick, options=None, profile_key=C.PROF_KEY_NONE): | 397 def _join(self, room_jid_s, nick, options=None, profile_key=C.PROF_KEY_NONE): |
387 """join method used by bridge: use the join method, but doesn't return any deferred | 398 """join method used by bridge |
399 | |
388 @return: unicode (the room bare) | 400 @return: unicode (the room bare) |
389 """ | 401 """ |
402 client = self.host.getClient(profile_key) | |
390 if options is None: | 403 if options is None: |
391 options = {} | 404 options = {} |
392 profile = self.host.memory.getProfileName(profile_key) | |
393 if not self.checkClient(profile): | |
394 return | |
395 if room_jid_s: | 405 if room_jid_s: |
396 muc_service = self.host.getClient(profile).muc_service | 406 muc_service = self.host.getClient(client.profile).muc_service |
397 try: | 407 try: |
398 room_jid = jid.JID(room_jid_s) | 408 room_jid = jid.JID(room_jid_s) |
399 except (RuntimeError, jid.InvalidFormat, AttributeError): | 409 except (RuntimeError, jid.InvalidFormat, AttributeError): |
400 return defer.fail(jid.InvalidFormat(_(u"Invalid room identifier: '%s'. Please give a room short or full identifier like 'room' or 'room@%s'.") % (room_jid_s, unicode(muc_service)))) | 410 return defer.fail(jid.InvalidFormat(_(u"Invalid room identifier: '%s'. Please give a room short or full identifier like 'room' or 'room@%s'.") % (room_jid_s, unicode(muc_service)))) |
401 if not room_jid.user: | 411 if not room_jid.user: |
402 room_jid.user, room_jid.host = room_jid.host, muc_service | 412 room_jid.user, room_jid.host = room_jid.host, muc_service |
403 else: | 413 else: |
404 room_jid = self.getUniqueName(profile_key=profile_key) | 414 room_jid = self.getUniqueName(profile_key=client.profile) |
405 # TODO: error management + signal in bridge | 415 # TODO: error management + signal in bridge |
406 d = self.join(room_jid, nick, options, profile) | 416 d = self.join(client, room_jid, nick, options) |
407 return d.addCallback(lambda room: room.roomJID.userhost()) | 417 return d.addCallback(lambda room: room.roomJID.userhost()) |
408 | 418 |
409 def nick(self, room_jid, nick, profile_key): | 419 def nick(self, room_jid, nick, profile_key): |
410 profile = self.getProfileAssertInRoom(room_jid, profile_key) | 420 profile = self.getProfileAssertInRoom(room_jid, profile_key) |
411 return self.clients[profile].nick(room_jid, nick) | 421 return self.clients[profile].nick(room_jid, nick) |
475 # TODO: handles reason and nick | 485 # TODO: handles reason and nick |
476 return self.clients[profile].modifyAffiliationList(room_jid, [entity_jid], options['affiliation']) | 486 return self.clients[profile].modifyAffiliationList(room_jid, [entity_jid], options['affiliation']) |
477 | 487 |
478 # Text commands # | 488 # Text commands # |
479 | 489 |
480 def cmd_nick(self, mess_data, profile): | 490 def cmd_nick(self, client, mess_data): |
481 """change nickname | 491 """change nickname |
482 | 492 |
483 @command (group): new_nick | 493 @command (group): new_nick |
484 - new_nick: new nick to use | 494 - new_nick: new nick to use |
485 """ | 495 """ |
486 nick = mess_data["unparsed"].strip() | 496 nick = mess_data["unparsed"].strip() |
487 if nick: | 497 if nick: |
488 room = mess_data["to"] | 498 room = mess_data["to"] |
489 self.nick(room, nick, profile) | 499 self.nick(room, nick, client.profile) |
490 | 500 |
491 return False | 501 return False |
492 | 502 |
493 def cmd_join(self, mess_data, profile): | 503 def cmd_join(self, client, mess_data): |
494 """join a new room | 504 """join a new room |
495 | 505 |
496 @command (all): JID | 506 @command (all): JID |
497 - JID: room to join (on the same service if full jid is not specified) | 507 - JID: room to join (on the same service if full jid is not specified) |
498 """ | 508 """ |
499 if mess_data["unparsed"].strip(): | 509 if mess_data["unparsed"].strip(): |
500 room_jid = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host) | 510 room_jid = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host) |
501 nick = (self.getRoomNick(room_jid, profile) or | 511 nick = (self.getRoomNick(room_jid, client.profile) or |
502 self.host.getClient(profile).jid.user) | 512 self.host.getClient(client.profile).jid.user) |
503 self.join(room_jid, nick, {}, profile) | 513 self.join(client, room_jid, nick, {}) |
504 | 514 |
505 return False | 515 return False |
506 | 516 |
507 def cmd_leave(self, mess_data, profile): | 517 def cmd_leave(self, client, mess_data): |
508 """quit a room | 518 """quit a room |
509 | 519 |
510 @command (group): [ROOM_JID] | 520 @command (group): [ROOM_JID] |
511 - ROOM_JID: jid of the room to live (current room if not specified) | 521 - ROOM_JID: jid of the room to live (current room if not specified) |
512 """ | 522 """ |
513 if mess_data["unparsed"].strip(): | 523 if mess_data["unparsed"].strip(): |
514 room = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host) | 524 room = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host) |
515 else: | 525 else: |
516 room = mess_data["to"] | 526 room = mess_data["to"] |
517 | 527 |
518 self.leave(room, profile) | 528 self.leave(room, client.profile) |
519 | 529 |
520 return False | 530 return False |
521 | 531 |
522 def cmd_part(self, mess_data, profile): | 532 def cmd_part(self, client, mess_data): |
523 """just a synonym of /leave | 533 """just a synonym of /leave |
524 | 534 |
525 @command (group): [ROOM_JID] | 535 @command (group): [ROOM_JID] |
526 - ROOM_JID: jid of the room to live (current room if not specified) | 536 - ROOM_JID: jid of the room to live (current room if not specified) |
527 """ | 537 """ |
528 return self.cmd_leave(mess_data, profile) | 538 return self.cmd_leave(client, mess_data) |
529 | 539 |
530 def cmd_kick(self, mess_data, profile): | 540 def cmd_kick(self, client, mess_data): |
531 """kick a room member | 541 """kick a room member |
532 | 542 |
533 @command (group): ROOM_NICK | 543 @command (group): ROOM_NICK |
534 - ROOM_NICK: the nick of the person to kick | 544 - ROOM_NICK: the nick of the person to kick |
535 """ | 545 """ |
536 options = mess_data["unparsed"].strip().split() | 546 options = mess_data["unparsed"].strip().split() |
537 try: | 547 try: |
538 nick = options[0] | 548 nick = options[0] |
539 assert(self.isNickInRoom(mess_data["to"], nick, profile)) | 549 assert(self.isNickInRoom(mess_data["to"], nick, client.profile)) |
540 except (IndexError, AssertionError): | 550 except (IndexError, AssertionError): |
541 feedback = _(u"You must provide a member's nick to kick.") | 551 feedback = _(u"You must provide a member's nick to kick.") |
542 self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) | 552 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data) |
543 return False | 553 return False |
544 | 554 |
545 d = self.kick(nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, profile) | 555 d = self.kick(nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, client.profile) |
546 | 556 |
547 def cb(dummy): | 557 def cb(dummy): |
548 feedback_msg = _(u'You have kicked {}').format(nick) | 558 feedback_msg = _(u'You have kicked {}').format(nick) |
549 if len(options) > 1: | 559 if len(options) > 1: |
550 feedback_msg += _(u' for the following reason: {}').format(options[1]) | 560 feedback_msg += _(u' for the following reason: {}').format(options[1]) |
551 self.host.plugins[C.TEXT_CMDS].feedBack(feedback_msg, mess_data, profile) | 561 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback_msg, mess_data) |
552 return True | 562 return True |
553 d.addCallback(cb) | 563 d.addCallback(cb) |
554 return d | 564 return d |
555 | 565 |
556 def cmd_ban(self, mess_data, profile): | 566 def cmd_ban(self, client, mess_data): |
557 """ban an entity from the room | 567 """ban an entity from the room |
558 | 568 |
559 @command (group): (JID) [reason] | 569 @command (group): (JID) [reason] |
560 - JID: the JID of the entity to ban | 570 - JID: the JID of the entity to ban |
561 - reason: the reason why this entity is being banned | 571 - reason: the reason why this entity is being banned |
566 entity_jid = jid.JID(jid_s).userhostJID() | 576 entity_jid = jid.JID(jid_s).userhostJID() |
567 assert(entity_jid.user) | 577 assert(entity_jid.user) |
568 assert(entity_jid.host) | 578 assert(entity_jid.host) |
569 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): | 579 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): |
570 feedback = _(u"You must provide a valid JID to ban, like in '/ban contact@example.net'") | 580 feedback = _(u"You must provide a valid JID to ban, like in '/ban contact@example.net'") |
571 self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) | 581 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data) |
572 return False | 582 return False |
573 | 583 |
574 d = self.ban(entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, profile) | 584 d = self.ban(entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, client.profile) |
575 | 585 |
576 def cb(dummy): | 586 def cb(dummy): |
577 feedback_msg = _(u'You have banned {}').format(entity_jid) | 587 feedback_msg = _(u'You have banned {}').format(entity_jid) |
578 if len(options) > 1: | 588 if len(options) > 1: |
579 feedback_msg += _(u' for the following reason: {}').format(options[1]) | 589 feedback_msg += _(u' for the following reason: {}').format(options[1]) |
580 self.host.plugins[C.TEXT_CMDS].feedBack(feedback_msg, mess_data, profile) | 590 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback_msg, mess_data) |
581 return True | 591 return True |
582 d.addCallback(cb) | 592 d.addCallback(cb) |
583 return d | 593 return d |
584 | 594 |
585 def cmd_affiliate(self, mess_data, profile): | 595 def cmd_affiliate(self, client, mess_data): |
586 """affiliate an entity to the room | 596 """affiliate an entity to the room |
587 | 597 |
588 @command (group): (JID) [owner|admin|member|none|outcast] | 598 @command (group): (JID) [owner|admin|member|none|outcast] |
589 - JID: the JID of the entity to affiliate | 599 - JID: the JID of the entity to affiliate |
590 - owner: grant owner privileges | 600 - owner: grant owner privileges |
599 entity_jid = jid.JID(jid_s).userhostJID() | 609 entity_jid = jid.JID(jid_s).userhostJID() |
600 assert(entity_jid.user) | 610 assert(entity_jid.user) |
601 assert(entity_jid.host) | 611 assert(entity_jid.host) |
602 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): | 612 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): |
603 feedback = _(u"You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'") | 613 feedback = _(u"You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'") |
604 self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) | 614 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data) |
605 return False | 615 return False |
606 | 616 |
607 affiliation = options[1] if len(options) > 1 else 'none' | 617 affiliation = options[1] if len(options) > 1 else 'none' |
608 if affiliation not in AFFILIATIONS: | 618 if affiliation not in AFFILIATIONS: |
609 feedback = _(u"You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS) | 619 feedback = _(u"You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS) |
610 self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile) | 620 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data) |
611 return False | 621 return False |
612 | 622 |
613 d = self.affiliate(entity_jid, mess_data["to"], {'affiliation': affiliation}, profile) | 623 d = self.affiliate(entity_jid, mess_data["to"], {'affiliation': affiliation}, client.profile) |
614 | 624 |
615 def cb(dummy): | 625 def cb(dummy): |
616 feedback_msg = _(u'New affiliation for %(entity)s: %(affiliation)s').format(entity=entity_jid, affiliation=affiliation) | 626 feedback_msg = _(u'New affiliation for %(entity)s: %(affiliation)s').format(entity=entity_jid, affiliation=affiliation) |
617 self.host.plugins[C.TEXT_CMDS].feedBack(feedback_msg, mess_data, profile) | 627 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback_msg, mess_data) |
618 return True | 628 return True |
619 d.addCallback(cb) | 629 d.addCallback(cb) |
620 return d | 630 return d |
621 | 631 |
622 def cmd_title(self, mess_data, profile): | 632 def cmd_title(self, client, mess_data): |
623 """change room's subject | 633 """change room's subject |
624 | 634 |
625 @command (group): title | 635 @command (group): title |
626 - title: new room subject | 636 - title: new room subject |
627 """ | 637 """ |
628 subject = mess_data["unparsed"].strip() | 638 subject = mess_data["unparsed"].strip() |
629 | 639 |
630 if subject: | 640 if subject: |
631 room = mess_data["to"] | 641 room = mess_data["to"] |
632 self.subject(room, subject, profile) | 642 self.subject(room, subject, client.profile) |
633 | 643 |
634 return False | 644 return False |
635 | 645 |
636 def cmd_topic(self, mess_data, profile): | 646 def cmd_topic(self, client, mess_data): |
637 """just a synonym of /title | 647 """just a synonym of /title |
638 | 648 |
639 @command (group): title | 649 @command (group): title |
640 - title: new room subject | 650 - title: new room subject |
641 """ | 651 """ |
642 return self.cmd_title(mess_data, profile) | 652 return self.cmd_title(client, mess_data) |
643 | 653 |
644 def _whois(self, whois_msg, mess_data, target_jid, profile): | 654 def _whois(self, client, whois_msg, mess_data, target_jid): |
645 """ Add MUC user information to whois """ | 655 """ Add MUC user information to whois """ |
646 if mess_data['type'] != "groupchat": | 656 if mess_data['type'] != "groupchat": |
647 return | 657 return |
648 if target_jid.userhostJID() not in self.clients[profile].joined_rooms: | 658 if target_jid.userhostJID() not in self.clients[client.profile].joined_rooms: |
649 log.warning(_("This room has not been joined")) | 659 log.warning(_("This room has not been joined")) |
650 return | 660 return |
651 if not target_jid.resource: | 661 if not target_jid.resource: |
652 return | 662 return |
653 user = self.clients[profile].joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource) | 663 user = self.clients[client.profile].joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource) |
654 whois_msg.append(_("Nickname: %s") % user.nick) | 664 whois_msg.append(_("Nickname: %s") % user.nick) |
655 if user.entity: | 665 if user.entity: |
656 whois_msg.append(_("Entity: %s") % user.entity) | 666 whois_msg.append(_("Entity: %s") % user.entity) |
657 if user.affiliation != 'none': | 667 if user.affiliation != 'none': |
658 whois_msg.append(_("Affiliation: %s") % user.affiliation) | 668 whois_msg.append(_("Affiliation: %s") % user.affiliation) |
679 def __init__(self, plugin_parent): | 689 def __init__(self, plugin_parent): |
680 self.plugin_parent = plugin_parent | 690 self.plugin_parent = plugin_parent |
681 self.host = plugin_parent.host | 691 self.host = plugin_parent.host |
682 muc.MUCClient.__init__(self) | 692 muc.MUCClient.__init__(self) |
683 self.rec_subjects = {} | 693 self.rec_subjects = {} |
684 self.__changing_nicks = set() # used to keep trace of who is changing nick, | 694 self._changing_nicks = set() # used to keep trace of who is changing nick, |
685 # and to discard userJoinedRoom signal in this case | 695 # and to discard userJoinedRoom signal in this case |
686 print "init SatMUCClient OK" | 696 print "init SatMUCClient OK" |
687 | 697 |
688 @property | 698 @property |
689 def joined_rooms(self): | 699 def joined_rooms(self): |
690 return self._rooms | 700 return self._rooms |
691 | 701 |
692 def subject(self, room, subject): | 702 def _addRoom(self, room): |
693 return muc.MUCClientProtocol.subject(self, room, subject) | 703 super(SatMUCClient, self)._addRoom(room) |
704 room._roster_ok = False | |
705 room._room_ok = None # False when roster, history and subject are available | |
706 # True when new messages are saved to database | |
707 room._history_d = defer.Deferred() # use to send bridge signal once backlog are written in history | |
708 room._history_d.callback(None) | |
709 room._cache = [] | |
710 | |
711 def _gotLastDbHistory(self, mess_data_list, room_jid, nick, password): | |
712 if mess_data_list: | |
713 timestamp = mess_data_list[0][1] | |
714 # we use seconds since last message to get backlog without duplicates | |
715 # and we remove 1 second to avoid getting the last message again | |
716 seconds = int(time.time() - timestamp) - 1 | |
717 else: | |
718 seconds = None | |
719 d = super(SatMUCClient, self).join(room_jid, nick, muc.HistoryOptions(seconds=seconds), password) | |
720 return d | |
721 | |
722 def join(self, room_jid, nick, password=None): | |
723 d = self.host.memory.historyGet(self.parent.jid.userhostJID(), room_jid, 1, True, profile=self.parent.profile) | |
724 d.addCallback(self._gotLastDbHistory, room_jid, nick, password) | |
725 return d | |
726 | |
727 ## presence/roster ## | |
694 | 728 |
695 def availableReceived(self, presence): | 729 def availableReceived(self, presence): |
696 """ | 730 """ |
697 Available presence was received. | 731 Available presence was received. |
698 """ | 732 """ |
699 # XXX: we override MUCClient.availableReceived to fix bugs | 733 # XXX: we override MUCClient.availableReceived to fix bugs |
700 # (affiliation and role are not set) | 734 # (affiliation and role are not set) |
701 # FIXME: propose a patch upstream | |
702 | 735 |
703 room, user = self._getRoomUser(presence) | 736 room, user = self._getRoomUser(presence) |
704 | 737 |
705 if room is None: | 738 if room is None: |
706 return | 739 return |
718 if room.inRoster(user): | 751 if room.inRoster(user): |
719 self.userUpdatedStatus(room, user, presence.show, presence.status) | 752 self.userUpdatedStatus(room, user, presence.show, presence.status) |
720 else: | 753 else: |
721 room.addUser(user) | 754 room.addUser(user) |
722 self.userJoinedRoom(room, user) | 755 self.userJoinedRoom(room, user) |
756 | |
723 def unavailableReceived(self, presence): | 757 def unavailableReceived(self, presence): |
724 # XXX: we override this method to manage nickname change | 758 # XXX: we override this method to manage nickname change |
725 # TODO: feed this back to Wokkel | |
726 """ | 759 """ |
727 Unavailable presence was received. | 760 Unavailable presence was received. |
728 | 761 |
729 If this was received from a MUC room occupant JID, that occupant has | 762 If this was received from a MUC room occupant JID, that occupant has |
730 left the room. | 763 left the room. |
735 return | 768 return |
736 | 769 |
737 room.removeUser(user) | 770 room.removeUser(user) |
738 | 771 |
739 if muc.STATUS_CODE.NEW_NICK in presence.mucStatuses: | 772 if muc.STATUS_CODE.NEW_NICK in presence.mucStatuses: |
740 self.__changing_nicks.add(presence.nick) | 773 self._changing_nicks.add(presence.nick) |
741 self.userChangedNick(room, user, presence.nick) | 774 self.userChangedNick(room, user, presence.nick) |
742 else: | 775 else: |
743 self.__changing_nicks.discard(presence.nick) | 776 self._changing_nicks.discard(presence.nick) |
744 self.userLeftRoom(room, user) | 777 self.userLeftRoom(room, user) |
745 | 778 |
746 def userJoinedRoom(self, room, user): | 779 def userJoinedRoom(self, room, user): |
747 self.host.memory.updateEntityData(room.roomJID, "type", "chatroom", profile_key=self.parent.profile) | 780 if user.nick == room.nick: |
748 if user.nick in self.__changing_nicks: | 781 # we have received our own nick, this mean that the full room roster was received |
749 self.__changing_nicks.remove(user.nick) | 782 room._roster_ok = True |
750 else: | 783 log.debug(u"room {room} joined with nick {nick}".format(room=room.occupantJID.userhost(), nick=user.nick)) |
751 log.debug(_(u"user %(nick)s has joined room (%(room_id)s)") % {'nick': user.nick, 'room_id': room.occupantJID.userhost()}) | 784 # We set type so we don't have use a deferred with disco to check entity type |
752 if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile): | 785 self.host.memory.updateEntityData(room.roomJID, C.ENTITY_TYPE, ENTITY_TYPE_MUC, profile_key=self.parent.profile) |
753 return | 786 |
754 user_data = {'entity': user.entity.full() if user.entity else '', 'affiliation': user.affiliation, 'role': user.role} | 787 elif room._roster_ok: |
755 self.host.bridge.roomUserJoined(room.roomJID.userhost(), user.nick, user_data, self.parent.profile) | 788 try: |
789 self._changing_nicks.remove(user.nick) | |
790 except KeyError: | |
791 # this is a new user | |
792 log.debug(_(u"user {nick} has joined room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) | |
793 if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile): | |
794 return | |
795 | |
796 extra = {'info_type': ROOM_USER_JOINED, | |
797 'user_affiliation': user.affiliation, | |
798 'user_role': user.role, | |
799 'user_nick': user.nick | |
800 } | |
801 if user.entity is not None: | |
802 extra['user_entity'] = user.entity.full() | |
803 mess_data = { # dict is similar to the one used in client.onMessage | |
804 "from": room.roomJID, | |
805 "to": self.parent.jid, | |
806 "uid": unicode(uuid.uuid4()), | |
807 "message": {'': D_(u"=> {} has joined the room").format(user.nick)}, | |
808 "subject": {}, | |
809 "type": C.MESS_TYPE_INFO, | |
810 "extra": extra, | |
811 "timestamp": time.time(), | |
812 } | |
813 self.host.messageAddToHistory(mess_data, self.parent) | |
814 self.host.messageSendToBridge(mess_data, self.parent) | |
815 | |
756 | 816 |
757 def userLeftRoom(self, room, user): | 817 def userLeftRoom(self, room, user): |
758 if not self.host.trigger.point("MUC user left", room, user, self.parent.profile): | 818 if not self.host.trigger.point("MUC user left", room, user, self.parent.profile): |
759 return | 819 return |
760 if user.nick == room.nick: | 820 if user.nick == room.nick: |
761 # we left the room | 821 # we left the room |
762 room_jid_s = room.roomJID.userhost() | 822 room_jid_s = room.roomJID.userhost() |
763 log.info(_(u"Room [%(room)s] left (%(profile)s))") % {"room": room_jid_s, | 823 log.info(_(u"Room ({room}) left ({profile})").format( |
764 "profile": self.parent.profile}) | 824 room = room_jid_s, profile = self.parent.profile)) |
765 self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile) | 825 self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile) |
766 self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile) | 826 self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile) |
767 else: | 827 else: |
768 log.debug(_(u"user %(nick)s left room (%(room_id)s)") % {'nick': user.nick, 'room_id': room.occupantJID.userhost()}) | 828 log.debug(_(u"user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) |
769 user_data = {'entity': user.entity.full() if user.entity else '', 'affiliation': user.affiliation, 'role': user.role} | 829 extra = {'info_type': ROOM_USER_LEFT, |
770 self.host.bridge.roomUserLeft(room.roomJID.userhost(), user.nick, user_data, self.parent.profile) | 830 'user_affiliation': user.affiliation, |
831 'user_role': user.role, | |
832 'user_nick': user.nick | |
833 } | |
834 if user.entity is not None: | |
835 extra['user_entity'] = user.entity.full() | |
836 mess_data = { # dict is similar to the one used in client.onMessage | |
837 "from": room.roomJID, | |
838 "to": self.parent.jid, | |
839 "uid": unicode(uuid.uuid4()), | |
840 "message": {'': D_(u"<= {} has left the room").format(user.nick)}, | |
841 "subject": {}, | |
842 "type": C.MESS_TYPE_INFO, | |
843 "extra": extra, | |
844 "timestamp": time.time(), | |
845 } | |
846 self.host.messageAddToHistory(mess_data, self.parent) | |
847 self.host.messageSendToBridge(mess_data, self.parent) | |
771 | 848 |
772 def userChangedNick(self, room, user, new_nick): | 849 def userChangedNick(self, room, user, new_nick): |
773 self.host.bridge.roomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile) | 850 self.host.bridge.roomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile) |
774 | 851 |
775 def userUpdatedStatus(self, room, user, show, status): | 852 def userUpdatedStatus(self, room, user, show, status): |
776 self.host.bridge.presenceUpdate(room.roomJID.userhost() + '/' + user.nick, show or '', 0, {C.PRESENCE_STATUSES_DEFAULT: status or ''}, self.parent.profile) | 853 self.host.bridge.presenceUpdate(room.roomJID.userhost() + '/' + user.nick, show or '', 0, {C.PRESENCE_STATUSES_DEFAULT: status or ''}, self.parent.profile) |
777 | 854 |
855 ## messages ## | |
856 | |
778 def receivedGroupChat(self, room, user, body): | 857 def receivedGroupChat(self, room, user, body): |
779 log.debug(u'receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body)) | 858 log.debug(u'receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body)) |
780 | 859 |
860 def _addToHistory(self, dummy, user, message): | |
861 # we check if message is not in history | |
862 # and raise ConflictError else | |
863 stamp = message.delay.stamp.astimezone(tzutc()).timetuple() | |
864 timestamp = float(calendar.timegm(stamp)) | |
865 data = { # dict is similar to the one used in client.onMessage | |
866 "from": message.sender, | |
867 "to": message.recipient, | |
868 "uid": unicode(uuid.uuid4()), | |
869 "type": C.MESS_TYPE_GROUPCHAT, | |
870 "extra": {}, | |
871 "timestamp": timestamp, | |
872 "received_timestamp": unicode(time.time()), | |
873 } | |
874 # FIXME: message and subject don't handle xml:lang | |
875 data['message'] = {'': message.body} if message.body is not None else {} | |
876 data['subject'] = {'': message.subject} if message.subject is not None else {} | |
877 | |
878 if data['message'] or data['subject']: | |
879 return self.host.memory.addToHistory(self.parent, data) | |
880 else: | |
881 return defer.succeed(None) | |
882 | |
883 def _addToHistoryEb(self, failure): | |
884 failure.trap(exceptions.CancelError) | |
885 | |
781 def receivedHistory(self, room, user, message): | 886 def receivedHistory(self, room, user, message): |
782 # http://xmpp.org/extensions/xep-0045.html#enter-history | 887 """Called when history (backlog) message are received |
783 # log.debug(u'receivedHistory: room=%s user=%s body=%s' % (room.roomJID.full(), user, message)) | 888 |
784 pass | 889 we check if message is not already in our history |
890 and add it if needed | |
891 @param room(muc.Room): room instance | |
892 @param user(muc.User, None): the user that sent the message | |
893 None if the message come from the room | |
894 @param message(muc.GroupChat): the parsed message | |
895 """ | |
896 room._history_d.addCallback(self._addToHistory, user, message) | |
897 room._history_d.addErrback(self._addToHistoryEb) | |
898 | |
899 ## subject ## | |
900 | |
901 def groupChatReceived(self, message): | |
902 """ | |
903 A group chat message has been received from a MUC room. | |
904 | |
905 There are a few event methods that may get called here. | |
906 L{receivedGroupChat}, L{receivedSubject} or L{receivedHistory}. | |
907 """ | |
908 # We override this method to fix subject handling | |
909 # FIXME: remove this merge fixed upstream | |
910 room, user = self._getRoomUser(message) | |
911 | |
912 if room is None: | |
913 return | |
914 | |
915 if message.subject is not None: | |
916 self.receivedSubject(room, user, message.subject) | |
917 elif message.delay is None: | |
918 self.receivedGroupChat(room, user, message) | |
919 else: | |
920 self.receivedHistory(room, user, message) | |
921 | |
922 def subject(self, room, subject): | |
923 return muc.MUCClientProtocol.subject(self, room, subject) | |
924 | |
925 def _historyCb(self, dummy, room): | |
926 self.host.bridge.roomJoined( | |
927 room.roomJID.userhost(), | |
928 XEP_0045._getOccupants(room), | |
929 room.nick, | |
930 room.subject, | |
931 self.parent.profile) | |
932 del room._history_d | |
933 cache = room._cache | |
934 del room._cache | |
935 room._room_ok = True | |
936 for elem in cache: | |
937 self.parent.xmlstream.dispatch(elem) | |
938 | |
939 | |
940 def _historyEb(self, failure_, room): | |
941 log.error(u"Error while managing history: {}".format(failure_)) | |
942 self._historyCb(None, room) | |
785 | 943 |
786 def receivedSubject(self, room, user, subject): | 944 def receivedSubject(self, room, user, subject): |
787 # http://xmpp.org/extensions/xep-0045.html#enter-subject | 945 # when subject is received, we know that we have whole roster and history |
788 log.debug(_(u"New subject for room (%(room_id)s): %(subject)s") % {'room_id': room.roomJID.full(), 'subject': subject}) | 946 # cf. http://xmpp.org/extensions/xep-0045.html#enter-subject |
947 room.subject = subject # FIXME: subject doesn't handle xml:lang | |
789 self.rec_subjects[room.roomJID.userhost()] = (room.roomJID.userhost(), subject) | 948 self.rec_subjects[room.roomJID.userhost()] = (room.roomJID.userhost(), subject) |
790 self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile) | 949 if room._room_ok is None: |
950 # this is the first subject we receive | |
951 # that mean that we have received everything we need | |
952 room._room_ok = False | |
953 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room]) | |
954 else: | |
955 # the subject has been changed | |
956 log.debug(_(u"New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject)) | |
957 self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile) | |
958 | |
959 ## disco ## | |
791 | 960 |
792 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | 961 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): |
793 return [disco.DiscoFeature(NS_MUC)] | 962 return [disco.DiscoFeature(NS_MUC)] |
794 | 963 |
795 def getDiscoItems(self, requestor, target, nodeIdentifier=''): | 964 def getDiscoItems(self, requestor, target, nodeIdentifier=''): |