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
This commit is contained in:
parent
feadc8c89e
commit
a7de473107
1 changed files with 987 additions and 598 deletions
|
|
@ -23,10 +23,13 @@ from sat.core.log import getLogger
|
|||
log = getLogger(__name__)
|
||||
from twisted.internet import defer
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from dateutil.tz import tzutc
|
||||
|
||||
from sat.core import exceptions
|
||||
from sat.memory import memory
|
||||
|
||||
import calendar
|
||||
import time
|
||||
import uuid
|
||||
import copy
|
||||
|
||||
|
|
@ -50,6 +53,10 @@ PLUGIN_INFO = {
|
|||
|
||||
NS_MUC = 'http://jabber.org/protocol/muc'
|
||||
AFFILIATIONS = ('owner', 'admin', 'member', 'none', 'outcast')
|
||||
ROOM_USER_JOINED = 'ROOM_USER_JOINED'
|
||||
ROOM_USER_LEFT = 'ROOM_USER_LEFT'
|
||||
OCCUPANT_KEYS = ('nick', 'entity', 'affiliation', 'role')
|
||||
ENTITY_TYPE_MUC = "MUC"
|
||||
|
||||
CONFIG_SECTION = u'plugin muc'
|
||||
|
||||
|
|
@ -73,20 +80,18 @@ class XEP_0045(object):
|
|||
def __init__(self, host):
|
||||
log.info(_("Plugin XEP_0045 initialization"))
|
||||
self.host = host
|
||||
self.clients = {}
|
||||
self.clients = {} # FIXME: should be moved to profile's client
|
||||
self._sessions = memory.Sessions()
|
||||
host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join, async=True)
|
||||
host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._join, async=True)
|
||||
host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self.mucNick)
|
||||
host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self.mucLeave, async=True)
|
||||
host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sass)', method=self.getRoomsJoined)
|
||||
host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self.getRoomsJoined)
|
||||
host.bridge.addMethod("getRoomsSubjects", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjects)
|
||||
host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName)
|
||||
host.bridge.addMethod("configureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async=True)
|
||||
host.bridge.addMethod("getDefaultMUC", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC)
|
||||
host.bridge.addSignal("roomJoined", ".plugin", signature='sasss') # args: room_jid, room_nicks, user_nick, profile
|
||||
host.bridge.addSignal("roomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile
|
||||
host.bridge.addSignal("roomLeft", ".plugin", signature='ss') # args: room_jid, profile
|
||||
host.bridge.addSignal("roomUserJoined", ".plugin", signature='ssa{ss}s') # args: room_jid, user_nick, user_data, profile
|
||||
host.bridge.addSignal("roomUserLeft", ".plugin", signature='ssa{ss}s') # args: room_jid, user_nick, user_data, profile
|
||||
host.bridge.addSignal("roomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile
|
||||
host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile
|
||||
self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True)
|
||||
|
|
@ -98,6 +103,7 @@ class XEP_0045(object):
|
|||
log.info(_("Text commands not available"))
|
||||
|
||||
host.trigger.add("presence_available", self.presenceTrigger)
|
||||
host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=1000000)
|
||||
|
||||
def profileConnected(self, profile):
|
||||
def assign_service(service):
|
||||
|
|
@ -105,6 +111,23 @@ class XEP_0045(object):
|
|||
client.muc_service = service
|
||||
return self.getMUCService(profile=profile).addCallback(assign_service)
|
||||
|
||||
def MessageReceivedTrigger(self, client, message_elt, post_treat):
|
||||
if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
|
||||
if message_elt.subject or message_elt.delay:
|
||||
return False
|
||||
from_jid = jid.JID(message_elt['from'])
|
||||
room_jid = from_jid.userhostJID()
|
||||
if room_jid in self.clients[client.profile].joined_rooms:
|
||||
room = self.clients[client.profile].joined_rooms[room_jid]
|
||||
if not room._room_ok:
|
||||
log.warning(u"Received non delayed message in a room before its initialisation: {}".format(message_elt.toXml()))
|
||||
room._cache.append(message_elt)
|
||||
return False
|
||||
else:
|
||||
log.warning(u"Received groupchat message for a room which has not been joined, ignoring it: {}".format(message_elt.toXml()))
|
||||
return False
|
||||
return True
|
||||
|
||||
def checkClient(self, profile):
|
||||
"""Check if the profile is connected and has used the MUC feature.
|
||||
|
||||
|
|
@ -132,29 +155,23 @@ class XEP_0045(object):
|
|||
raise UnknownRoom("This room has not been joined")
|
||||
return profile
|
||||
|
||||
def __room_joined(self, room, profile):
|
||||
def _joinCb(self, room, profile):
|
||||
"""Called when the user is in the requested room"""
|
||||
|
||||
def _sendBridgeSignal(ignore=None):
|
||||
self.host.bridge.roomJoined(room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick, profile)
|
||||
|
||||
self.clients[profile].joined_rooms[room.roomJID] = room
|
||||
if room.locked:
|
||||
# FIXME: the current behaviour is to create an instant room
|
||||
# and send the signal only when the room is unlocked
|
||||
# a proper configuration management should be done
|
||||
print "room locked !"
|
||||
self.clients[profile].configure(room.roomJID, {}).addCallbacks(_sendBridgeSignal, lambda x: log.error(_(u'Error while configuring the room')))
|
||||
else:
|
||||
_sendBridgeSignal()
|
||||
d = self.clients[profile].configure(room.roomJID, {})
|
||||
d.addErrback(lambda dummy: log.error(_(u'Error while configuring the room')))
|
||||
return room
|
||||
|
||||
def __err_joining_room(self, failure, room_jid, nick, history_options, password, profile):
|
||||
def _joinEb(self, failure, room_jid, nick, password, profile):
|
||||
"""Called when something is going wrong when joining the room"""
|
||||
if hasattr(failure.value, "condition") and failure.value.condition == 'conflict':
|
||||
# we have a nickname conflict, we try again with "_" suffixed to current nickname
|
||||
nick += '_'
|
||||
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])
|
||||
return self.clients[profile].join(room_jid, nick, password).addCallbacks(self._joinCb, self._joinEb, callbackKeywords={'profile': profile}, errbackArgs=[room_jid, nick, password, profile])
|
||||
mess = D_("Error while joining the room %s" % room_jid.userhost())
|
||||
try:
|
||||
mess += " with condition '%s'" % failure.value.condition
|
||||
|
|
@ -164,6 +181,11 @@ class XEP_0045(object):
|
|||
self.host.bridge.newAlert(mess, D_("Group chat error"), "ERROR", profile)
|
||||
raise failure
|
||||
|
||||
@staticmethod
|
||||
def _getOccupants(room):
|
||||
"""Get occupants of a room in a form suitable for bridge"""
|
||||
return {u.nick: {k:unicode(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in room.roster.values()}
|
||||
|
||||
def isRoom(self, entity_bare, profile_key):
|
||||
"""Tell if a bare entity is a MUC room.
|
||||
|
||||
|
|
@ -181,7 +203,8 @@ class XEP_0045(object):
|
|||
if not self.checkClient(profile):
|
||||
return result
|
||||
for room in self.clients[profile].joined_rooms.values():
|
||||
result.append((room.roomJID.userhost(), [user.nick for user in room.roster.values()], room.nick))
|
||||
if room._room_ok:
|
||||
result.append((room.roomJID.userhost(), self._getOccupants(room), room.nick, room.subject))
|
||||
return result
|
||||
|
||||
def getRoomNick(self, room_jid, profile_key=C.PROF_KEY_NONE):
|
||||
|
|
@ -301,6 +324,7 @@ class XEP_0045(object):
|
|||
|
||||
def getRoomsSubjects(self, profile_key=C.PROF_KEY_NONE):
|
||||
"""Return received subjects of rooms"""
|
||||
# FIXME: to be removed
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not self.checkClient(profile):
|
||||
return []
|
||||
|
|
@ -355,45 +379,31 @@ class XEP_0045(object):
|
|||
"""
|
||||
return self.host.memory.getConfig(CONFIG_SECTION, 'default_muc', default_conf['default_muc'])
|
||||
|
||||
def join(self, room_jid, nick, options, profile_key=C.PROF_KEY_NONE):
|
||||
def join(self, client, room_jid, nick, options):
|
||||
def _errDeferred(exc_obj=Exception, txt='Error while joining room'):
|
||||
d = defer.Deferred()
|
||||
d.errback(exc_obj(txt))
|
||||
return d
|
||||
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not self.checkClient(profile):
|
||||
return _errDeferred()
|
||||
if room_jid in self.clients[profile].joined_rooms:
|
||||
log.warning(_(u'%(profile)s is already in room %(room_jid)s') % {'profile': profile, 'room_jid': room_jid.userhost()})
|
||||
if room_jid in self.clients[client.profile].joined_rooms:
|
||||
log.warning(_(u'%(profile)s is already in room %(room_jid)s') % {'profile': client.profile, 'room_jid': room_jid.userhost()})
|
||||
return _errDeferred(AlreadyJoinedRoom, D_(u"The room has already been joined"))
|
||||
log.info(_(u"[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile': profile, 'room': room_jid.userhost(), 'nick': nick})
|
||||
log.info(_(u"[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile': client.profile, 'room': room_jid.userhost(), 'nick': nick})
|
||||
|
||||
if "history" in options:
|
||||
history_limit = int(options["history"])
|
||||
else:
|
||||
history_limit = int(self.host.memory.getParamA(C.HISTORY_LIMIT, 'General', profile_key=profile))
|
||||
# http://xmpp.org/extensions/xep-0045.html#enter-managehistory
|
||||
history_options = muc.HistoryOptions(maxStanzas=history_limit)
|
||||
password = options["password"] if "password" in options else None
|
||||
|
||||
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])
|
||||
# FIXME: how to set the cancel method on the Deferred created by wokkel?
|
||||
# This happens when the room is not reachable, e.g. no internet connection:
|
||||
# > /usr/local/lib/python2.7/dist-packages/twisted/internet/defer.py(480)_startRunCallbacks()
|
||||
# -> raise AlreadyCalledError(extra)
|
||||
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])
|
||||
|
||||
def _join(self, room_jid_s, nick, options=None, profile_key=C.PROF_KEY_NONE):
|
||||
"""join method used by bridge: use the join method, but doesn't return any deferred
|
||||
"""join method used by bridge
|
||||
|
||||
@return: unicode (the room bare)
|
||||
"""
|
||||
client = self.host.getClient(profile_key)
|
||||
if options is None:
|
||||
options = {}
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not self.checkClient(profile):
|
||||
return
|
||||
if room_jid_s:
|
||||
muc_service = self.host.getClient(profile).muc_service
|
||||
muc_service = self.host.getClient(client.profile).muc_service
|
||||
try:
|
||||
room_jid = jid.JID(room_jid_s)
|
||||
except (RuntimeError, jid.InvalidFormat, AttributeError):
|
||||
|
|
@ -401,9 +411,9 @@ class XEP_0045(object):
|
|||
if not room_jid.user:
|
||||
room_jid.user, room_jid.host = room_jid.host, muc_service
|
||||
else:
|
||||
room_jid = self.getUniqueName(profile_key=profile_key)
|
||||
room_jid = self.getUniqueName(profile_key=client.profile)
|
||||
# TODO: error management + signal in bridge
|
||||
d = self.join(room_jid, nick, options, profile)
|
||||
d = self.join(client, room_jid, nick, options)
|
||||
return d.addCallback(lambda room: room.roomJID.userhost())
|
||||
|
||||
def nick(self, room_jid, nick, profile_key):
|
||||
|
|
@ -477,7 +487,7 @@ class XEP_0045(object):
|
|||
|
||||
# Text commands #
|
||||
|
||||
def cmd_nick(self, mess_data, profile):
|
||||
def cmd_nick(self, client, mess_data):
|
||||
"""change nickname
|
||||
|
||||
@command (group): new_nick
|
||||
|
|
@ -486,11 +496,11 @@ class XEP_0045(object):
|
|||
nick = mess_data["unparsed"].strip()
|
||||
if nick:
|
||||
room = mess_data["to"]
|
||||
self.nick(room, nick, profile)
|
||||
self.nick(room, nick, client.profile)
|
||||
|
||||
return False
|
||||
|
||||
def cmd_join(self, mess_data, profile):
|
||||
def cmd_join(self, client, mess_data):
|
||||
"""join a new room
|
||||
|
||||
@command (all): JID
|
||||
|
|
@ -498,13 +508,13 @@ class XEP_0045(object):
|
|||
"""
|
||||
if mess_data["unparsed"].strip():
|
||||
room_jid = self.host.plugins[C.TEXT_CMDS].getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host)
|
||||
nick = (self.getRoomNick(room_jid, profile) or
|
||||
self.host.getClient(profile).jid.user)
|
||||
self.join(room_jid, nick, {}, profile)
|
||||
nick = (self.getRoomNick(room_jid, client.profile) or
|
||||
self.host.getClient(client.profile).jid.user)
|
||||
self.join(client, room_jid, nick, {})
|
||||
|
||||
return False
|
||||
|
||||
def cmd_leave(self, mess_data, profile):
|
||||
def cmd_leave(self, client, mess_data):
|
||||
"""quit a room
|
||||
|
||||
@command (group): [ROOM_JID]
|
||||
|
|
@ -515,19 +525,19 @@ class XEP_0045(object):
|
|||
else:
|
||||
room = mess_data["to"]
|
||||
|
||||
self.leave(room, profile)
|
||||
self.leave(room, client.profile)
|
||||
|
||||
return False
|
||||
|
||||
def cmd_part(self, mess_data, profile):
|
||||
def cmd_part(self, client, mess_data):
|
||||
"""just a synonym of /leave
|
||||
|
||||
@command (group): [ROOM_JID]
|
||||
- ROOM_JID: jid of the room to live (current room if not specified)
|
||||
"""
|
||||
return self.cmd_leave(mess_data, profile)
|
||||
return self.cmd_leave(client, mess_data)
|
||||
|
||||
def cmd_kick(self, mess_data, profile):
|
||||
def cmd_kick(self, client, mess_data):
|
||||
"""kick a room member
|
||||
|
||||
@command (group): ROOM_NICK
|
||||
|
|
@ -536,24 +546,24 @@ class XEP_0045(object):
|
|||
options = mess_data["unparsed"].strip().split()
|
||||
try:
|
||||
nick = options[0]
|
||||
assert(self.isNickInRoom(mess_data["to"], nick, profile))
|
||||
assert(self.isNickInRoom(mess_data["to"], nick, client.profile))
|
||||
except (IndexError, AssertionError):
|
||||
feedback = _(u"You must provide a member's nick to kick.")
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data)
|
||||
return False
|
||||
|
||||
d = self.kick(nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, profile)
|
||||
d = self.kick(nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, client.profile)
|
||||
|
||||
def cb(dummy):
|
||||
feedback_msg = _(u'You have kicked {}').format(nick)
|
||||
if len(options) > 1:
|
||||
feedback_msg += _(u' for the following reason: {}').format(options[1])
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(feedback_msg, mess_data, profile)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback_msg, mess_data)
|
||||
return True
|
||||
d.addCallback(cb)
|
||||
return d
|
||||
|
||||
def cmd_ban(self, mess_data, profile):
|
||||
def cmd_ban(self, client, mess_data):
|
||||
"""ban an entity from the room
|
||||
|
||||
@command (group): (JID) [reason]
|
||||
|
|
@ -568,21 +578,21 @@ class XEP_0045(object):
|
|||
assert(entity_jid.host)
|
||||
except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError):
|
||||
feedback = _(u"You must provide a valid JID to ban, like in '/ban contact@example.net'")
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data)
|
||||
return False
|
||||
|
||||
d = self.ban(entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, profile)
|
||||
d = self.ban(entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}, client.profile)
|
||||
|
||||
def cb(dummy):
|
||||
feedback_msg = _(u'You have banned {}').format(entity_jid)
|
||||
if len(options) > 1:
|
||||
feedback_msg += _(u' for the following reason: {}').format(options[1])
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(feedback_msg, mess_data, profile)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback_msg, mess_data)
|
||||
return True
|
||||
d.addCallback(cb)
|
||||
return d
|
||||
|
||||
def cmd_affiliate(self, mess_data, profile):
|
||||
def cmd_affiliate(self, client, mess_data):
|
||||
"""affiliate an entity to the room
|
||||
|
||||
@command (group): (JID) [owner|admin|member|none|outcast]
|
||||
|
|
@ -601,25 +611,25 @@ class XEP_0045(object):
|
|||
assert(entity_jid.host)
|
||||
except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError):
|
||||
feedback = _(u"You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'")
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data)
|
||||
return False
|
||||
|
||||
affiliation = options[1] if len(options) > 1 else 'none'
|
||||
if affiliation not in AFFILIATIONS:
|
||||
feedback = _(u"You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(feedback, mess_data, profile)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data)
|
||||
return False
|
||||
|
||||
d = self.affiliate(entity_jid, mess_data["to"], {'affiliation': affiliation}, profile)
|
||||
d = self.affiliate(entity_jid, mess_data["to"], {'affiliation': affiliation}, client.profile)
|
||||
|
||||
def cb(dummy):
|
||||
feedback_msg = _(u'New affiliation for %(entity)s: %(affiliation)s').format(entity=entity_jid, affiliation=affiliation)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(feedback_msg, mess_data, profile)
|
||||
self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback_msg, mess_data)
|
||||
return True
|
||||
d.addCallback(cb)
|
||||
return d
|
||||
|
||||
def cmd_title(self, mess_data, profile):
|
||||
def cmd_title(self, client, mess_data):
|
||||
"""change room's subject
|
||||
|
||||
@command (group): title
|
||||
|
|
@ -629,28 +639,28 @@ class XEP_0045(object):
|
|||
|
||||
if subject:
|
||||
room = mess_data["to"]
|
||||
self.subject(room, subject, profile)
|
||||
self.subject(room, subject, client.profile)
|
||||
|
||||
return False
|
||||
|
||||
def cmd_topic(self, mess_data, profile):
|
||||
def cmd_topic(self, client, mess_data):
|
||||
"""just a synonym of /title
|
||||
|
||||
@command (group): title
|
||||
- title: new room subject
|
||||
"""
|
||||
return self.cmd_title(mess_data, profile)
|
||||
return self.cmd_title(client, mess_data)
|
||||
|
||||
def _whois(self, whois_msg, mess_data, target_jid, profile):
|
||||
def _whois(self, client, whois_msg, mess_data, target_jid):
|
||||
""" Add MUC user information to whois """
|
||||
if mess_data['type'] != "groupchat":
|
||||
return
|
||||
if target_jid.userhostJID() not in self.clients[profile].joined_rooms:
|
||||
if target_jid.userhostJID() not in self.clients[client.profile].joined_rooms:
|
||||
log.warning(_("This room has not been joined"))
|
||||
return
|
||||
if not target_jid.resource:
|
||||
return
|
||||
user = self.clients[profile].joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource)
|
||||
user = self.clients[client.profile].joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource)
|
||||
whois_msg.append(_("Nickname: %s") % user.nick)
|
||||
if user.entity:
|
||||
whois_msg.append(_("Entity: %s") % user.entity)
|
||||
|
|
@ -681,16 +691,40 @@ class SatMUCClient (muc.MUCClient):
|
|||
self.host = plugin_parent.host
|
||||
muc.MUCClient.__init__(self)
|
||||
self.rec_subjects = {}
|
||||
self.__changing_nicks = set() # used to keep trace of who is changing nick,
|
||||
# and to discard userJoinedRoom signal in this case
|
||||
self._changing_nicks = set() # used to keep trace of who is changing nick,
|
||||
# and to discard userJoinedRoom signal in this case
|
||||
print "init SatMUCClient OK"
|
||||
|
||||
@property
|
||||
def joined_rooms(self):
|
||||
return self._rooms
|
||||
|
||||
def subject(self, room, subject):
|
||||
return muc.MUCClientProtocol.subject(self, room, subject)
|
||||
def _addRoom(self, room):
|
||||
super(SatMUCClient, self)._addRoom(room)
|
||||
room._roster_ok = False
|
||||
room._room_ok = None # False when roster, history and subject are available
|
||||
# True when new messages are saved to database
|
||||
room._history_d = defer.Deferred() # use to send bridge signal once backlog are written in history
|
||||
room._history_d.callback(None)
|
||||
room._cache = []
|
||||
|
||||
def _gotLastDbHistory(self, mess_data_list, room_jid, nick, password):
|
||||
if mess_data_list:
|
||||
timestamp = mess_data_list[0][1]
|
||||
# we use seconds since last message to get backlog without duplicates
|
||||
# and we remove 1 second to avoid getting the last message again
|
||||
seconds = int(time.time() - timestamp) - 1
|
||||
else:
|
||||
seconds = None
|
||||
d = super(SatMUCClient, self).join(room_jid, nick, muc.HistoryOptions(seconds=seconds), password)
|
||||
return d
|
||||
|
||||
def join(self, room_jid, nick, password=None):
|
||||
d = self.host.memory.historyGet(self.parent.jid.userhostJID(), room_jid, 1, True, profile=self.parent.profile)
|
||||
d.addCallback(self._gotLastDbHistory, room_jid, nick, password)
|
||||
return d
|
||||
|
||||
## presence/roster ##
|
||||
|
||||
def availableReceived(self, presence):
|
||||
"""
|
||||
|
|
@ -698,7 +732,6 @@ class SatMUCClient (muc.MUCClient):
|
|||
"""
|
||||
# XXX: we override MUCClient.availableReceived to fix bugs
|
||||
# (affiliation and role are not set)
|
||||
# FIXME: propose a patch upstream
|
||||
|
||||
room, user = self._getRoomUser(presence)
|
||||
|
||||
|
|
@ -720,9 +753,9 @@ class SatMUCClient (muc.MUCClient):
|
|||
else:
|
||||
room.addUser(user)
|
||||
self.userJoinedRoom(room, user)
|
||||
|
||||
def unavailableReceived(self, presence):
|
||||
# XXX: we override this method to manage nickname change
|
||||
# TODO: feed this back to Wokkel
|
||||
"""
|
||||
Unavailable presence was received.
|
||||
|
||||
|
|
@ -737,22 +770,49 @@ class SatMUCClient (muc.MUCClient):
|
|||
room.removeUser(user)
|
||||
|
||||
if muc.STATUS_CODE.NEW_NICK in presence.mucStatuses:
|
||||
self.__changing_nicks.add(presence.nick)
|
||||
self._changing_nicks.add(presence.nick)
|
||||
self.userChangedNick(room, user, presence.nick)
|
||||
else:
|
||||
self.__changing_nicks.discard(presence.nick)
|
||||
self._changing_nicks.discard(presence.nick)
|
||||
self.userLeftRoom(room, user)
|
||||
|
||||
def userJoinedRoom(self, room, user):
|
||||
self.host.memory.updateEntityData(room.roomJID, "type", "chatroom", profile_key=self.parent.profile)
|
||||
if user.nick in self.__changing_nicks:
|
||||
self.__changing_nicks.remove(user.nick)
|
||||
else:
|
||||
log.debug(_(u"user %(nick)s has joined room (%(room_id)s)") % {'nick': user.nick, 'room_id': room.occupantJID.userhost()})
|
||||
if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile):
|
||||
return
|
||||
user_data = {'entity': user.entity.full() if user.entity else '', 'affiliation': user.affiliation, 'role': user.role}
|
||||
self.host.bridge.roomUserJoined(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
|
||||
if user.nick == room.nick:
|
||||
# we have received our own nick, this mean that the full room roster was received
|
||||
room._roster_ok = True
|
||||
log.debug(u"room {room} joined with nick {nick}".format(room=room.occupantJID.userhost(), nick=user.nick))
|
||||
# We set type so we don't have use a deferred with disco to check entity type
|
||||
self.host.memory.updateEntityData(room.roomJID, C.ENTITY_TYPE, ENTITY_TYPE_MUC, profile_key=self.parent.profile)
|
||||
|
||||
elif room._roster_ok:
|
||||
try:
|
||||
self._changing_nicks.remove(user.nick)
|
||||
except KeyError:
|
||||
# this is a new user
|
||||
log.debug(_(u"user {nick} has joined room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost()))
|
||||
if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile):
|
||||
return
|
||||
|
||||
extra = {'info_type': ROOM_USER_JOINED,
|
||||
'user_affiliation': user.affiliation,
|
||||
'user_role': user.role,
|
||||
'user_nick': user.nick
|
||||
}
|
||||
if user.entity is not None:
|
||||
extra['user_entity'] = user.entity.full()
|
||||
mess_data = { # dict is similar to the one used in client.onMessage
|
||||
"from": room.roomJID,
|
||||
"to": self.parent.jid,
|
||||
"uid": unicode(uuid.uuid4()),
|
||||
"message": {'': D_(u"=> {} has joined the room").format(user.nick)},
|
||||
"subject": {},
|
||||
"type": C.MESS_TYPE_INFO,
|
||||
"extra": extra,
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
self.host.messageAddToHistory(mess_data, self.parent)
|
||||
self.host.messageSendToBridge(mess_data, self.parent)
|
||||
|
||||
|
||||
def userLeftRoom(self, room, user):
|
||||
if not self.host.trigger.point("MUC user left", room, user, self.parent.profile):
|
||||
|
|
@ -760,14 +820,31 @@ class SatMUCClient (muc.MUCClient):
|
|||
if user.nick == room.nick:
|
||||
# we left the room
|
||||
room_jid_s = room.roomJID.userhost()
|
||||
log.info(_(u"Room [%(room)s] left (%(profile)s))") % {"room": room_jid_s,
|
||||
"profile": self.parent.profile})
|
||||
log.info(_(u"Room ({room}) left ({profile})").format(
|
||||
room = room_jid_s, profile = self.parent.profile))
|
||||
self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile)
|
||||
self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile)
|
||||
else:
|
||||
log.debug(_(u"user %(nick)s left room (%(room_id)s)") % {'nick': user.nick, 'room_id': room.occupantJID.userhost()})
|
||||
user_data = {'entity': user.entity.full() if user.entity else '', 'affiliation': user.affiliation, 'role': user.role}
|
||||
self.host.bridge.roomUserLeft(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
|
||||
log.debug(_(u"user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost()))
|
||||
extra = {'info_type': ROOM_USER_LEFT,
|
||||
'user_affiliation': user.affiliation,
|
||||
'user_role': user.role,
|
||||
'user_nick': user.nick
|
||||
}
|
||||
if user.entity is not None:
|
||||
extra['user_entity'] = user.entity.full()
|
||||
mess_data = { # dict is similar to the one used in client.onMessage
|
||||
"from": room.roomJID,
|
||||
"to": self.parent.jid,
|
||||
"uid": unicode(uuid.uuid4()),
|
||||
"message": {'': D_(u"<= {} has left the room").format(user.nick)},
|
||||
"subject": {},
|
||||
"type": C.MESS_TYPE_INFO,
|
||||
"extra": extra,
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
self.host.messageAddToHistory(mess_data, self.parent)
|
||||
self.host.messageSendToBridge(mess_data, self.parent)
|
||||
|
||||
def userChangedNick(self, room, user, new_nick):
|
||||
self.host.bridge.roomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile)
|
||||
|
|
@ -775,19 +852,111 @@ class SatMUCClient (muc.MUCClient):
|
|||
def userUpdatedStatus(self, room, user, show, status):
|
||||
self.host.bridge.presenceUpdate(room.roomJID.userhost() + '/' + user.nick, show or '', 0, {C.PRESENCE_STATUSES_DEFAULT: status or ''}, self.parent.profile)
|
||||
|
||||
## messages ##
|
||||
|
||||
def receivedGroupChat(self, room, user, body):
|
||||
log.debug(u'receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body))
|
||||
|
||||
def _addToHistory(self, dummy, user, message):
|
||||
# we check if message is not in history
|
||||
# and raise ConflictError else
|
||||
stamp = message.delay.stamp.astimezone(tzutc()).timetuple()
|
||||
timestamp = float(calendar.timegm(stamp))
|
||||
data = { # dict is similar to the one used in client.onMessage
|
||||
"from": message.sender,
|
||||
"to": message.recipient,
|
||||
"uid": unicode(uuid.uuid4()),
|
||||
"type": C.MESS_TYPE_GROUPCHAT,
|
||||
"extra": {},
|
||||
"timestamp": timestamp,
|
||||
"received_timestamp": unicode(time.time()),
|
||||
}
|
||||
# FIXME: message and subject don't handle xml:lang
|
||||
data['message'] = {'': message.body} if message.body is not None else {}
|
||||
data['subject'] = {'': message.subject} if message.subject is not None else {}
|
||||
|
||||
if data['message'] or data['subject']:
|
||||
return self.host.memory.addToHistory(self.parent, data)
|
||||
else:
|
||||
return defer.succeed(None)
|
||||
|
||||
def _addToHistoryEb(self, failure):
|
||||
failure.trap(exceptions.CancelError)
|
||||
|
||||
def receivedHistory(self, room, user, message):
|
||||
# http://xmpp.org/extensions/xep-0045.html#enter-history
|
||||
# log.debug(u'receivedHistory: room=%s user=%s body=%s' % (room.roomJID.full(), user, message))
|
||||
pass
|
||||
"""Called when history (backlog) message are received
|
||||
|
||||
we check if message is not already in our history
|
||||
and add it if needed
|
||||
@param room(muc.Room): room instance
|
||||
@param user(muc.User, None): the user that sent the message
|
||||
None if the message come from the room
|
||||
@param message(muc.GroupChat): the parsed message
|
||||
"""
|
||||
room._history_d.addCallback(self._addToHistory, user, message)
|
||||
room._history_d.addErrback(self._addToHistoryEb)
|
||||
|
||||
## subject ##
|
||||
|
||||
def groupChatReceived(self, message):
|
||||
"""
|
||||
A group chat message has been received from a MUC room.
|
||||
|
||||
There are a few event methods that may get called here.
|
||||
L{receivedGroupChat}, L{receivedSubject} or L{receivedHistory}.
|
||||
"""
|
||||
# We override this method to fix subject handling
|
||||
# FIXME: remove this merge fixed upstream
|
||||
room, user = self._getRoomUser(message)
|
||||
|
||||
if room is None:
|
||||
return
|
||||
|
||||
if message.subject is not None:
|
||||
self.receivedSubject(room, user, message.subject)
|
||||
elif message.delay is None:
|
||||
self.receivedGroupChat(room, user, message)
|
||||
else:
|
||||
self.receivedHistory(room, user, message)
|
||||
|
||||
def subject(self, room, subject):
|
||||
return muc.MUCClientProtocol.subject(self, room, subject)
|
||||
|
||||
def _historyCb(self, dummy, room):
|
||||
self.host.bridge.roomJoined(
|
||||
room.roomJID.userhost(),
|
||||
XEP_0045._getOccupants(room),
|
||||
room.nick,
|
||||
room.subject,
|
||||
self.parent.profile)
|
||||
del room._history_d
|
||||
cache = room._cache
|
||||
del room._cache
|
||||
room._room_ok = True
|
||||
for elem in cache:
|
||||
self.parent.xmlstream.dispatch(elem)
|
||||
|
||||
|
||||
def _historyEb(self, failure_, room):
|
||||
log.error(u"Error while managing history: {}".format(failure_))
|
||||
self._historyCb(None, room)
|
||||
|
||||
def receivedSubject(self, room, user, subject):
|
||||
# http://xmpp.org/extensions/xep-0045.html#enter-subject
|
||||
log.debug(_(u"New subject for room (%(room_id)s): %(subject)s") % {'room_id': room.roomJID.full(), 'subject': subject})
|
||||
# when subject is received, we know that we have whole roster and history
|
||||
# cf. http://xmpp.org/extensions/xep-0045.html#enter-subject
|
||||
room.subject = subject # FIXME: subject doesn't handle xml:lang
|
||||
self.rec_subjects[room.roomJID.userhost()] = (room.roomJID.userhost(), subject)
|
||||
self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile)
|
||||
if room._room_ok is None:
|
||||
# this is the first subject we receive
|
||||
# that mean that we have received everything we need
|
||||
room._room_ok = False
|
||||
room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room])
|
||||
else:
|
||||
# the subject has been changed
|
||||
log.debug(_(u"New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject))
|
||||
self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile)
|
||||
|
||||
## disco ##
|
||||
|
||||
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
|
||||
return [disco.DiscoFeature(NS_MUC)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue