Do not use XEP-0012 anymore to know idle time. Use XEP-0319
This commit is contained in:
parent
9d6e3f4323
commit
28917aaf56
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.18.3 -->
|
||||
<!-- Generated with glade 3.20.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.12"/>
|
||||
<object class="GtkGrid" id="tooltip_grid">
|
||||
|
@ -14,7 +14,6 @@
|
|||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -29,8 +28,6 @@
|
|||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Jabber ID:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -41,13 +38,12 @@
|
|||
<object class="GtkImage" id="avatar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="height">14</property>
|
||||
<property name="height">13</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -55,9 +51,8 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Resource:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -70,8 +65,6 @@
|
|||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -83,9 +76,9 @@
|
|||
<object class="GtkLabel" id="resource">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -96,9 +89,9 @@
|
|||
<object class="GtkLabel" id="jid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
|
@ -117,8 +110,6 @@
|
|||
<property name="label" translatable="yes">Status:</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="max_width_chars">40</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -130,63 +121,34 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Idle since:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">12</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="idle_for_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Idle for:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">13</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="idle_since">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">12</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="idle_for">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">13</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="mood_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Mood:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -198,10 +160,9 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Activity:</property>
|
||||
<property name="lines">2</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -213,9 +174,8 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Tune:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -227,9 +187,8 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Location:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -240,9 +199,9 @@
|
|||
<object class="GtkLabel" id="mood">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -253,9 +212,8 @@
|
|||
<object class="GtkLabel" id="activity">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -266,9 +224,9 @@
|
|||
<object class="GtkLabel" id="tune">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -279,9 +237,9 @@
|
|||
<object class="GtkLabel" id="location">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -293,9 +251,8 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">OpenPGP:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -306,9 +263,9 @@
|
|||
<object class="GtkLabel" id="pgp">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -320,9 +277,8 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Subscription:</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -333,9 +289,9 @@
|
|||
<object class="GtkLabel" id="sub">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
|
@ -346,11 +302,11 @@
|
|||
<object class="GtkLabel" id="status">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="max_width_chars">30</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="yalign">0</property>
|
||||
<attributes>
|
||||
<attribute name="style" value="italic"/>
|
||||
</attributes>
|
||||
|
|
|
@ -293,7 +293,6 @@ class Config:
|
|||
'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to notification icon.')],
|
||||
'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
|
||||
'uri_schemes': [opt_str, 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: shttp:// sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal:// aesgcm://', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True],
|
||||
'ask_offline_status_on_connection': [ opt_bool, False, _('Request offline status messages from all contacts upon connecting. WARNING: This causes a lot of requests to be sent!') ],
|
||||
'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')],
|
||||
'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True],
|
||||
'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
|
||||
|
|
|
@ -510,24 +510,6 @@ class CommonConnection:
|
|||
def account_changed(self, new_name):
|
||||
self.name = new_name
|
||||
|
||||
def request_last_status_time(self, jid, resource, groupchat_jid=None):
|
||||
"""
|
||||
groupchat_jid is used when we want to send a request to a real jid and
|
||||
act as if the answer comes from the groupchat_jid
|
||||
"""
|
||||
if not gajim.account_is_connected(self.name):
|
||||
return
|
||||
to_whom_jid = jid
|
||||
if resource:
|
||||
to_whom_jid += '/' + resource
|
||||
iq = nbxmpp.Iq(to=to_whom_jid, typ='get', queryNS=nbxmpp.NS_LAST)
|
||||
id_ = self.connection.getAnID()
|
||||
iq.setID(id_)
|
||||
if groupchat_jid:
|
||||
self.groupchat_jids[id_] = groupchat_jid
|
||||
self.last_ids.append(id_)
|
||||
self.connection.send(iq)
|
||||
|
||||
def request_os_info(self, jid, resource):
|
||||
"""
|
||||
To be implemented by derivated classes
|
||||
|
@ -649,11 +631,18 @@ class CommonConnection:
|
|||
return -1
|
||||
was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
|
||||
self.connected = gajim.SHOW_LIST.index(show)
|
||||
idle_time = None
|
||||
if auto:
|
||||
global HAS_IDLE
|
||||
if HAS_IDLE and gajim.config.get('autoaway'):
|
||||
idle_sec = int(self.sleeper.getIdleSec())
|
||||
idle_time = time.strftime('%Y-%m-%dT%H:%M:%SZ',
|
||||
time.gmtime(time.time() - idle_sec))
|
||||
gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
|
||||
conn=self, show=show, message=msg))
|
||||
if was_invisible:
|
||||
self._change_from_invisible()
|
||||
self._update_status(show, msg)
|
||||
self._update_status(show, msg, idle_time=idle_time)
|
||||
|
||||
class Connection(CommonConnection, ConnectionHandlers):
|
||||
def __init__(self, name):
|
||||
|
@ -2027,7 +2016,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
if self.privacy_rules_supported:
|
||||
self.set_active_list('')
|
||||
|
||||
def _update_status(self, show, msg):
|
||||
def _update_status(self, show, msg, idle_time=None):
|
||||
xmpp_show = helpers.get_xmpp_show(show)
|
||||
priority = gajim.get_priority(self.name, xmpp_show)
|
||||
p = nbxmpp.Presence(typ=None, priority=priority, show=xmpp_show)
|
||||
|
@ -2037,6 +2026,9 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
signed = self.get_signed_presence(msg)
|
||||
if signed:
|
||||
p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
|
||||
if idle_time:
|
||||
idle = p.setTag('idle', namespace=nbxmpp.NS_IDLE)
|
||||
idle.setAttr('since', idle_time)
|
||||
if self.connection:
|
||||
self.connection.send(p)
|
||||
self.priority = priority
|
||||
|
@ -2705,7 +2697,7 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
destroy.setAttr('jid', jid)
|
||||
self.connection.send(iq)
|
||||
|
||||
def send_gc_status(self, nick, jid, show, status):
|
||||
def send_gc_status(self, nick, jid, show, status, auto=False):
|
||||
if not gajim.account_is_connected(self.name):
|
||||
return
|
||||
if show == 'invisible':
|
||||
|
@ -2724,6 +2716,14 @@ class Connection(CommonConnection, ConnectionHandlers):
|
|||
if gajim.config.get('send_sha_in_gc_presence') and show != 'offline':
|
||||
p = self.add_sha(p, ptype != 'unavailable')
|
||||
self.add_lang(p)
|
||||
if auto:
|
||||
global HAS_IDLE
|
||||
if HAS_IDLE and gajim.config.get('autoaway'):
|
||||
idle_sec = int(self.sleeper.getIdleSec())
|
||||
idle_time = time.strftime('%Y-%m-%dT%H:%M:%SZ',
|
||||
time.gmtime(time.time() - idle_sec))
|
||||
idle = p.setTag('idle', namespace=nbxmpp.NS_IDLE)
|
||||
idle.setAttr('since', idle_time)
|
||||
# send instantly so when we go offline, status is sent to gc before we
|
||||
# disconnect from jabber server
|
||||
self.connection.send(p)
|
||||
|
|
|
@ -874,8 +874,6 @@ class ConnectionHandlersBase:
|
|||
# keep the jids we auto added (transports contacts) to not send the
|
||||
# SUBSCRIBED event to gui
|
||||
self.automatically_added = []
|
||||
# IDs of jabber:iq:last requests
|
||||
self.last_ids = []
|
||||
|
||||
# keep track of sessions this connection has with other JIDs
|
||||
self.sessions = {}
|
||||
|
@ -918,10 +916,6 @@ class ConnectionHandlersBase:
|
|||
def _nec_iq_error_received(self, obj):
|
||||
if obj.conn.name != self.name:
|
||||
return
|
||||
if obj.id_ in self.last_ids:
|
||||
gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
|
||||
conn=self, stanza=obj.stanza))
|
||||
return True
|
||||
|
||||
def _nec_presence_received(self, obj):
|
||||
account = obj.conn.name
|
||||
|
@ -972,7 +966,8 @@ class ConnectionHandlersBase:
|
|||
obj.need_redraw = True
|
||||
|
||||
if obj.old_show == obj.new_show and obj.contact.status == \
|
||||
obj.status and obj.contact.priority == obj.prio: # no change
|
||||
obj.status and obj.contact.priority == obj.prio and \
|
||||
obj.contact.idle_time = obj.idle_time: # no change
|
||||
return True
|
||||
else:
|
||||
obj.contact = gajim.contacts.get_first_contact_from_jid(account,
|
||||
|
@ -1029,12 +1024,8 @@ class ConnectionHandlersBase:
|
|||
else:
|
||||
# Do not override assigned key
|
||||
obj.contact.keyID = obj.keyID
|
||||
if obj.timestamp:
|
||||
obj.contact.last_status_time = localtime(obj.timestamp)
|
||||
elif not gajim.block_signed_in_notifications[account]:
|
||||
# We're connected since more that 30 seconds
|
||||
obj.contact.last_status_time = localtime()
|
||||
obj.contact.contact_nickname = obj.contact_nickname
|
||||
obj.contact.idle_time = obj.idle_time
|
||||
|
||||
if gajim.jid_is_transport(jid):
|
||||
return
|
||||
|
@ -1178,11 +1169,6 @@ class ConnectionHandlersBase:
|
|||
fjid=frm, error_code=msg.getErrorCode(), error_msg=error_msg,
|
||||
msg=msgtxt, time_=tim, session=session, stanza=msg))
|
||||
|
||||
def _LastResultCB(self, con, iq_obj):
|
||||
log.debug('LastResultCB')
|
||||
gajim.nec.push_incoming_event(LastResultReceivedEvent(None, conn=self,
|
||||
stanza=iq_obj))
|
||||
|
||||
def get_sessions(self, jid):
|
||||
"""
|
||||
Get all sessions for the given full jid
|
||||
|
@ -2246,7 +2232,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
|
|||
con.RegisterHandler('iq', self._TimeRevisedCB, 'get',
|
||||
nbxmpp.NS_TIME_REVISED)
|
||||
con.RegisterHandler('iq', self._LastCB, 'get', nbxmpp.NS_LAST)
|
||||
con.RegisterHandler('iq', self._LastResultCB, 'result', nbxmpp.NS_LAST)
|
||||
con.RegisterHandler('iq', self._VersionResultCB, 'result',
|
||||
nbxmpp.NS_VERSION)
|
||||
con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result',
|
||||
|
|
|
@ -138,34 +138,6 @@ class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
|
|||
self.msg = self.stanza.getTagData('body')
|
||||
return True
|
||||
|
||||
class LastResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||
name = 'last-result-received'
|
||||
base_network_events = []
|
||||
|
||||
def generate(self):
|
||||
self.get_id()
|
||||
self.get_jid_resource(check_fake_jid=True)
|
||||
if self.id_ in self.conn.last_ids:
|
||||
self.conn.last_ids.remove(self.id_)
|
||||
|
||||
self.status = ''
|
||||
self.seconds = -1
|
||||
|
||||
if self.stanza.getType() == 'error':
|
||||
return True
|
||||
|
||||
qp = self.stanza.getTag('query')
|
||||
if not qp:
|
||||
return
|
||||
sec = qp.getAttr('seconds')
|
||||
self.status = qp.getData()
|
||||
try:
|
||||
self.seconds = int(sec)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
return True
|
||||
|
||||
class VersionResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||
name = 'version-result-received'
|
||||
base_network_events = []
|
||||
|
@ -825,6 +797,13 @@ PresenceHelperEvent):
|
|||
delay_tag = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2)
|
||||
if delay_tag:
|
||||
self._generate_timestamp(self.stanza.getTimestamp2())
|
||||
# XEP-0319
|
||||
self.idle_time = None
|
||||
idle_tag = self.stanza.getTag('idle', namespace=nbxmpp.NS_IDLE)
|
||||
if idle_tag:
|
||||
time_str = idle_tag.getAttr('since')
|
||||
tim = helpers.datetime_tuple(time_str)
|
||||
self.idle_time = timegm(tim)
|
||||
xtags = self.stanza.getTags('x')
|
||||
for x in xtags:
|
||||
namespace = x.getNamespace()
|
||||
|
|
|
@ -94,8 +94,7 @@ class Contact(CommonContact):
|
|||
"""
|
||||
def __init__(self, jid, account, name='', groups=None, show='', status='',
|
||||
sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
|
||||
our_chatstate=None, chatstate=None, last_status_time=None, msg_log_id=None,
|
||||
last_activity_time=None):
|
||||
our_chatstate=None, chatstate=None, idle_time=None, msg_log_id=None):
|
||||
if not isinstance(jid, str):
|
||||
print('no str')
|
||||
if groups is None:
|
||||
|
@ -113,8 +112,7 @@ class Contact(CommonContact):
|
|||
self.priority = priority
|
||||
self.keyID = keyID
|
||||
self.msg_log_id = msg_log_id
|
||||
self.last_status_time = last_status_time
|
||||
self.last_activity_time = last_activity_time
|
||||
self.idle_time = idle_time
|
||||
|
||||
self.pep = {}
|
||||
|
||||
|
@ -248,8 +246,7 @@ class LegacyContactsAPI:
|
|||
|
||||
def create_contact(self, jid, account, name='', groups=None, show='',
|
||||
status='', sub='', ask='', resource='', priority=0, keyID='',
|
||||
client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None,
|
||||
last_activity_time=None):
|
||||
client_caps=None, our_chatstate=None, chatstate=None, idle_time=None):
|
||||
if groups is None:
|
||||
groups = []
|
||||
# Use Account object if available
|
||||
|
@ -258,8 +255,7 @@ class LegacyContactsAPI:
|
|||
show=show, status=status, sub=sub, ask=ask, resource=resource,
|
||||
priority=priority, keyID=keyID, client_caps=client_caps,
|
||||
our_chatstate=our_chatstate, chatstate=chatstate,
|
||||
last_status_time=last_status_time,
|
||||
last_activity_time=last_activity_time)
|
||||
idle_time=idle_time)
|
||||
|
||||
def create_self_contact(self, jid, account, resource, show, status, priority,
|
||||
name='', keyID=''):
|
||||
|
@ -288,8 +284,7 @@ class LegacyContactsAPI:
|
|||
resource=contact.resource, priority=contact.priority,
|
||||
keyID=contact.keyID, client_caps=contact.client_caps,
|
||||
our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
|
||||
last_status_time=contact.last_status_time,
|
||||
last_activity_time=contact.last_activity_time)
|
||||
idle_time=contact.idle_time)
|
||||
|
||||
def add_contact(self, account, contact):
|
||||
if account not in self._accounts:
|
||||
|
|
|
@ -568,30 +568,6 @@ class Interface:
|
|||
if obj.conn.name in self.show_vcard_when_connect:
|
||||
self.show_vcard_when_connect.remove(obj.conn.name)
|
||||
|
||||
def handle_event_last_status_time(self, obj):
|
||||
# ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
|
||||
account = obj.conn.name
|
||||
c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
|
||||
tooltip_window = self.roster.tree.get_tooltip_window()
|
||||
if obj.seconds < 0:
|
||||
if tooltip_window:
|
||||
tooltip_window.update_last_time(c, True)
|
||||
return
|
||||
|
||||
if c: # c can be none if it's a gc contact
|
||||
if obj.status:
|
||||
c.status = obj.status
|
||||
self.roster.draw_contact(c.jid, account) # draw offline status
|
||||
last_time = time.localtime(time.time() - obj.seconds)
|
||||
if c.show == 'offline':
|
||||
c.last_status_time = last_time
|
||||
else:
|
||||
c.last_activity_time = last_time
|
||||
|
||||
# Set last time on roster tooltip
|
||||
if tooltip_window:
|
||||
tooltip_window.update_last_time(c)
|
||||
|
||||
def handle_event_gc_config(self, obj):
|
||||
#('GC_CONFIG', account, (jid, form_node)) config is a dict
|
||||
account = obj.conn.name
|
||||
|
@ -1148,12 +1124,6 @@ class Interface:
|
|||
notify.popup(event_type, jid, account, msg_type, path_to_image=path,
|
||||
title=event_type, text=txt)
|
||||
|
||||
@staticmethod
|
||||
def ask_offline_status(account):
|
||||
for contact in gajim.contacts.iter_contacts(account):
|
||||
gajim.connections[account].request_last_status_time(contact.jid,
|
||||
contact.resource)
|
||||
|
||||
def handle_event_signed_in(self, obj):
|
||||
"""
|
||||
SIGNED_IN event is emitted when we sign in, so handle it
|
||||
|
@ -1164,10 +1134,6 @@ class Interface:
|
|||
gajim.block_signed_in_notifications[account] = True
|
||||
state = self.sleeper.getState()
|
||||
connected = obj.conn.connected
|
||||
if gajim.config.get('ask_offline_status_on_connection'):
|
||||
# Ask offline status in 1 minute so w'are sure we got all online
|
||||
# presences
|
||||
GLib.timeout_add_seconds(60, self.ask_offline_status, account)
|
||||
if state != sleepy.STATE_UNKNOWN and connected in (2, 3):
|
||||
# we go online or free for chat, so we activate auto status
|
||||
gajim.sleeper_state[account] = 'online'
|
||||
|
@ -1608,7 +1574,6 @@ class Interface:
|
|||
'jingle-error-received': [self.handle_event_jingle_error],
|
||||
'jingle-request-received': [self.handle_event_jingle_incoming],
|
||||
'jingleFT-cancelled-received': [self.handle_event_jingleft_cancel],
|
||||
'last-result-received': [self.handle_event_last_status_time],
|
||||
'message-error': [self.handle_event_msgerror],
|
||||
'message-not-sent': [self.handle_event_msgnotsent],
|
||||
'message-sent': [self.handle_event_msgsent],
|
||||
|
@ -2282,33 +2247,47 @@ class Interface:
|
|||
if account not in gajim.sleeper_state or \
|
||||
not gajim.sleeper_state[account]:
|
||||
continue
|
||||
if state == sleepy.STATE_AWAKE and \
|
||||
gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
|
||||
# we go online
|
||||
self.roster.send_status(account, 'online',
|
||||
if state == sleepy.STATE_AWAKE:
|
||||
if gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
|
||||
# we go online
|
||||
self.roster.send_status(account, 'online',
|
||||
gajim.status_before_autoaway[account])
|
||||
gajim.status_before_autoaway[account] = ''
|
||||
gajim.sleeper_state[account] = 'online'
|
||||
elif state == sleepy.STATE_AWAY and \
|
||||
gajim.sleeper_state[account] == 'online' and \
|
||||
gajim.config.get('autoaway'):
|
||||
# we save out online status
|
||||
gajim.status_before_autoaway[account] = \
|
||||
gajim.status_before_autoaway[account] = ''
|
||||
gajim.sleeper_state[account] = 'online'
|
||||
if gajim.sleeper_state[account] == 'idle':
|
||||
# we go to the previous state
|
||||
connected = gajim.connections[account].connected
|
||||
self.roster.send_status(account, gajim.SHOW_LIST[connected],
|
||||
gajim.status_before_autoaway[account])
|
||||
gajim.status_before_autoaway[account] = ''
|
||||
gajim.sleeper_state[account] = 'off'
|
||||
elif state == sleepy.STATE_AWAY and gajim.config.get('autoaway'):
|
||||
if gajim.sleeper_state[account] == 'online':
|
||||
# we save out online status
|
||||
gajim.status_before_autoaway[account] = \
|
||||
gajim.connections[account].status
|
||||
# we go away (no auto status) [we pass True to auto param]
|
||||
auto_message = gajim.config.get('autoaway_message')
|
||||
if not auto_message:
|
||||
auto_message = gajim.connections[account].status
|
||||
else:
|
||||
auto_message = auto_message.replace('$S', '%(status)s')
|
||||
auto_message = auto_message.replace('$T', '%(time)s')
|
||||
auto_message = auto_message % {
|
||||
# we go away (no auto status) [we pass True to auto param]
|
||||
auto_message = gajim.config.get('autoaway_message')
|
||||
if not auto_message:
|
||||
auto_message = gajim.connections[account].status
|
||||
else:
|
||||
auto_message = auto_message.replace('$S', '%(status)s')
|
||||
auto_message = auto_message.replace('$T', '%(time)s')
|
||||
auto_message = auto_message % {
|
||||
'status': gajim.status_before_autoaway[account],
|
||||
'time': gajim.config.get('autoawaytime')
|
||||
}
|
||||
self.roster.send_status(account, 'away', auto_message,
|
||||
auto=True)
|
||||
gajim.sleeper_state[account] = 'autoaway'
|
||||
}
|
||||
self.roster.send_status(account, 'away', auto_message,
|
||||
auto=True)
|
||||
gajim.sleeper_state[account] = 'autoaway'
|
||||
elif gajim.sleeper_state[account] == 'off':
|
||||
# we save out online status
|
||||
gajim.status_before_autoaway[account] = \
|
||||
gajim.connections[account].status
|
||||
connected = gajim.connections[account].connected
|
||||
self.roster.send_status(account, gajim.SHOW_LIST[connected],
|
||||
gajim.status_before_autoaway[account], auto=True)
|
||||
gajim.sleeper_state[account] = 'idle'
|
||||
elif state == sleepy.STATE_XA and \
|
||||
gajim.sleeper_state[account] in ('online', 'autoaway',
|
||||
'autoaway-forced') and gajim.config.get('autoxa'):
|
||||
|
|
|
@ -107,8 +107,6 @@ class Remote:
|
|||
bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
|
||||
self.signal_object = SignalObject(bus_name)
|
||||
|
||||
gajim.ged.register_event_handler('last-result-received', ged.POSTGUI,
|
||||
self.on_last_status_time)
|
||||
gajim.ged.register_event_handler('version-result-received', ged.POSTGUI,
|
||||
self.on_os_info)
|
||||
gajim.ged.register_event_handler('time-result-received', ged.POSTGUI,
|
||||
|
@ -150,10 +148,6 @@ class Remote:
|
|||
self.raise_signal('MessageSent', (obj.conn.name, [
|
||||
obj.jid, obj.message, obj.keyID, chatstate]))
|
||||
|
||||
def on_last_status_time(self, obj):
|
||||
self.raise_signal('LastStatusTime', (obj.conn.name, [
|
||||
obj.jid, obj.resource, obj.seconds, obj.status]))
|
||||
|
||||
def on_os_info(self, obj):
|
||||
self.raise_signal('OsInfo', (obj.conn.name, [obj.jid, obj.resource,
|
||||
obj.client_info, obj.os_info]))
|
||||
|
@ -278,10 +272,6 @@ class SignalObject(dbus.service.Object):
|
|||
def VcardInfo(self, account_and_vcard):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(INTERFACE, signature='av')
|
||||
def LastStatusTime(self, account_and_array):
|
||||
pass
|
||||
|
||||
@dbus.service.signal(INTERFACE, signature='av')
|
||||
def OsInfo(self, account_and_array):
|
||||
pass
|
||||
|
|
|
@ -2117,9 +2117,6 @@ class RosterWindow:
|
|||
def send_status(self, account, status, txt, auto=False, to=None):
|
||||
if status != 'offline':
|
||||
if to is None:
|
||||
if status == gajim.connections[account].get_status() and \
|
||||
txt == gajim.connections[account].status:
|
||||
return
|
||||
gajim.config.set_per('accounts', account, 'last_status', status)
|
||||
gajim.config.set_per('accounts', account, 'last_status_msg',
|
||||
helpers.to_one_line(txt))
|
||||
|
@ -2194,7 +2191,8 @@ class RosterWindow:
|
|||
if gc_control.account == account:
|
||||
if gajim.gc_connected[account][gc_control.room_jid]:
|
||||
gajim.connections[account].send_gc_status(
|
||||
gc_control.nick, gc_control.room_jid, status, txt)
|
||||
gc_control.nick, gc_control.room_jid, status, txt,
|
||||
auto=auto)
|
||||
if was_invisible and status != 'offline':
|
||||
# We come back from invisible, join bookmarks
|
||||
gajim.interface.auto_join_bookmarks(account)
|
||||
|
|
|
@ -70,7 +70,6 @@ class BaseTooltip:
|
|||
self.win = None
|
||||
self.id = None
|
||||
self.cur_data = None
|
||||
self.check_last_time = None
|
||||
self.shown = False
|
||||
self.position_computed = False
|
||||
|
||||
|
@ -142,7 +141,7 @@ class BaseTooltip:
|
|||
self.screen.get_width() + half_width:
|
||||
self.preferred_position[0] = self.screen.get_width() - \
|
||||
rect.width
|
||||
elif not self.check_last_time:
|
||||
else:
|
||||
self.preferred_position[0] -= half_width
|
||||
self.position_computed = True
|
||||
self.win.move(self.preferred_position[0], self.preferred_position[1])
|
||||
|
@ -184,7 +183,6 @@ class BaseTooltip:
|
|||
self.win = None
|
||||
self.id = None
|
||||
self.cur_data = None
|
||||
self.check_last_time = None
|
||||
self.shown = False
|
||||
|
||||
class StatusTable:
|
||||
|
@ -230,8 +228,8 @@ class StatusTable:
|
|||
str_status += ' - <i>' + status + '</i>'
|
||||
return str_status
|
||||
|
||||
def add_status_row(self, file_path, show, str_status, status_time=None,
|
||||
show_lock=False, indent=True):
|
||||
def add_status_row(self, file_path, show, str_status, show_lock=False,
|
||||
indent=True):
|
||||
"""
|
||||
Append a new row with status icon to the table
|
||||
"""
|
||||
|
@ -420,7 +418,6 @@ class RosterTooltip(Gtk.Window, StatusTable):
|
|||
StatusTable.__init__(self)
|
||||
self.create_table()
|
||||
self.row = None
|
||||
self.check_last_time = {}
|
||||
self.contact_jid = None
|
||||
self.last_widget = None
|
||||
self.num_resources = 0
|
||||
|
@ -433,11 +430,10 @@ class RosterTooltip(Gtk.Window, StatusTable):
|
|||
self.xml = gtkgui_helpers.get_gtk_builder('tooltip_roster_contact.ui')
|
||||
for name in ('name', 'status', 'jid', 'user_show', 'fillelement',
|
||||
'resource', 'avatar', 'resource_label', 'pgp', 'pgp_label',
|
||||
'jid_label', 'tooltip_grid', 'idle_since', 'idle_for',
|
||||
'idle_since_label', 'idle_for_label', 'mood', 'tune',
|
||||
'activity', 'location', 'tune_label', 'location_label',
|
||||
'activity_label', 'mood_label', 'sub_label', 'sub',
|
||||
'status_label'):
|
||||
'jid_label', 'tooltip_grid', 'idle_since', 'idle_since_label',
|
||||
'mood', 'tune', 'activity', 'location', 'tune_label',
|
||||
'location_label', 'activity_label', 'mood_label', 'sub_label',
|
||||
'sub', 'status_label'):
|
||||
setattr(self, name, self.xml.get_object(name))
|
||||
|
||||
self.add(self.tooltip_grid)
|
||||
|
@ -612,8 +608,7 @@ class RosterTooltip(Gtk.Window, StatusTable):
|
|||
|
||||
status_line = self.get_status_info(acontact.resource,
|
||||
acontact.priority, acontact.show, status)
|
||||
self.add_status_row(file_path, icon_name, status_line,
|
||||
acontact.last_status_time)
|
||||
self.add_status_row(file_path, icon_name, status_line)
|
||||
if add_text:
|
||||
self.add_text_row(acontact.status, 2)
|
||||
|
||||
|
@ -621,31 +616,12 @@ class RosterTooltip(Gtk.Window, StatusTable):
|
|||
self.table.show_all()
|
||||
|
||||
else: # only one resource
|
||||
if contact.show:
|
||||
request_time = False
|
||||
try:
|
||||
last_time = self.check_last_time[contact]
|
||||
if isinstance(last_time, float) and last_time < time.time() - 60:
|
||||
request_time = True
|
||||
except KeyError:
|
||||
request_time = True
|
||||
|
||||
if request_time:
|
||||
if contact.show == 'offline':
|
||||
gajim.connections[account].\
|
||||
request_last_status_time(contact.jid, '')
|
||||
elif contact.resource:
|
||||
gajim.connections[account].\
|
||||
request_last_status_time(
|
||||
contact.jid, contact.resource)
|
||||
self.check_last_time[contact] = time.time()
|
||||
|
||||
if contact.status:
|
||||
status = contact.status.strip()
|
||||
if status:
|
||||
self.status.set_text(status)
|
||||
self.status.show()
|
||||
self.status_label.show()
|
||||
if contact.show and contact.status:
|
||||
status = contact.status.strip()
|
||||
if status:
|
||||
self.status.set_text(status)
|
||||
self.status.show()
|
||||
self.status_label.show()
|
||||
|
||||
# PEP Info
|
||||
self._append_pep_info(contact)
|
||||
|
@ -736,51 +712,24 @@ class RosterTooltip(Gtk.Window, StatusTable):
|
|||
self.location_label.show()
|
||||
|
||||
def _set_idle_time(self, contact):
|
||||
if contact.last_activity_time:
|
||||
last_active = datetime(*contact.last_activity_time[:6])
|
||||
if contact.idle_time:
|
||||
idle_color = gajim.config.get('tooltip_idle_color')
|
||||
idle_time = contact.idle_time
|
||||
idle_time = time.localtime(contact.idle_time)
|
||||
idle_time = datetime(*(idle_time[:6]))
|
||||
current = datetime.now()
|
||||
|
||||
diff = current - last_active
|
||||
diff = timedelta(diff.days, diff.seconds)
|
||||
|
||||
if last_active.date() == current.date():
|
||||
formatted = last_active.strftime("%X")
|
||||
if idle_time.date() == current.date():
|
||||
formatted = idle_time.strftime("%X")
|
||||
else:
|
||||
formatted = last_active.strftime("%c")
|
||||
|
||||
# Do not show the "Idle since" and "Idle for" items if there
|
||||
# is no meaningful difference between last activity time and
|
||||
# current time.
|
||||
if diff.days > 0 or diff.seconds > 0:
|
||||
idle_color = gajim.config.get('tooltip_idle_color')
|
||||
idle_markup = "<span foreground='{}'>{}</span>".format(idle_color, formatted)
|
||||
self.idle_since.set_markup(idle_markup)
|
||||
self.idle_since.show()
|
||||
self.idle_since_label.show()
|
||||
idle_markup = "<span foreground='{}'>{}</span>".format(idle_color, str(diff))
|
||||
self.idle_for.set_markup(idle_markup)
|
||||
self.idle_for_label.show()
|
||||
self.idle_for.show()
|
||||
formatted = idle_time.strftime("%c")
|
||||
idle_markup = "<span foreground='{}'>{}</span>".format(idle_color,
|
||||
formatted)
|
||||
self.idle_since.set_markup(idle_markup)
|
||||
self.idle_since.show()
|
||||
self.idle_since_label.show()
|
||||
|
||||
if contact.show and self.num_resources < 2:
|
||||
show = helpers.get_uf_show(contact.show)
|
||||
if contact.last_status_time:
|
||||
if contact.show == 'offline':
|
||||
text = ' - ' + _('Last status: %s')
|
||||
else:
|
||||
text = _(' since %s')
|
||||
|
||||
if time.strftime('%j', time.localtime()) == \
|
||||
time.strftime('%j', contact.last_status_time):
|
||||
# it's today, show only the locale hour representation
|
||||
local_time = time.strftime('%X', contact.last_status_time)
|
||||
else:
|
||||
# time.strftime returns locale encoded string
|
||||
local_time = time.strftime('%c', contact.last_status_time)
|
||||
|
||||
text = text % local_time
|
||||
show += text
|
||||
|
||||
# Contact is Groupchat
|
||||
if (self.account and
|
||||
self.prim_contact.jid in gajim.gc_connected[self.account]):
|
||||
|
@ -805,15 +754,6 @@ class RosterTooltip(Gtk.Window, StatusTable):
|
|||
return contact.show
|
||||
return 'not in roster'
|
||||
|
||||
def update_last_time(self, contact, error=False):
|
||||
if not contact:
|
||||
return
|
||||
if error:
|
||||
self.check_last_time[contact] = 'error'
|
||||
return
|
||||
if contact.jid == self.contact_jid:
|
||||
self._set_idle_time(contact)
|
||||
|
||||
|
||||
class FileTransfersTooltip(BaseTooltip):
|
||||
"""
|
||||
|
|
|
@ -134,8 +134,6 @@ class VcardWindow:
|
|||
|
||||
gajim.ged.register_event_handler('version-result-received', ged.GUI1,
|
||||
self.set_os_info)
|
||||
gajim.ged.register_event_handler('last-result-received', ged.GUI2,
|
||||
self.set_last_status_time)
|
||||
gajim.ged.register_event_handler('time-result-received', ged.GUI1,
|
||||
self.set_entity_time)
|
||||
gajim.ged.register_event_handler('vcard-received', ged.GUI1,
|
||||
|
@ -181,8 +179,6 @@ class VcardWindow:
|
|||
connection.store_annotations()
|
||||
gajim.ged.remove_event_handler('version-result-received', ged.GUI1,
|
||||
self.set_os_info)
|
||||
gajim.ged.remove_event_handler('last-result-received', ged.GUI2,
|
||||
self.set_last_status_time)
|
||||
gajim.ged.remove_event_handler('time-result-received', ged.GUI1,
|
||||
self.set_entity_time)
|
||||
gajim.ged.remove_event_handler('vcard-received', ged.GUI1,
|
||||
|
@ -294,13 +290,6 @@ class VcardWindow:
|
|||
self.clear_values()
|
||||
self.set_values(obj.vcard_dict)
|
||||
|
||||
def set_last_status_time(self, obj):
|
||||
if obj.conn.name != self.account:
|
||||
return
|
||||
if obj.fjid != self.real_jid:
|
||||
return
|
||||
self.fill_status_label()
|
||||
|
||||
def set_os_info(self, obj):
|
||||
if obj.conn.name != self.account:
|
||||
return
|
||||
|
@ -387,18 +376,12 @@ class VcardWindow:
|
|||
stats = helpers.get_uf_show(self.contact.show)
|
||||
if self.contact.status:
|
||||
stats += ': ' + self.contact.status
|
||||
if self.contact.last_status_time:
|
||||
stats += '\n' + _('since %s') % time.strftime('%c',
|
||||
self.contact.last_status_time)
|
||||
for c in connected_contact_list:
|
||||
if c.resource != self.contact.resource:
|
||||
stats += '\n'
|
||||
stats += helpers.get_uf_show(c.show)
|
||||
if c.status:
|
||||
stats += ': ' + c.status
|
||||
if c.last_status_time:
|
||||
stats += '\n' + _('since %s') % time.strftime('%c',
|
||||
c.last_status_time)
|
||||
else: # Maybe gc_vcard ?
|
||||
stats = helpers.get_uf_show(self.contact.show)
|
||||
if self.contact.status:
|
||||
|
@ -452,16 +435,6 @@ class VcardWindow:
|
|||
if not self.contact.status:
|
||||
self.contact.status = ''
|
||||
|
||||
# Request list time status only if contact is offline
|
||||
if self.contact.show == 'offline':
|
||||
if self.gc_contact:
|
||||
j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
|
||||
gajim.connections[self.account].request_last_status_time(j, r,
|
||||
self.contact.jid)
|
||||
else:
|
||||
gajim.connections[self.account].request_last_status_time(
|
||||
self.contact.jid, '')
|
||||
|
||||
# do not wait for os_info if contact is not connected or has error
|
||||
# additional check for observer is needed, as show is offline for him
|
||||
if self.contact.show in ('offline', 'error')\
|
||||
|
@ -599,9 +572,6 @@ class ZeroconfVcardWindow:
|
|||
stats += helpers.get_uf_show(c.show)
|
||||
if c.status:
|
||||
stats += ': ' + c.status
|
||||
if c.last_status_time:
|
||||
stats += '\n' + _('since %s') % time.strftime('%c',
|
||||
c.last_status_time).decode(locale.getpreferredencoding())
|
||||
one = False
|
||||
else: # Maybe gc_vcard ?
|
||||
stats = helpers.get_uf_show(self.contact.show)
|
||||
|
|
Loading…
Reference in New Issue