merged branch frontends_multi_profiles
This commit is contained in:
commit
6760c927d3
4 changed files with 4611 additions and 4514 deletions
|
|
@ -70,18 +70,36 @@ class Const(object):
|
|||
MENU_JID_CONTEXT = "JID_CONTEXT"
|
||||
MENU_ROSTER_JID_CONTEXT = "ROSTER_JID_CONTEXT"
|
||||
MENU_ROSTER_GROUP_CONTEXT = "MENU_ROSTER_GROUP_CONTEXT"
|
||||
MENU_ROOM_OCCUPANT_CONTEXT = "MENU_ROOM_OCCUPANT_CONTEXT"
|
||||
|
||||
|
||||
## Profile and entities ##
|
||||
PROF_KEY_NONE = '@NONE@'
|
||||
PROF_KEY_DEFAULT = '@DEFAULT@'
|
||||
ENTITY_ALL = '@ALL@'
|
||||
ENTITY_LAST_RESOURCE = 'LAST_RESOURCE'
|
||||
ENTITY_ALL_RESOURCES = '@ALL_RESOURCES@'
|
||||
ENTITY_MAIN_RESOURCE = '@MAIN_RESOURCE@'
|
||||
ENTITY_CAP_HASH = 'CAP_HASH'
|
||||
|
||||
|
||||
## Messages ##
|
||||
MESS_TYPE_INFO = 'info'
|
||||
MESS_TYPE_CHAT = 'chat'
|
||||
MESS_TYPE_ERROR = 'error'
|
||||
MESS_TYPE_GROUPCHAT = 'groupchat'
|
||||
MESS_TYPE_HEADLINE = 'headline'
|
||||
MESS_TYPE_NORMAL = 'normal'
|
||||
|
||||
## PRESENCE ##
|
||||
PRESENCE_UNAVAILABLE = 'unavailable'
|
||||
PRESENCE_SHOW_AWAY = 'away'
|
||||
PRESENCE_SHOW_CHAT = 'chat'
|
||||
PRESENCE_SHOW_DND = 'dnd'
|
||||
PRESENCE_SHOW_XA = 'xa'
|
||||
PRESENCE_SHOW = 'show'
|
||||
PRESENCE_STATUSES = 'statuses'
|
||||
PRESENCE_PRIORITY = 'priority'
|
||||
|
||||
|
||||
## Configuration ##
|
||||
if BaseDirectory: # skipped when xdg module is not available (should not happen in backend)
|
||||
|
|
@ -202,14 +220,12 @@ class Const(object):
|
|||
|
||||
@classmethod
|
||||
def bool(cls, value):
|
||||
"""@return: bool value for any type"""
|
||||
if isinstance(value, str) or isinstance(value, unicode): # dbus.String is unicode but not str
|
||||
return value.lower() == cls.BOOL_TRUE
|
||||
return bool(value)
|
||||
"""@return (bool): bool value for associated constant"""
|
||||
assert isinstance(value, basestring)
|
||||
return value.lower() == cls.BOOL_TRUE
|
||||
|
||||
@classmethod
|
||||
def str(cls, value):
|
||||
"""@return: str text value for any type"""
|
||||
if isinstance(value, bool):
|
||||
return cls.BOOL_TRUE if value else cls.BOOL_FALSE
|
||||
return str(value)
|
||||
def boolConst(cls, value):
|
||||
"""@return (str): constant associated to bool value"""
|
||||
assert isinstance(value, bool)
|
||||
return cls.BOOL_TRUE if value else cls.BOOL_FALSE
|
||||
|
|
|
|||
|
|
@ -93,5 +93,10 @@ class DatabaseError(Exception):
|
|||
class PasswordError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PermissionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SkipHistory(Exception): # used in MessageReceivedTrigger to avoid history writting
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -83,14 +83,15 @@ class SAT(service.Service):
|
|||
self.bridge.register("getVersion", lambda: C.APP_VERSION)
|
||||
self.bridge.register("getProfileName", self.memory.getProfileName)
|
||||
self.bridge.register("getProfilesList", self.memory.getProfilesList)
|
||||
self.bridge.register("getEntityData", lambda _jid, keys, profile: self.memory.getEntityData(jid.JID(_jid), keys, profile))
|
||||
self.bridge.register("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile))
|
||||
self.bridge.register("getEntitiesData", self.memory._getEntitiesData)
|
||||
self.bridge.register("asyncCreateProfile", self.memory.asyncCreateProfile)
|
||||
self.bridge.register("asyncDeleteProfile", self.memory.asyncDeleteProfile)
|
||||
self.bridge.register("asyncConnect", self.asyncConnect)
|
||||
self.bridge.register("disconnect", self.disconnect)
|
||||
self.bridge.register("getContacts", self.getContacts)
|
||||
self.bridge.register("getContactsFromGroup", self.getContactsFromGroup)
|
||||
self.bridge.register("getLastResource", self.memory._getLastResource)
|
||||
self.bridge.register("getMainResource", self.memory._getMainResource)
|
||||
self.bridge.register("getPresenceStatuses", self.memory._getPresenceStatuses)
|
||||
self.bridge.register("getWaitingSub", self.memory.getWaitingSub)
|
||||
self.bridge.register("getWaitingConf", self.getWaitingConf)
|
||||
|
|
@ -264,9 +265,13 @@ class SAT(service.Service):
|
|||
for plugin in self.plugins.iteritems():
|
||||
if plugin[1].is_handler:
|
||||
plugin[1].getHandler(profile).setHandlerParent(current)
|
||||
connected_cb = getattr(plugin[1], "profileConnected", None)
|
||||
connected_cb = getattr(plugin[1], "profileConnected", None) # profile connected is called after client is ready and roster is got
|
||||
if connected_cb:
|
||||
plugin_conn_cb.append((plugin[0], connected_cb))
|
||||
try:
|
||||
yield plugin[1].profileConnecting(profile) # profile connecting is called before actually starting client
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
current.startService()
|
||||
|
||||
|
|
@ -289,7 +294,7 @@ class SAT(service.Service):
|
|||
{'name': plugin_conn_cb[idx][0], 'failure': result})
|
||||
|
||||
yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze
|
||||
# TODO: mesure time to launch of each plugin
|
||||
# TODO: mesure launch time of each plugin
|
||||
|
||||
def _authenticateProfile(self, password, profile):
|
||||
"""Authenticate the profile.
|
||||
|
|
@ -331,12 +336,15 @@ class SAT(service.Service):
|
|||
|
||||
def getContacts(self, profile_key):
|
||||
client = self.getClient(profile_key)
|
||||
ret = []
|
||||
for item in client.roster.getItems(): # we get all items for client's roster
|
||||
# and convert them to expected format
|
||||
attr = client.roster.getAttributes(item)
|
||||
ret.append([item.jid.userhost(), attr, item.groups])
|
||||
return ret
|
||||
def got_roster(dummy):
|
||||
ret = []
|
||||
for item in client.roster.getItems(): # we get all items for client's roster
|
||||
# and convert them to expected format
|
||||
attr = client.roster.getAttributes(item)
|
||||
ret.append([item.jid.userhost(), attr, item.groups])
|
||||
return ret
|
||||
|
||||
return client.roster.got_roster.addCallback(got_roster)
|
||||
|
||||
def getContactsFromGroup(self, group, profile_key):
|
||||
client = self.getClient(profile_key)
|
||||
|
|
@ -456,7 +464,7 @@ class SAT(service.Service):
|
|||
def sendMessage(self, to_jid, msg, subject=None, mess_type='auto', extra={}, no_trigger=False, profile_key=C.PROF_KEY_NONE):
|
||||
#FIXME: check validity of recipient
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
assert profile
|
||||
client = self.profiles[profile]
|
||||
if extra is None:
|
||||
extra = {}
|
||||
|
|
@ -479,7 +487,7 @@ class SAT(service.Service):
|
|||
# we may have a groupchat message, we check if the we know this jid
|
||||
try:
|
||||
entity_type = self.memory.getEntityData(mess_data["to"], ['type'], profile)["type"]
|
||||
#FIXME: should entity_type manage ressources ?
|
||||
#FIXME: should entity_type manage resources ?
|
||||
except (exceptions.UnknownEntityError, KeyError):
|
||||
entity_type = "contact"
|
||||
|
||||
|
|
@ -497,7 +505,7 @@ class SAT(service.Service):
|
|||
if not self.trigger.point("sendMessage", mess_data, pre_xml_treatments, post_xml_treatments, profile):
|
||||
return defer.succeed(None)
|
||||
|
||||
log.debug(_("Sending jabber message of type [%(type)s] to %(to)s...") % {"type": mess_data["type"], "to": to_jid.full()})
|
||||
log.debug(_(u"Sending message (type {type}, to {to})").format(type=mess_data["type"], to=to_jid.full()))
|
||||
|
||||
def cancelErrorTrap(failure):
|
||||
"""A message sending can be cancelled by a plugin treatment"""
|
||||
|
|
@ -572,7 +580,7 @@ class SAT(service.Service):
|
|||
if statuses is None:
|
||||
statuses = {}
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
assert profile
|
||||
priority = int(self.memory.getParamA("Priority", "Connection", profile_key=profile))
|
||||
self.profiles[profile].presence.available(to_jid, show, statuses, priority)
|
||||
#XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
|
||||
|
|
@ -588,7 +596,7 @@ class SAT(service.Service):
|
|||
@param raw_jid: unicode entity's jid
|
||||
@param profile_key: profile"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
assert profile
|
||||
to_jid = jid.JID(raw_jid)
|
||||
log.debug(_('subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type': subs_type, 'jid': to_jid.full()})
|
||||
if subs_type == "subscribe":
|
||||
|
|
@ -606,8 +614,8 @@ class SAT(service.Service):
|
|||
def addContact(self, to_jid, profile_key):
|
||||
"""Add a contact in roster list"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
#self.profiles[profile].roster.addItem(to_jid) #XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
|
||||
assert profile
|
||||
# presence is sufficient, as a roster push will be sent according to RFC 6121 §3.1.2
|
||||
self.profiles[profile].presence.subscribe(to_jid)
|
||||
|
||||
def _updateContact(self, to_jid_s, name, groups, profile_key):
|
||||
|
|
@ -616,12 +624,12 @@ class SAT(service.Service):
|
|||
def updateContact(self, to_jid, name, groups, profile_key):
|
||||
"""update a contact in roster list"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
assert profile
|
||||
groups = set(groups)
|
||||
roster_item = RosterItem(to_jid)
|
||||
roster_item.name = name or None
|
||||
roster_item.groups = set(groups)
|
||||
self.profiles[profile].roster.updateItem(roster_item)
|
||||
return self.profiles[profile].roster.setItem(roster_item)
|
||||
|
||||
def _delContact(self, to_jid_s, profile_key):
|
||||
return self.delContact(jid.JID(to_jid_s), profile_key)
|
||||
|
|
@ -629,10 +637,9 @@ class SAT(service.Service):
|
|||
def delContact(self, to_jid, profile_key):
|
||||
"""Remove contact from roster list"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
assert(profile)
|
||||
self.profiles[profile].roster.removeItem(to_jid)
|
||||
self.profiles[profile].presence.unsubscribe(to_jid)
|
||||
|
||||
assert profile
|
||||
self.profiles[profile].presence.unsubscribe(to_jid) # is not asynchronous
|
||||
return self.profiles[profile].roster.removeItem(to_jid)
|
||||
|
||||
## Discovery ##
|
||||
# discovery methods are shortcuts to self.memory.disco
|
||||
|
|
@ -770,6 +777,9 @@ class SAT(service.Service):
|
|||
@profile_key: %(doc_profile_key)s
|
||||
@return: a deferred which fire a dict where key can be:
|
||||
- xmlui: a XMLUI need to be displayed
|
||||
- validated: if present, can be used to launch a callback, it can have the values
|
||||
- C.BOOL_TRUE
|
||||
- C.BOOL_FALSE
|
||||
"""
|
||||
profile = self.memory.getProfileName(profile_key)
|
||||
if not profile:
|
||||
|
|
@ -848,6 +858,7 @@ class SAT(service.Service):
|
|||
|
||||
def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT):
|
||||
"""Return all menus registered
|
||||
|
||||
@param language: language used for translation, or empty string for default
|
||||
@param security_limit: %(doc_security_limit)s
|
||||
@return: array of tuple with:
|
||||
|
|
@ -855,7 +866,9 @@ class SAT(service.Service):
|
|||
- menu type
|
||||
- raw menu path (array of strings)
|
||||
- translated menu path
|
||||
|
||||
- extra (dict(unicode, unicode)): extra data where key can be:
|
||||
- icon: name of the icon to use (TODO)
|
||||
- help_url: link to a page with more complete documentation (TODO)
|
||||
"""
|
||||
ret = []
|
||||
for menu_id, menu_data in self._menus.iteritems():
|
||||
|
|
@ -867,7 +880,8 @@ class SAT(service.Service):
|
|||
languageSwitch(language)
|
||||
path_i18n = [_(elt) for elt in path]
|
||||
languageSwitch()
|
||||
ret.append((menu_id, type_, path, path_i18n))
|
||||
extra = {} # TODO: manage extra data like icon
|
||||
ret.append((menu_id, type_, path, path_i18n, extra))
|
||||
|
||||
return ret
|
||||
|
||||
|
|
|
|||
134
src/core/xmpp.py
134
src/core/xmpp.py
|
|
@ -174,14 +174,42 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
|
|||
def __init__(self, host):
|
||||
xmppim.RosterClientProtocol.__init__(self)
|
||||
self.host = host
|
||||
self.got_roster = defer.Deferred()
|
||||
self.got_roster = defer.Deferred() # called when roster is received and ready
|
||||
#XXX: the two following dicts keep a local copy of the roster
|
||||
self._groups = {} # map from groups to bare jids: key=group value=set of bare jids
|
||||
self._jids = {} # map from bare jids to RosterItem: key=jid value=RosterItem
|
||||
self._groups = {} # map from groups to jids: key=group value=set of jids
|
||||
self._jids = None # map from jids to RosterItem: key=jid value=RosterItem
|
||||
|
||||
def rosterCb(self, roster):
|
||||
for raw_jid, item in roster.iteritems():
|
||||
self.onRosterSet(item)
|
||||
assert roster is not None # FIXME: must be managed with roster versioning
|
||||
self._jids = roster
|
||||
for roster_item in roster.itervalues():
|
||||
self._registerItem(roster_item)
|
||||
|
||||
def _registerItem(self, item):
|
||||
"""Register item in local cache
|
||||
|
||||
item must be already registered in self._jids before this method is called
|
||||
@param item (RosterIem): item added
|
||||
"""
|
||||
log.debug(u"registering item: {}".format(item.jid.full()))
|
||||
if item.entity.resource:
|
||||
log.warning(u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.")
|
||||
if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
|
||||
#XXX: current behaviour: we don't want contact in our roster list
|
||||
# if there is no presence subscription
|
||||
# may change in the future
|
||||
self.removeItem(item.jid) # FIXME: to be checked
|
||||
return
|
||||
if not item.subscriptionTo:
|
||||
if not item.subscriptionFrom:
|
||||
log.info(_(u"There's no subscription between you and [{}]!").format(item.jid.full()))
|
||||
else:
|
||||
log.info(_(u"You are not subscribed to [{}]!").format(item.jid.full()))
|
||||
if not item.subscriptionFrom:
|
||||
log.info(_(u"[{}] is not subscribed to you!").format(item.jid.full()))
|
||||
|
||||
for group in item.groups:
|
||||
self._groups.setdefault(group, set()).add(item.entity)
|
||||
|
||||
def requestRoster(self):
|
||||
""" ask the server for Roster list """
|
||||
|
|
@ -192,31 +220,9 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
|
|||
def removeItem(self, to_jid):
|
||||
"""Remove a contact from roster list
|
||||
@param to_jid: a JID instance
|
||||
@return: Deferred
|
||||
"""
|
||||
xmppim.RosterClientProtocol.removeItem(self, to_jid)
|
||||
#TODO: check IQ result
|
||||
|
||||
#XXX: disabled (cf http://wokkel.ik.nu/ticket/56))
|
||||
#def addItem(self, to):
|
||||
#"""Add a contact to roster list"""
|
||||
#xmppim.RosterClientProtocol.addItem(self, to)
|
||||
#TODO: check IQ result"""
|
||||
|
||||
def updateItem(self, roster_item):
|
||||
"""
|
||||
Update an item of the contact list.
|
||||
|
||||
@param roster_item: item to update
|
||||
"""
|
||||
iq = compat.IQ(self.xmlstream, 'set')
|
||||
iq.addElement((xmppim.NS_ROSTER, 'query'))
|
||||
item = iq.query.addElement('item')
|
||||
item['jid'] = roster_item.jid.userhost()
|
||||
if roster_item.name:
|
||||
item['name'] = roster_item.name
|
||||
for group in roster_item.groups:
|
||||
item.addElement('group', content=group)
|
||||
return iq.send()
|
||||
return xmppim.RosterClientProtocol.removeItem(self, to_jid)
|
||||
|
||||
def getAttributes(self, item):
|
||||
"""Return dictionary of attributes as used in bridge from a RosterItem
|
||||
|
|
@ -230,72 +236,54 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
|
|||
item_attr['name'] = item.name
|
||||
return item_attr
|
||||
|
||||
def onRosterSet(self, item):
|
||||
"""Called when a new/update roster item is received"""
|
||||
#TODO: send a signal to frontends
|
||||
if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
|
||||
#XXX: current behaviour: we don't want contact in our roster list
|
||||
# if there is no presence subscription
|
||||
# may change in the future
|
||||
self.removeItem(item.jid)
|
||||
return
|
||||
log.debug(_("New contact in roster list: %s") % item.jid.full())
|
||||
if not item.subscriptionTo:
|
||||
if not item.subscriptionFrom:
|
||||
log.info(_("There's no subscription between you and [%s]!") % item.jid.full())
|
||||
else:
|
||||
log.info(_("You are not subscribed to [%s]!") % item.jid.full())
|
||||
if not item.subscriptionFrom:
|
||||
log.info(_("[%s] is not subscribed to you!") % item.jid.full())
|
||||
#self.host.memory.addContact(item.jid, item_attr, item.groups, self.parent.profile)
|
||||
|
||||
bare_jid = item.jid.userhostJID()
|
||||
self._jids[bare_jid] = item
|
||||
for group in item.groups:
|
||||
self._groups.setdefault(group, set()).add(bare_jid)
|
||||
def setReceived(self, request):
|
||||
#TODO: implement roster versioning (cf RFC 6121 §2.6)
|
||||
item = request.item
|
||||
self._jids[item.entity] = item
|
||||
self.host.bridge.newContact(item.jid.full(), self.getAttributes(item), item.groups, self.parent.profile)
|
||||
|
||||
def onRosterRemove(self, entity):
|
||||
"""Called when a roster removal event is received"""
|
||||
print _("removing %s from roster list") % entity.full()
|
||||
bare_jid = entity.userhostJID()
|
||||
def removeReceived(self, request):
|
||||
entity = request.item.entity
|
||||
log.info(u"removing %s from roster list" % entity.full())
|
||||
|
||||
# we first remove item from local cache (self._groups and self._jids)
|
||||
try:
|
||||
item = self._jids.pop(bare_jid)
|
||||
item = self._jids.pop(entity)
|
||||
except KeyError:
|
||||
log.warning("Received a roster remove event for an item not in cache")
|
||||
log.error("Received a roster remove event for an item not in cache ({})".format(entity))
|
||||
return
|
||||
for group in item.groups:
|
||||
try:
|
||||
jids_set = self._groups[group]
|
||||
jids_set.remove(bare_jid)
|
||||
jids_set.remove(entity)
|
||||
if not jids_set:
|
||||
del self._groups[group]
|
||||
except KeyError:
|
||||
log.warning("there is not cache for the group [%(groups)s] of the removed roster item [%(jid)s]" %
|
||||
{"group": group, "jid": bare_jid})
|
||||
{"group": group, "jid": entity})
|
||||
|
||||
# then we send the bridge signal
|
||||
self.host.bridge.contactDeleted(entity.userhost(), self.parent.profile)
|
||||
self.host.bridge.contactDeleted(entity.full(), self.parent.profile)
|
||||
|
||||
def getGroups(self):
|
||||
"""Return a list of groups"""
|
||||
return self._groups.keys()
|
||||
|
||||
def getItem(self, jid):
|
||||
def getItem(self, entity_jid):
|
||||
"""Return RosterItem for a given jid
|
||||
@param jid: jid of the contact
|
||||
@return: RosterItem or None if contact is not in cache"""
|
||||
return self._jids.get(jid.userhostJID(), None)
|
||||
|
||||
def getBareJids(self):
|
||||
"""Return all bare jids (as unicode) of the roster"""
|
||||
@param entity_jid: jid of the contact
|
||||
@return: RosterItem or None if contact is not in cache
|
||||
"""
|
||||
return self._jids.get(entity_jid, None)
|
||||
|
||||
def getJids(self):
|
||||
"""Return all jids of the roster"""
|
||||
return self._jids.keys()
|
||||
|
||||
def isJidInRoster(self, entity_jid):
|
||||
"""Return True if jid is in roster"""
|
||||
return entity_jid.userhostJID() in self._jids
|
||||
return entity_jid in self._jids
|
||||
|
||||
def getItems(self):
|
||||
"""Return all items of the roster"""
|
||||
|
|
@ -305,7 +293,7 @@ class SatRosterProtocol(xmppim.RosterClientProtocol):
|
|||
try:
|
||||
return self._groups[group]
|
||||
except KeyError:
|
||||
raise exceptions.UnknownGroupError
|
||||
raise exceptions.UnknownGroupError(group)
|
||||
|
||||
|
||||
class SatPresenceProtocol(xmppim.PresenceClientProtocol):
|
||||
|
|
@ -320,7 +308,7 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
|
|||
super(SatPresenceProtocol, self).send(obj)
|
||||
|
||||
def availableReceived(self, entity, show=None, statuses=None, priority=0):
|
||||
log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, 'show': show, 'statuses': statuses, 'priority': priority})
|
||||
log.debug(_("presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, C.PRESENCE_SHOW: show, C.PRESENCE_STATUSES: statuses, C.PRESENCE_PRIORITY: priority})
|
||||
|
||||
if not statuses:
|
||||
statuses = {}
|
||||
|
|
@ -333,17 +321,13 @@ class SatPresenceProtocol(xmppim.PresenceClientProtocol):
|
|||
int(priority), statuses,
|
||||
self.parent.profile)
|
||||
|
||||
# uncomment these two lines if you need the trigger
|
||||
#if not self.host.trigger.point("presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile):
|
||||
# return
|
||||
|
||||
# now it's time to notify frontends
|
||||
self.host.bridge.presenceUpdate(entity.full(), show or "",
|
||||
int(priority), statuses,
|
||||
self.parent.profile)
|
||||
|
||||
def unavailableReceived(self, entity, statuses=None):
|
||||
log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, 'statuses': statuses})
|
||||
log.debug(_("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, C.PRESENCE_STATUSES: statuses})
|
||||
|
||||
if not statuses:
|
||||
statuses = {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue