Add roster implementation to Gajim
This commit is contained in:
parent
7ad6a28e12
commit
db77fa1ace
|
@ -301,6 +301,8 @@ class AccountsWindow(Gtk.ApplicationWindow):
|
||||||
app.interface.roster.regroup = app.config.get('mergeaccounts')
|
app.interface.roster.regroup = app.config.get('mergeaccounts')
|
||||||
else:
|
else:
|
||||||
app.interface.roster.regroup = False
|
app.interface.roster.regroup = False
|
||||||
|
app.config.set_per(
|
||||||
|
'accounts', account, 'roster_version', '')
|
||||||
app.interface.roster.setup_and_draw_roster()
|
app.interface.roster.setup_and_draw_roster()
|
||||||
gui_menu_builder.build_accounts_menu()
|
gui_menu_builder.build_accounts_menu()
|
||||||
|
|
||||||
|
|
|
@ -387,15 +387,15 @@ class CommonConnection:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def update_contact(self, jid, name, groups):
|
def update_contact(self, jid, name, groups):
|
||||||
if self.connection and self.roster_supported:
|
if self.connection:
|
||||||
self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
|
self.getRoster().setItem(jid=jid, name=name, groups=groups)
|
||||||
|
|
||||||
def update_contacts(self, contacts):
|
def update_contacts(self, contacts):
|
||||||
"""
|
"""
|
||||||
Update multiple roster items
|
Update multiple roster items
|
||||||
"""
|
"""
|
||||||
if self.connection and self.roster_supported:
|
if self.connection:
|
||||||
self.connection.getRoster().setItemMulti(contacts)
|
self.getRoster().setItemMulti(contacts)
|
||||||
|
|
||||||
def new_account(self, name, config, sync=False):
|
def new_account(self, name, config, sync=False):
|
||||||
"""
|
"""
|
||||||
|
@ -443,10 +443,6 @@ class CommonConnection:
|
||||||
return self.gpg.get_secret_keys()
|
return self.gpg.get_secret_keys()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def load_roster_from_db(self):
|
|
||||||
# Do nothing by default
|
|
||||||
return
|
|
||||||
|
|
||||||
def _event_dispatcher(self, realm, event, data):
|
def _event_dispatcher(self, realm, event, data):
|
||||||
if realm == '':
|
if realm == '':
|
||||||
if event == 'STANZA_RECEIVED':
|
if event == 'STANZA_RECEIVED':
|
||||||
|
@ -1622,7 +1618,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
iq.setID(id_)
|
iq.setID(id_)
|
||||||
self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
|
self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
|
||||||
self.connection.send(iq)
|
self.connection.send(iq)
|
||||||
self.connection.getRoster().delItem(agent)
|
self.getRoster().delItem(agent)
|
||||||
|
|
||||||
def send_new_account_infos(self, form, is_form):
|
def send_new_account_infos(self, form, is_form):
|
||||||
if is_form:
|
if is_form:
|
||||||
|
@ -1747,6 +1743,9 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
iq3.addChild(name='meta', attrs=dict_)
|
iq3.addChild(name='meta', attrs=dict_)
|
||||||
self.connection.send(iq)
|
self.connection.send(iq)
|
||||||
|
|
||||||
|
def getRoster(self):
|
||||||
|
return self.get_module('Roster')
|
||||||
|
|
||||||
def request_roster(self, resume=False):
|
def request_roster(self, resume=False):
|
||||||
version = None
|
version = None
|
||||||
features = self.connection.Dispatcher.Stream.features
|
features = self.connection.Dispatcher.Stream.features
|
||||||
|
@ -1754,18 +1753,8 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
version = app.config.get_per(
|
version = app.config.get_per(
|
||||||
'accounts', self.name, 'roster_version')
|
'accounts', self.name, 'roster_version')
|
||||||
|
|
||||||
iq_id = self.connection.initRoster(version=version,
|
if not resume:
|
||||||
request=not resume)
|
self.get_module('Roster').request_roster(version)
|
||||||
if resume:
|
|
||||||
self._init_roster_from_db()
|
|
||||||
else:
|
|
||||||
self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
|
|
||||||
|
|
||||||
def _init_roster_from_db(self):
|
|
||||||
account_jid = app.get_jid_from_account(self.name)
|
|
||||||
roster_data = app.logger.get_roster(account_jid)
|
|
||||||
roster = self.connection.getRoster(force=True)
|
|
||||||
roster.setRaw(roster_data)
|
|
||||||
|
|
||||||
def send_agent_status(self, agent, ptype):
|
def send_agent_status(self, agent, ptype):
|
||||||
if not app.account_is_connected(self.name):
|
if not app.account_is_connected(self.name):
|
||||||
|
@ -2056,8 +2045,3 @@ class Connection(CommonConnection, ConnectionHandlers):
|
||||||
self.reconnect()
|
self.reconnect()
|
||||||
else:
|
else:
|
||||||
self.time_to_reconnect = None
|
self.time_to_reconnect = None
|
||||||
|
|
||||||
def load_roster_from_db(self):
|
|
||||||
app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self))
|
|
||||||
|
|
||||||
# END Connection
|
|
||||||
|
|
|
@ -468,19 +468,11 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
|
||||||
app.nec.register_incoming_event(StreamConflictReceivedEvent)
|
app.nec.register_incoming_event(StreamConflictReceivedEvent)
|
||||||
app.nec.register_incoming_event(NotificationEvent)
|
app.nec.register_incoming_event(NotificationEvent)
|
||||||
|
|
||||||
app.ged.register_event_handler('roster-set-received',
|
|
||||||
ged.CORE, self._nec_roster_set_received)
|
|
||||||
app.ged.register_event_handler('roster-received', ged.CORE,
|
|
||||||
self._nec_roster_received)
|
|
||||||
app.ged.register_event_handler('agent-removed', ged.CORE,
|
app.ged.register_event_handler('agent-removed', ged.CORE,
|
||||||
self._nec_agent_removed)
|
self._nec_agent_removed)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
ConnectionHandlersBase.cleanup(self)
|
ConnectionHandlersBase.cleanup(self)
|
||||||
app.ged.remove_event_handler('roster-set-received',
|
|
||||||
ged.CORE, self._nec_roster_set_received)
|
|
||||||
app.ged.remove_event_handler('roster-received', ged.CORE,
|
|
||||||
self._nec_roster_received)
|
|
||||||
app.ged.remove_event_handler('agent-removed', ged.CORE,
|
app.ged.remove_event_handler('agent-removed', ged.CORE,
|
||||||
self._nec_agent_removed)
|
self._nec_agent_removed)
|
||||||
|
|
||||||
|
@ -555,41 +547,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
|
||||||
|
|
||||||
# We can now continue connection by requesting the roster
|
# We can now continue connection by requesting the roster
|
||||||
self.request_roster()
|
self.request_roster()
|
||||||
elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
|
|
||||||
if iq_obj.getType() == 'result':
|
|
||||||
if not iq_obj.getTag('query'):
|
|
||||||
self._init_roster_from_db()
|
|
||||||
self._getRoster()
|
|
||||||
elif iq_obj.getType() == 'error':
|
|
||||||
self.roster_supported = False
|
|
||||||
self.get_module('Discovery').discover_server_items()
|
|
||||||
if app.config.get_per('accounts', self.name,
|
|
||||||
'use_ft_proxies'):
|
|
||||||
self.discover_ft_proxies()
|
|
||||||
app.nec.push_incoming_event(RosterReceivedEvent(None,
|
|
||||||
conn=self))
|
|
||||||
del self.awaiting_answers[id_]
|
|
||||||
|
|
||||||
def _rosterSetCB(self, con, iq_obj):
|
|
||||||
log.debug('rosterSetCB')
|
|
||||||
app.nec.push_incoming_event(RosterSetReceivedEvent(None, conn=self,
|
|
||||||
stanza=iq_obj))
|
|
||||||
raise nbxmpp.NodeProcessed
|
|
||||||
|
|
||||||
def _nec_roster_set_received(self, obj):
|
|
||||||
if obj.conn.name != self.name:
|
|
||||||
return
|
|
||||||
for jid in obj.items:
|
|
||||||
item = obj.items[jid]
|
|
||||||
app.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
|
|
||||||
jid=jid, nickname=item['name'], sub=item['sub'],
|
|
||||||
ask=item['ask'], groups=item['groups']))
|
|
||||||
account_jid = app.get_jid_from_account(self.name)
|
|
||||||
app.logger.add_or_update_contact(account_jid, jid, item['name'],
|
|
||||||
item['sub'], item['ask'], item['groups'])
|
|
||||||
if obj.version:
|
|
||||||
app.config.set_per('accounts', self.name, 'roster_version',
|
|
||||||
obj.version)
|
|
||||||
|
|
||||||
def _dispatch_gc_msg_with_captcha(self, stanza, msg_obj):
|
def _dispatch_gc_msg_with_captcha(self, stanza, msg_obj):
|
||||||
msg_obj.stanza = stanza
|
msg_obj.stanza = stanza
|
||||||
|
@ -657,15 +614,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
|
||||||
# This way we'll really remove it
|
# This way we'll really remove it
|
||||||
app.to_be_removed[self.name].remove(jid)
|
app.to_be_removed[self.name].remove(jid)
|
||||||
|
|
||||||
def _getRoster(self):
|
|
||||||
log.debug('getRosterCB')
|
|
||||||
if not self.connection:
|
|
||||||
return
|
|
||||||
self.connection.getRoster(self._on_roster_set)
|
|
||||||
self.get_module('Discovery').discover_server_items()
|
|
||||||
if app.config.get_per('accounts', self.name, 'use_ft_proxies'):
|
|
||||||
self.discover_ft_proxies()
|
|
||||||
|
|
||||||
def discover_ft_proxies(self):
|
def discover_ft_proxies(self):
|
||||||
cfg_proxies = app.config.get_per('accounts', self.name,
|
cfg_proxies = app.config.get_per('accounts', self.name,
|
||||||
'file_transfer_proxies')
|
'file_transfer_proxies')
|
||||||
|
@ -679,15 +627,7 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
|
||||||
app.proxy65_manager.resolve(proxy, self.connection, our_jid,
|
app.proxy65_manager.resolve(proxy, self.connection, our_jid,
|
||||||
testit=testit)
|
testit=testit)
|
||||||
|
|
||||||
def _on_roster_set(self, roster):
|
def send_first_presence(self):
|
||||||
app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
|
|
||||||
xmpp_roster=roster))
|
|
||||||
|
|
||||||
def _nec_roster_received(self, obj):
|
|
||||||
if obj.conn.name != self.name:
|
|
||||||
return
|
|
||||||
our_jid = app.get_jid_from_account(self.name)
|
|
||||||
|
|
||||||
if self.connected > 1 and self.continue_connect_info:
|
if self.connected > 1 and self.continue_connect_info:
|
||||||
msg = self.continue_connect_info[1]
|
msg = self.continue_connect_info[1]
|
||||||
sign_msg = self.continue_connect_info[2]
|
sign_msg = self.continue_connect_info[2]
|
||||||
|
@ -705,20 +645,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
|
||||||
if send_first_presence:
|
if send_first_presence:
|
||||||
self._send_first_presence(signed)
|
self._send_first_presence(signed)
|
||||||
|
|
||||||
app.logger.replace_roster(self.name, obj.version, obj.roster)
|
|
||||||
|
|
||||||
for contact in app.contacts.iter_contacts(self.name):
|
|
||||||
if not contact.is_groupchat() and contact.jid not in obj.roster\
|
|
||||||
and contact.jid != our_jid:
|
|
||||||
app.nec.push_incoming_event(RosterInfoEvent(None,
|
|
||||||
conn=self, jid=contact.jid, nickname=None, sub=None,
|
|
||||||
ask=None, groups=()))
|
|
||||||
for jid, info in obj.roster.items():
|
|
||||||
app.nec.push_incoming_event(RosterInfoEvent(None,
|
|
||||||
conn=self, jid=jid, nickname=info['name'],
|
|
||||||
sub=info['subscription'], ask=info['ask'],
|
|
||||||
groups=info['groups'], avatar_sha=info['avatar_sha']))
|
|
||||||
|
|
||||||
def _send_first_presence(self, signed=''):
|
def _send_first_presence(self, signed=''):
|
||||||
show = self.continue_connect_info[0]
|
show = self.continue_connect_info[0]
|
||||||
msg = self.continue_connect_info[1]
|
msg = self.continue_connect_info[1]
|
||||||
|
@ -786,7 +712,7 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
|
||||||
def _register_handlers(self, con, con_type):
|
def _register_handlers(self, con, con_type):
|
||||||
# try to find another way to register handlers in each class
|
# try to find another way to register handlers in each class
|
||||||
# that defines handlers
|
# that defines handlers
|
||||||
con.RegisterHandler('iq', self._rosterSetCB, 'set', nbxmpp.NS_ROSTER)
|
|
||||||
con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
|
con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
|
||||||
con.RegisterHandler('iq', self._siErrorCB, 'error', nbxmpp.NS_SI)
|
con.RegisterHandler('iq', self._siErrorCB, 'error', nbxmpp.NS_SI)
|
||||||
con.RegisterHandler('iq', self._siResultCB, 'result', nbxmpp.NS_SI)
|
con.RegisterHandler('iq', self._siResultCB, 'result', nbxmpp.NS_SI)
|
||||||
|
|
|
@ -179,93 +179,6 @@ class HelperEvent:
|
||||||
self.muc_pm = muc_user.getChildren() == []
|
self.muc_pm = muc_user.getChildren() == []
|
||||||
return self.muc_pm
|
return self.muc_pm
|
||||||
|
|
||||||
class RosterReceivedEvent(nec.NetworkIncomingEvent):
|
|
||||||
name = 'roster-received'
|
|
||||||
base_network_events = []
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
if hasattr(self, 'xmpp_roster'):
|
|
||||||
self.version = self.xmpp_roster.version
|
|
||||||
self.received_from_server = self.xmpp_roster.received_from_server
|
|
||||||
self.roster = {}
|
|
||||||
raw_roster = self.xmpp_roster.getRaw()
|
|
||||||
our_jid = app.get_jid_from_account(self.conn.name)
|
|
||||||
|
|
||||||
for jid in raw_roster:
|
|
||||||
try:
|
|
||||||
j = helpers.parse_jid(jid)
|
|
||||||
except Exception:
|
|
||||||
print(_('JID %s is not RFC compliant. It will not be added '
|
|
||||||
'to your roster. Use roster management tools such as '
|
|
||||||
'http://jru.jabberstudio.org/ to remove it') % jid,
|
|
||||||
file=sys.stderr)
|
|
||||||
else:
|
|
||||||
infos = raw_roster[jid]
|
|
||||||
if jid != our_jid and (not infos['subscription'] or \
|
|
||||||
infos['subscription'] == 'none') and (not infos['ask'] or \
|
|
||||||
infos['ask'] == 'none') and not infos['name'] and \
|
|
||||||
not infos['groups']:
|
|
||||||
# remove this useless item, it won't be shown in roster
|
|
||||||
# anyway
|
|
||||||
self.conn.connection.getRoster().delItem(jid)
|
|
||||||
elif jid != our_jid: # don't add our jid
|
|
||||||
self.roster[j] = raw_roster[jid]
|
|
||||||
self.roster[j]['avatar_sha'] = None
|
|
||||||
else:
|
|
||||||
# Roster comes from DB
|
|
||||||
self.received_from_server = False
|
|
||||||
self.version = app.config.get_per('accounts', self.conn.name,
|
|
||||||
'roster_version')
|
|
||||||
self.roster = app.logger.get_roster(app.get_jid_from_account(
|
|
||||||
self.conn.name))
|
|
||||||
if not self.roster:
|
|
||||||
app.config.set_per(
|
|
||||||
'accounts', self.conn.name, 'roster_version', '')
|
|
||||||
return True
|
|
||||||
|
|
||||||
class RosterSetReceivedEvent(nec.NetworkIncomingEvent):
|
|
||||||
name = 'roster-set-received'
|
|
||||||
base_network_events = []
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
frm = self.stanza.getFrom()
|
|
||||||
our_jid = self.conn.get_own_jid()
|
|
||||||
if frm is not None and not frm.bareMatch(our_jid):
|
|
||||||
return
|
|
||||||
self.version = self.stanza.getTagAttr('query', 'ver')
|
|
||||||
self.items = {}
|
|
||||||
for item in self.stanza.getTag('query').getChildren():
|
|
||||||
try:
|
|
||||||
jid = helpers.parse_jid(item.getAttr('jid'))
|
|
||||||
except helpers.InvalidFormat:
|
|
||||||
log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
|
|
||||||
continue
|
|
||||||
name = item.getAttr('name')
|
|
||||||
sub = item.getAttr('subscription')
|
|
||||||
ask = item.getAttr('ask')
|
|
||||||
groups = []
|
|
||||||
for group in item.getTags('group'):
|
|
||||||
groups.append(group.getData())
|
|
||||||
self.items[jid] = {'name': name, 'sub': sub, 'ask': ask,
|
|
||||||
'groups': groups}
|
|
||||||
if len(self.items) > 1:
|
|
||||||
reply = nbxmpp.Iq(typ='error', attrs={'id': self.stanza.getID()},
|
|
||||||
to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
|
|
||||||
self.conn.connection.send(reply)
|
|
||||||
return
|
|
||||||
if self.conn.connection and self.conn.connected > 1:
|
|
||||||
reply = nbxmpp.Iq(typ='result', attrs={'id': self.stanza.getID()},
|
|
||||||
to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
|
|
||||||
self.conn.connection.send(reply)
|
|
||||||
return True
|
|
||||||
|
|
||||||
class RosterInfoEvent(nec.NetworkIncomingEvent):
|
|
||||||
name = 'roster-info'
|
|
||||||
base_network_events = []
|
|
||||||
|
|
||||||
def init(self):
|
|
||||||
self.avatar_sha = None
|
|
||||||
|
|
||||||
class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||||
name = 'iq-error-received'
|
name = 'iq-error-received'
|
||||||
base_network_events = []
|
base_network_events = []
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import logging
|
import logging
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
log = logging.getLogger('gajim.c.m')
|
log = logging.getLogger('gajim.c.m')
|
||||||
|
|
||||||
|
@ -60,9 +61,7 @@ class ModuleMock:
|
||||||
self.supported = False
|
self.supported = False
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
def _mock(self, *args, **kwargs):
|
return MagicMock()
|
||||||
return
|
|
||||||
return _mock
|
|
||||||
|
|
||||||
|
|
||||||
def register(con, *args, **kwargs):
|
def register(con, *args, **kwargs):
|
||||||
|
|
|
@ -140,15 +140,15 @@ class Presence:
|
||||||
if not app.account_is_connected(self._account):
|
if not app.account_is_connected(self._account):
|
||||||
return
|
return
|
||||||
if remove_auth:
|
if remove_auth:
|
||||||
self._con.connection.getRoster().delItem(jid)
|
self._con.getRoster().delItem(jid)
|
||||||
jid_list = app.config.get_per('contacts')
|
jid_list = app.config.get_per('contacts')
|
||||||
for j in jid_list:
|
for j in jid_list:
|
||||||
if j.startswith(jid):
|
if j.startswith(jid):
|
||||||
app.config.del_per('contacts', j)
|
app.config.del_per('contacts', j)
|
||||||
else:
|
else:
|
||||||
log.info('Unsubscribe from %s', jid)
|
log.info('Unsubscribe from %s', jid)
|
||||||
self._con.connection.getRoster().Unsubscribe(jid)
|
self._con.getRoster().Unsubscribe(jid)
|
||||||
self._con.connection.getRoster().setItem(jid)
|
self._con.getRoster().setItem(jid)
|
||||||
|
|
||||||
def subscribe(self, jid, msg='', name='', groups=None,
|
def subscribe(self, jid, msg='', name='', groups=None,
|
||||||
auto_auth=False, user_nick=''):
|
auto_auth=False, user_nick=''):
|
||||||
|
|
|
@ -0,0 +1,351 @@
|
||||||
|
# This file is part of Gajim.
|
||||||
|
#
|
||||||
|
# Gajim is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published
|
||||||
|
# by the Free Software Foundation; version 3 only.
|
||||||
|
#
|
||||||
|
# Gajim is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Roster
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import nbxmpp
|
||||||
|
|
||||||
|
from gajim.common import app
|
||||||
|
from gajim.common.nec import NetworkEvent
|
||||||
|
|
||||||
|
log = logging.getLogger('gajim.c.m.roster')
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Error IQs
|
||||||
|
# What if roster not supported on server -> error
|
||||||
|
|
||||||
|
RosterItem = namedtuple('RosterItem', 'jid data')
|
||||||
|
|
||||||
|
|
||||||
|
class Roster:
|
||||||
|
def __init__(self, con):
|
||||||
|
self._con = con
|
||||||
|
self._account = con.name
|
||||||
|
|
||||||
|
self.handlers = [
|
||||||
|
('iq', self._roster_push_received, 'set', nbxmpp.NS_ROSTER),
|
||||||
|
('presence', self._presence_received)
|
||||||
|
]
|
||||||
|
|
||||||
|
self._data = {}
|
||||||
|
self._set = None
|
||||||
|
|
||||||
|
def load_roster(self):
|
||||||
|
log.info('Load from database')
|
||||||
|
account_jid = self._con.get_own_jid().getStripped()
|
||||||
|
data = app.logger.get_roster(account_jid)
|
||||||
|
if data:
|
||||||
|
self.setRaw(data)
|
||||||
|
for jid, item in self._data.items():
|
||||||
|
app.nec.push_incoming_event(NetworkEvent(
|
||||||
|
'roster-info',
|
||||||
|
conn=self._con,
|
||||||
|
jid=jid,
|
||||||
|
nickname=item['name'],
|
||||||
|
sub=item['subscription'],
|
||||||
|
ask=item['ask'],
|
||||||
|
groups=item['groups'],
|
||||||
|
avatar_sha=item['avatar_sha']))
|
||||||
|
else:
|
||||||
|
log.info('Database empty, reset roster version')
|
||||||
|
app.config.set_per(
|
||||||
|
'accounts', self._account, 'roster_version', '')
|
||||||
|
|
||||||
|
def request_roster(self, version):
|
||||||
|
log.info('Requested from server')
|
||||||
|
iq = nbxmpp.Iq('get', nbxmpp.NS_ROSTER)
|
||||||
|
if version is not None:
|
||||||
|
iq.setTagAttr('query', 'ver', version)
|
||||||
|
log.info('Request version: %s', version)
|
||||||
|
self._con.connection.SendAndCallForResponse(
|
||||||
|
iq, self._roster_received)
|
||||||
|
|
||||||
|
def _roster_received(self, stanza):
|
||||||
|
if not nbxmpp.isResultNode(stanza):
|
||||||
|
log.warning('Unable to retrive roster: %s', stanza.getError())
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info('Received Roster')
|
||||||
|
received_from_server = False
|
||||||
|
if stanza.getTag('query') is not None:
|
||||||
|
# clear Roster
|
||||||
|
self._data = {}
|
||||||
|
version = self._parse_roster(stanza)
|
||||||
|
|
||||||
|
log.info('New version: %s', version)
|
||||||
|
app.logger.replace_roster(self._account, version, self._data)
|
||||||
|
|
||||||
|
received_from_server = True
|
||||||
|
|
||||||
|
app.nec.push_incoming_event(NetworkEvent(
|
||||||
|
'roster-received',
|
||||||
|
conn=self._con,
|
||||||
|
roster=self._data.copy(),
|
||||||
|
received_from_server=received_from_server))
|
||||||
|
|
||||||
|
self._con.send_first_presence()
|
||||||
|
|
||||||
|
def _roster_push_received(self, con, stanza):
|
||||||
|
log.info('Push received')
|
||||||
|
|
||||||
|
sender = stanza.getFrom()
|
||||||
|
if sender is not None:
|
||||||
|
if not self._con.get_own_jid().bareMatch(sender):
|
||||||
|
log.warning('Wrong JID %s', stanza.getFrom())
|
||||||
|
return
|
||||||
|
|
||||||
|
push_items, version = self._parse_push(stanza)
|
||||||
|
|
||||||
|
self._ack_roster_push(stanza)
|
||||||
|
|
||||||
|
for item in push_items:
|
||||||
|
attrs = item.data
|
||||||
|
app.nec.push_incoming_event(NetworkEvent(
|
||||||
|
'roster-info',
|
||||||
|
conn=self._con,
|
||||||
|
jid=item.jid,
|
||||||
|
nickname=attrs['name'],
|
||||||
|
sub=attrs['subscription'],
|
||||||
|
ask=attrs['ask'],
|
||||||
|
groups=attrs['groups'],
|
||||||
|
avatar_sha=None))
|
||||||
|
account_jid = self._con.get_own_jid().getStripped()
|
||||||
|
app.logger.add_or_update_contact(
|
||||||
|
account_jid, item.jid, attrs['name'],
|
||||||
|
attrs['subscription'], attrs['ask'], attrs['groups'])
|
||||||
|
|
||||||
|
log.info('New version: %s', version)
|
||||||
|
app.config.set_per(
|
||||||
|
'accounts', self._account, 'roster_version', version)
|
||||||
|
|
||||||
|
raise nbxmpp.NodeProcessed
|
||||||
|
|
||||||
|
def _parse_roster(self, stanza):
|
||||||
|
query = stanza.getTag('query')
|
||||||
|
version = query.getAttr('ver')
|
||||||
|
|
||||||
|
for item in query.getTags('item'):
|
||||||
|
jid = item.getAttr('jid')
|
||||||
|
self._data[jid] = self._get_item_attrs(item, update=False)
|
||||||
|
log.info('Item %s: %s', jid, self._data[jid])
|
||||||
|
return version
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_item_attrs(item, update=True):
|
||||||
|
'''
|
||||||
|
update: True
|
||||||
|
returns only the attrs that are present in the item
|
||||||
|
|
||||||
|
update: False
|
||||||
|
returns the attrs of the item but fills missing
|
||||||
|
attrs with default values
|
||||||
|
'''
|
||||||
|
|
||||||
|
default_attrs = {'name': None,
|
||||||
|
'ask': None,
|
||||||
|
'subscription': None,
|
||||||
|
'groups': [],
|
||||||
|
'avatar_sha': None}
|
||||||
|
|
||||||
|
attrs = item.getAttrs()
|
||||||
|
del attrs['jid']
|
||||||
|
groups = set([group.getData() for group in item.getTags('group')])
|
||||||
|
attrs['groups'] = list(groups)
|
||||||
|
|
||||||
|
if update:
|
||||||
|
return attrs
|
||||||
|
default_attrs.update(attrs)
|
||||||
|
return default_attrs
|
||||||
|
|
||||||
|
def _parse_push(self, stanza):
|
||||||
|
query = stanza.getTag('query')
|
||||||
|
version = query.getAttr('ver')
|
||||||
|
push_items = []
|
||||||
|
|
||||||
|
for item in query.getTags('item'):
|
||||||
|
push_items.append(self._update_roster_item(item))
|
||||||
|
for item in push_items:
|
||||||
|
log.info('Push: %s', item)
|
||||||
|
return push_items, version
|
||||||
|
|
||||||
|
def _update_roster_item(self, item):
|
||||||
|
jid = item.getAttr('jid')
|
||||||
|
|
||||||
|
if item.getAttr('subscription') == 'remove':
|
||||||
|
self._data.pop(jid, None)
|
||||||
|
attrs = self._get_item_attrs(item, update=False)
|
||||||
|
return RosterItem(jid, attrs)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if jid not in self._data:
|
||||||
|
self._data[jid] = self._get_item_attrs(item, update=False)
|
||||||
|
else:
|
||||||
|
self._data[jid].update(self._get_item_attrs(item))
|
||||||
|
|
||||||
|
return RosterItem(jid, self._data[jid])
|
||||||
|
|
||||||
|
def _ack_roster_push(self, stanza):
|
||||||
|
iq = nbxmpp.Iq('result',
|
||||||
|
to=stanza.getFrom(),
|
||||||
|
frm=stanza.getTo(),
|
||||||
|
attrs={'id': stanza.getID()})
|
||||||
|
self._con.connection.send(iq)
|
||||||
|
|
||||||
|
def _presence_received(self, con, pres):
|
||||||
|
'''
|
||||||
|
Add contacts that request subscription to our internal
|
||||||
|
roster and also to the database. The contact is put into the
|
||||||
|
'Not in roster' group and because we save it to the database
|
||||||
|
it is also after a restart available.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if pres.getType() != 'subscribe':
|
||||||
|
return
|
||||||
|
|
||||||
|
jid = pres.getFrom().getStripped()
|
||||||
|
|
||||||
|
if jid in self._data:
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info('Add Contact from presence %s', jid)
|
||||||
|
self._data[jid] = {'name': None,
|
||||||
|
'ask': None,
|
||||||
|
'subscription':
|
||||||
|
'none',
|
||||||
|
'groups': ['Not in roster']}
|
||||||
|
account_jid = self._con.get_own_jid().getStripped()
|
||||||
|
app.logger.add_or_update_contact(
|
||||||
|
account_jid, jid,
|
||||||
|
self._data[jid]['name'],
|
||||||
|
self._data[jid]['subscription'],
|
||||||
|
self._data[jid]['ask'],
|
||||||
|
self._data[jid]['groups'])
|
||||||
|
|
||||||
|
def _getItemData(self, jid, dataname):
|
||||||
|
"""
|
||||||
|
Return specific jid's representation in internal format.
|
||||||
|
"""
|
||||||
|
jid = jid[:(jid + '/').find('/')]
|
||||||
|
return self._data[jid][dataname]
|
||||||
|
|
||||||
|
def delItem(self, jid):
|
||||||
|
"""
|
||||||
|
Delete contact 'jid' from roster
|
||||||
|
"""
|
||||||
|
self._con.connection.send(
|
||||||
|
nbxmpp.Iq('set', nbxmpp.NS_ROSTER, payload=[
|
||||||
|
nbxmpp.Node('item', {'jid': jid, 'subscription': 'remove'})]))
|
||||||
|
|
||||||
|
def getGroups(self, jid):
|
||||||
|
"""
|
||||||
|
Return groups list that contact 'jid' belongs to
|
||||||
|
"""
|
||||||
|
return self._getItemData(jid, 'groups')
|
||||||
|
|
||||||
|
def getName(self, jid):
|
||||||
|
"""
|
||||||
|
Return name of contact 'jid'
|
||||||
|
"""
|
||||||
|
return self._getItemData(jid, 'name')
|
||||||
|
|
||||||
|
def setItem(self, jid, name=None, groups=None):
|
||||||
|
"""
|
||||||
|
Rename contact 'jid' and sets the groups list that it now belongs to
|
||||||
|
"""
|
||||||
|
iq = nbxmpp.Iq('set', nbxmpp.NS_ROSTER)
|
||||||
|
query = iq.getTag('query')
|
||||||
|
attrs = {'jid': jid}
|
||||||
|
if name:
|
||||||
|
attrs['name'] = name
|
||||||
|
item = query.setTag('item', attrs)
|
||||||
|
if groups is not None:
|
||||||
|
for group in groups:
|
||||||
|
item.addChild(node=nbxmpp.Node('group', payload=[group]))
|
||||||
|
self._con.connection.send(iq)
|
||||||
|
|
||||||
|
def setItemMulti(self, items):
|
||||||
|
"""
|
||||||
|
Rename multiple contacts and sets their group lists
|
||||||
|
"""
|
||||||
|
for i in items:
|
||||||
|
iq = nbxmpp.Iq('set', nbxmpp.NS_ROSTER)
|
||||||
|
query = iq.getTag('query')
|
||||||
|
attrs = {'jid': i['jid']}
|
||||||
|
if i['name']:
|
||||||
|
attrs['name'] = i['name']
|
||||||
|
item = query.setTag('item', attrs)
|
||||||
|
for group in i['groups']:
|
||||||
|
item.addChild(node=nbxmpp.Node('group', payload=[group]))
|
||||||
|
self._con.connection.send(iq)
|
||||||
|
|
||||||
|
def getItems(self):
|
||||||
|
"""
|
||||||
|
Return list of all [bare] JIDs that the roster is currently tracks
|
||||||
|
"""
|
||||||
|
return list(self._data.keys())
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
"""
|
||||||
|
Same as getItems. Provided for the sake of dictionary interface
|
||||||
|
"""
|
||||||
|
return list(self._data.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
"""
|
||||||
|
Get the contact in the internal format.
|
||||||
|
Raises KeyError if JID 'item' is not in roster
|
||||||
|
"""
|
||||||
|
return self._data[item]
|
||||||
|
|
||||||
|
def getItem(self, item):
|
||||||
|
"""
|
||||||
|
Get the contact in the internal format (or None if JID 'item' is not in
|
||||||
|
roster)
|
||||||
|
"""
|
||||||
|
if item in self._data:
|
||||||
|
return self._data[item]
|
||||||
|
|
||||||
|
def Unsubscribe(self, jid):
|
||||||
|
"""
|
||||||
|
Ask for removing our subscription for JID 'jid'
|
||||||
|
"""
|
||||||
|
self._con.connection.send(nbxmpp.Presence(jid, 'unsubscribe'))
|
||||||
|
|
||||||
|
def getRaw(self):
|
||||||
|
"""
|
||||||
|
Return the internal data representation of the roster
|
||||||
|
"""
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
def setRaw(self, data):
|
||||||
|
"""
|
||||||
|
Set the internal data representation of the roster
|
||||||
|
"""
|
||||||
|
own_jid = self._con.get_own_jid().getStripped()
|
||||||
|
self._data = data
|
||||||
|
self._data[own_jid] = {
|
||||||
|
'resources': {},
|
||||||
|
'name': None,
|
||||||
|
'ask': None,
|
||||||
|
'subscription': None,
|
||||||
|
'groups': None,
|
||||||
|
'avatar_sha': None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance(*args, **kwargs):
|
||||||
|
return Roster(*args, **kwargs), 'Roster'
|
|
@ -135,9 +135,11 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
||||||
diffs = self.roster.getDiffs()
|
diffs = self.roster.getDiffs()
|
||||||
for key in diffs:
|
for key in diffs:
|
||||||
self.roster.setItem(key)
|
self.roster.setItem(key)
|
||||||
app.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
|
app.nec.push_incoming_event(NetworkEvent(
|
||||||
jid=key, nickname=self.roster.getName(key), sub='both',
|
'roster-info', conn=self,jid=key,
|
||||||
ask='no', groups=self.roster.getGroups(key)))
|
nickname=self.roster.getName(key), sub='both',
|
||||||
|
ask='no', groups=self.roster.getGroups(key),
|
||||||
|
avatar_sha=None))
|
||||||
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
|
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
|
||||||
None, conn=self, fjid=key, show=self.roster.getStatus(key),
|
None, conn=self, fjid=key, show=self.roster.getStatus(key),
|
||||||
status=self.roster.getMessage(key)))
|
status=self.roster.getMessage(key)))
|
||||||
|
@ -147,9 +149,11 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
||||||
# callbacks called from zeroconf
|
# callbacks called from zeroconf
|
||||||
def _on_new_service(self, jid):
|
def _on_new_service(self, jid):
|
||||||
self.roster.setItem(jid)
|
self.roster.setItem(jid)
|
||||||
app.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
|
app.nec.push_incoming_event(NetworkEvent(
|
||||||
jid=jid, nickname=self.roster.getName(jid), sub='both',
|
'roster-info', conn=self, jid=jid,
|
||||||
ask='no', groups=self.roster.getGroups(jid)))
|
nickname=self.roster.getName(jid), sub='both',
|
||||||
|
ask='no', groups=self.roster.getGroups(jid),
|
||||||
|
avatar_sha=None))
|
||||||
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
|
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
|
||||||
None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
|
None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
|
||||||
status=self.roster.getMessage(jid)))
|
status=self.roster.getMessage(jid)))
|
||||||
|
@ -219,14 +223,16 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
|
||||||
else:
|
else:
|
||||||
self.connection.announce()
|
self.connection.announce()
|
||||||
self.roster = self.connection.getRoster()
|
self.roster = self.connection.getRoster()
|
||||||
app.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
|
app.nec.push_incoming_event(NetworkEvent('roster-received', conn=self,
|
||||||
xmpp_roster=self.roster))
|
roster=self.roster.copy(), received_from_server=True))
|
||||||
|
|
||||||
# display contacts already detected and resolved
|
# display contacts already detected and resolved
|
||||||
for jid in self.roster.keys():
|
for jid in self.roster.keys():
|
||||||
app.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
|
app.nec.push_incoming_event(NetworkEvent(
|
||||||
jid=jid, nickname=self.roster.getName(jid), sub='both',
|
'roster-info', conn=self, jid=jid,
|
||||||
ask='no', groups=self.roster.getGroups(jid)))
|
nickname=self.roster.getName(jid), sub='both',
|
||||||
|
ask='no', groups=self.roster.getGroups(jid),
|
||||||
|
avatar_sha=None))
|
||||||
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
|
app.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
|
||||||
None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
|
None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
|
||||||
status=self.roster.getMessage(jid)))
|
status=self.roster.getMessage(jid)))
|
||||||
|
|
|
@ -18,15 +18,14 @@
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
from gajim.common.zeroconf import zeroconf
|
|
||||||
from gajim.common.zeroconf.zeroconf import Constant, ConstantRI
|
from gajim.common.zeroconf.zeroconf import Constant, ConstantRI
|
||||||
|
|
||||||
|
|
||||||
class Roster:
|
class Roster:
|
||||||
def __init__(self, zeroconf):
|
def __init__(self, zeroconf):
|
||||||
self._data = None
|
self._data = None
|
||||||
self.zeroconf = zeroconf # our zeroconf instance
|
self.zeroconf = zeroconf # our zeroconf instance
|
||||||
self.version = ''
|
self.version = ''
|
||||||
self.received_from_server = True
|
|
||||||
|
|
||||||
def update_roster(self):
|
def update_roster(self):
|
||||||
for val in self.zeroconf.contacts.values():
|
for val in self.zeroconf.contacts.values():
|
||||||
|
@ -109,6 +108,9 @@ class Roster:
|
||||||
def __getitem__(self, jid):
|
def __getitem__(self, jid):
|
||||||
return self._data[jid]
|
return self._data[jid]
|
||||||
|
|
||||||
|
def __setitem__(self, jid, value):
|
||||||
|
self._data[jid] = value
|
||||||
|
|
||||||
def getItems(self):
|
def getItems(self):
|
||||||
# Return list of all [bare] JIDs that the roster currently tracks.
|
# Return list of all [bare] JIDs that the roster currently tracks.
|
||||||
return self._data.keys()
|
return self._data.keys()
|
||||||
|
@ -157,3 +159,6 @@ class Roster:
|
||||||
|
|
||||||
def Unauthorize(self, jid):
|
def Unauthorize(self, jid):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return self._data.copy()
|
||||||
|
|
|
@ -2628,7 +2628,7 @@ class Interface:
|
||||||
|
|
||||||
self.roster._before_fill()
|
self.roster._before_fill()
|
||||||
for account in app.connections:
|
for account in app.connections:
|
||||||
app.connections[account].load_roster_from_db()
|
app.connections[account].get_module('Roster').load_roster()
|
||||||
self.roster._after_fill()
|
self.roster._after_fill()
|
||||||
|
|
||||||
# get instances for windows/dialogs that will show_all()/hide()
|
# get instances for windows/dialogs that will show_all()/hide()
|
||||||
|
|
|
@ -2593,8 +2593,8 @@ class RosterWindow:
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj.nick == gc_ctrl.nick:
|
if obj.nick == gc_ctrl.nick:
|
||||||
contact = app.contacts.get_contact_with_highest_priority(account,
|
contact = app.contacts.get_contact_with_highest_priority(
|
||||||
obj.room_jid)
|
account, obj.room_jid)
|
||||||
if contact:
|
if contact:
|
||||||
contact.show = obj.show
|
contact.show = obj.show
|
||||||
self.draw_contact(obj.room_jid, account)
|
self.draw_contact(obj.room_jid, account)
|
||||||
|
@ -2615,28 +2615,28 @@ class RosterWindow:
|
||||||
if app.connections[account].server_resource:
|
if app.connections[account].server_resource:
|
||||||
resource = app.connections[account].server_resource
|
resource = app.connections[account].server_resource
|
||||||
sha = app.config.get_per('accounts', account, 'avatar_sha')
|
sha = app.config.get_per('accounts', account, 'avatar_sha')
|
||||||
contact = app.contacts.create_contact(jid=self_jid,
|
contact = app.contacts.create_contact(
|
||||||
account=account, name=app.nicks[account],
|
jid=self_jid, account=account, name=app.nicks[account],
|
||||||
groups=['self_contact'], show='offline', sub='both',
|
groups=['self_contact'], show='offline', sub='both',
|
||||||
ask='none', resource=resource, avatar_sha=sha)
|
ask='none', resource=resource, avatar_sha=sha)
|
||||||
app.contacts.add_contact(account, contact)
|
app.contacts.add_contact(account, contact)
|
||||||
self.add_contact(self_jid, account)
|
self.add_contact(self_jid, account)
|
||||||
if app.config.get('remember_opened_chat_controls'):
|
if app.config.get('remember_opened_chat_controls'):
|
||||||
account = obj.conn.name
|
account = obj.conn.name
|
||||||
controls = app.config.get_per('accounts', account,
|
controls = app.config.get_per(
|
||||||
'opened_chat_controls')
|
'accounts', account, 'opened_chat_controls')
|
||||||
if controls:
|
if controls:
|
||||||
for jid in controls.split(','):
|
for jid in controls.split(','):
|
||||||
contact = \
|
contact = \
|
||||||
app.contacts.get_contact_with_highest_priority(
|
app.contacts.get_contact_with_highest_priority(
|
||||||
account, jid)
|
account, jid)
|
||||||
if not contact:
|
if not contact:
|
||||||
contact = self.add_to_not_in_the_roster(account,
|
contact = self.add_to_not_in_the_roster(
|
||||||
jid)
|
account, jid)
|
||||||
app.interface.on_open_chat_window(None, contact,
|
app.interface.on_open_chat_window(
|
||||||
account)
|
None, contact, account)
|
||||||
app.config.set_per('accounts', account,
|
app.config.set_per(
|
||||||
'opened_chat_controls', '')
|
'accounts', account, 'opened_chat_controls', '')
|
||||||
GLib.idle_add(self.refilter_shown_roster_items)
|
GLib.idle_add(self.refilter_shown_roster_items)
|
||||||
|
|
||||||
def _nec_anonymous_auth(self, obj):
|
def _nec_anonymous_auth(self, obj):
|
||||||
|
|
|
@ -384,34 +384,6 @@ class RosterTooltip(Gtk.Window, StatusTable):
|
||||||
contact.keyID = app.config.get_per('accounts',
|
contact.keyID = app.config.get_per('accounts',
|
||||||
connection.name, 'keyid')
|
connection.name, 'keyid')
|
||||||
contacts.append(contact)
|
contacts.append(contact)
|
||||||
# if we're online ...
|
|
||||||
if connection.connection:
|
|
||||||
roster = connection.connection.getRoster()
|
|
||||||
# in threadless connection when no roster stanza is sent
|
|
||||||
# 'roster' is None
|
|
||||||
if roster and roster.getItem(jid):
|
|
||||||
resources = roster.getResources(jid)
|
|
||||||
# ...get the contact info for our other online
|
|
||||||
# resources
|
|
||||||
for resource in resources:
|
|
||||||
# Check if we already have this resource
|
|
||||||
found = False
|
|
||||||
for contact_ in contacts:
|
|
||||||
if contact_.resource == resource:
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if found:
|
|
||||||
continue
|
|
||||||
show = roster.getShow(jid + '/' + resource)
|
|
||||||
if not show:
|
|
||||||
show = 'online'
|
|
||||||
contact = app.contacts.create_self_contact(
|
|
||||||
jid=jid, account=account, show=show,
|
|
||||||
status=roster.getStatus(
|
|
||||||
jid + '/' + resource),
|
|
||||||
priority=roster.getPriority(
|
|
||||||
jid + '/' + resource), resource=resource)
|
|
||||||
contacts.append(contact)
|
|
||||||
|
|
||||||
# Username/Account/Groupchat
|
# Username/Account/Groupchat
|
||||||
self.prim_contact = app.contacts.get_highest_prio_contact_from_contacts(
|
self.prim_contact = app.contacts.get_highest_prio_contact_from_contacts(
|
||||||
|
|
Loading…
Reference in New Issue