summaryrefslogtreecommitdiff
path: root/poezio/core/handlers.py
diff options
context:
space:
mode:
authormathieui <mathieui@mathieui.net>2017-11-12 15:03:09 +0100
committermathieui <mathieui@mathieui.net>2017-11-12 15:03:09 +0100
commitd55cc5872503567775f0d7a7731d6f489bf2299b (patch)
tree725f9e7b8144d36054447b3c82edfb45bda8df1d /poezio/core/handlers.py
parent92496db823db34f7f7fb1ab31eaef093a707c3e8 (diff)
downloadpoezio-d55cc5872503567775f0d7a7731d6f489bf2299b.tar.gz
poezio-d55cc5872503567775f0d7a7731d6f489bf2299b.tar.bz2
poezio-d55cc5872503567775f0d7a7731d6f489bf2299b.tar.xz
poezio-d55cc5872503567775f0d7a7731d6f489bf2299b.zip
yapf -ir
Diffstat (limited to 'poezio/core/handlers.py')
-rw-r--r--poezio/core/handlers.py659
1 files changed, 422 insertions, 237 deletions
diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py
index e6d54558..ac6ae573 100644
--- a/poezio/core/handlers.py
+++ b/poezio/core/handlers.py
@@ -80,6 +80,7 @@ class HandlerCore:
"""
Enable carbons & blocking on session start if wanted and possible
"""
+
def callback(iq):
if not iq:
return
@@ -87,21 +88,23 @@ class HandlerCore:
rostertab = self.core.get_tab_by_name('Roster', tabs.RosterInfoTab)
rostertab.check_blocking(features)
rostertab.check_saslexternal(features)
- if (config.get('enable_carbons') and
- 'urn:xmpp:carbons:2' in features):
+ if (config.get('enable_carbons')
+ and 'urn:xmpp:carbons:2' in features):
self.core.xmpp.plugin['xep_0280'].enable()
self.core.check_bookmark_storage(features)
- self.core.xmpp.plugin['xep_0030'].get_info(jid=self.core.xmpp.boundjid.domain,
- callback=callback)
+ self.core.xmpp.plugin['xep_0030'].get_info(
+ jid=self.core.xmpp.boundjid.domain, callback=callback)
def on_carbon_received(self, message):
"""
Carbon <received/> received
"""
+
def ignore_message(recv):
log.debug('%s has category conference, ignoring carbon',
recv['from'].server)
+
def receive_message(recv):
recv['to'] = self.core.xmpp.boundjid.full
if recv['receipt']:
@@ -109,12 +112,14 @@ class HandlerCore:
self.on_normal_message(recv)
recv = message['carbon_received']
- if (recv['from'].bare not in roster or
- roster[recv['from'].bare].subscription == 'none'):
- fixes.has_identity(self.core.xmpp, recv['from'].server,
- identity='conference',
- on_true=functools.partial(ignore_message, recv),
- on_false=functools.partial(receive_message, recv))
+ if (recv['from'].bare not in roster
+ or roster[recv['from'].bare].subscription == 'none'):
+ fixes.has_identity(
+ self.core.xmpp,
+ recv['from'].server,
+ identity='conference',
+ on_true=functools.partial(ignore_message, recv),
+ on_false=functools.partial(receive_message, recv))
return
else:
receive_message(recv)
@@ -123,20 +128,24 @@ class HandlerCore:
"""
Carbon <sent/> received
"""
+
def ignore_message(sent):
log.debug('%s has category conference, ignoring carbon',
sent['to'].server)
+
def send_message(sent):
sent['from'] = self.core.xmpp.boundjid.full
self.on_normal_message(sent)
sent = message['carbon_sent']
- if (sent['to'].bare not in roster or
- roster[sent['to'].bare].subscription == 'none'):
- fixes.has_identity(self.core.xmpp, sent['to'].server,
- identity='conference',
- on_true=functools.partial(ignore_message, sent),
- on_false=functools.partial(send_message, sent))
+ if (sent['to'].bare not in roster
+ or roster[sent['to'].bare].subscription == 'none'):
+ fixes.has_identity(
+ self.core.xmpp,
+ sent['to'].server,
+ identity='conference',
+ on_true=functools.partial(ignore_message, sent),
+ on_false=functools.partial(send_message, sent))
else:
send_message(sent)
@@ -150,7 +159,11 @@ class HandlerCore:
if jid.bare in self.core.pending_invites:
return
# there are 2 'x' tags in the messages, making message['x'] useless
- invite = StanzaBase(self.core.xmpp, xml=message.xml.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite'))
+ invite = StanzaBase(
+ self.core.xmpp,
+ xml=message.xml.find(
+ '{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite'
+ ))
inviter = invite['from']
reason = invite['reason']
password = invite['password']
@@ -204,7 +217,9 @@ class HandlerCore:
When receiving private message from a muc OR a normal message
(from one of our contacts)
"""
- if message.xml.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite') is not None:
+ if message.xml.find(
+ '{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite'
+ ) is not None:
return
if message['type'] == 'groupchat':
return
@@ -228,7 +243,8 @@ class HandlerCore:
self.core.room_error(message, jid_from.bare)
else:
text = self.core.get_error_message(message)
- p_tab = self.core.get_tab_by_name(jid_from.full, tabs.PrivateTab)
+ p_tab = self.core.get_tab_by_name(jid_from.full,
+ tabs.PrivateTab)
if p_tab:
p_tab.add_error(text)
else:
@@ -240,12 +256,11 @@ class HandlerCore:
self.core.information(error_msg, 'Error')
return
error = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_CHAR_NACK),
- error_msg)
+ error_msg)
if not tab.nack_message('\n' + error, message['id'], message['to']):
tab.add_message(error, typ=0)
self.core.refresh_window()
-
def on_normal_message(self, message):
"""
When receiving "normal" messages (not a private message from a
@@ -254,14 +269,18 @@ class HandlerCore:
if message['type'] == 'error':
return
elif message['type'] == 'headline' and message['body']:
- return self.core.information('%s says: %s' % (message['from'], message['body']), 'Headline')
+ return self.core.information(
+ '%s says: %s' % (message['from'], message['body']), 'Headline')
- use_xhtml = config.get_by_tabname('enable_xhtml_im', message['from'].bare)
+ use_xhtml = config.get_by_tabname('enable_xhtml_im',
+ message['from'].bare)
tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images')
extract_images = config.get('extract_inline_images')
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
- tmp_dir=tmp_dir,
- extract_images=extract_images)
+ body = xhtml.get_body_from_message_stanza(
+ message,
+ use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
if not body:
if not self.core.xmpp.plugin['xep_0380'].has_eme(message):
return
@@ -279,7 +298,8 @@ class HandlerCore:
remote_nick = roster[conv_jid.bare].name
# check for a received nick
if not remote_nick and config.get('enable_user_nick'):
- if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None:
+ if message.xml.find(
+ '{http://jabber.org/protocol/nick}nick') is not None:
remote_nick = message['nick']['nick']
if not remote_nick:
remote_nick = conv_jid.user
@@ -298,7 +318,8 @@ class HandlerCore:
return
conversation = self.core.get_conversation_by_jid(conv_jid, create=True)
- if isinstance(conversation, tabs.DynamicConversationTab) and conv_jid.resource:
+ if isinstance(conversation,
+ tabs.DynamicConversationTab) and conv_jid.resource:
conversation.lock(conv_jid.resource)
if not own and not conversation.nick:
@@ -312,9 +333,11 @@ class HandlerCore:
self.core.events.trigger('conversation_msg', message, conversation)
if not message['body']:
return
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
- tmp_dir=tmp_dir,
- extract_images=extract_images)
+ body = xhtml.get_body_from_message_stanza(
+ message,
+ use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
delayed, date = common.find_delayed_tag(message)
def try_modify():
@@ -324,21 +347,27 @@ class HandlerCore:
if replaced_id and config.get_by_tabname('group_corrections',
conv_jid.bare):
try:
- conversation.modify_message(body, replaced_id, message['id'], jid=jid,
- nickname=remote_nick)
+ conversation.modify_message(
+ body,
+ replaced_id,
+ message['id'],
+ jid=jid,
+ nickname=remote_nick)
return True
except CorrectionError:
log.debug('Unable to correct a message', exc_info=True)
return False
if not try_modify():
- conversation.add_message(body, date,
- nickname=remote_nick,
- nick_color=color,
- history=delayed,
- identifier=message['id'],
- jid=jid,
- typ=1)
+ conversation.add_message(
+ body,
+ date,
+ nickname=remote_nick,
+ nick_color=color,
+ history=delayed,
+ identifier=message['id'],
+ jid=jid,
+ typ=1)
if conversation.remote_wants_chatstates is None and not delayed:
if message['chat_state']:
@@ -366,7 +395,8 @@ class HandlerCore:
return
log.debug('Received 0084 avatar update from %s', jid)
try:
- metadata = msg['pubsub_event']['items']['item']['avatar_metadata']['items']
+ metadata = msg['pubsub_event']['items']['item']['avatar_metadata'][
+ 'items']
except Exception:
log.debug('Failed getting metadata from 0084:', exc_info=True)
return
@@ -387,12 +417,16 @@ class HandlerCore:
# If we didn’t have any, query the data instead.
if not info['url']:
try:
- result = yield from self.core.xmpp['xep_0084'].retrieve_avatar(jid,
- avatar_hash,
- timeout=60)
- contact.avatar = result['pubsub']['items']['item']['avatar_data']['value']
+ result = yield from self.core.xmpp[
+ 'xep_0084'].retrieve_avatar(
+ jid, avatar_hash, timeout=60)
+ contact.avatar = result['pubsub']['items']['item'][
+ 'avatar_data']['value']
except Exception:
- log.debug('Failed retrieving 0084 data from %s:', jid, exc_info=True)
+ log.debug(
+ 'Failed retrieving 0084 data from %s:',
+ jid,
+ exc_info=True)
continue
log.debug('Received %s avatar: %s', jid, info['type'])
@@ -427,9 +461,8 @@ class HandlerCore:
# If we didn’t have any, query the vCard instead.
try:
- result = yield from self.core.xmpp['xep_0054'].get_vcard(jid,
- cached=True,
- timeout=60)
+ result = yield from self.core.xmpp['xep_0054'].get_vcard(
+ jid, cached=True, timeout=60)
avatar = result['vcard_temp']['PHOTO']
contact.avatar = avatar['BINVAL']
except Exception:
@@ -473,25 +506,32 @@ class HandlerCore:
item = item['gaming']
# only name and server_address are used for now
contact.gaming = {
- 'character_name': item['character_name'],
- 'character_profile': item['character_profile'],
- 'name': item['name'],
- 'level': item['level'],
- 'uri': item['uri'],
- 'server_name': item['server_name'],
- 'server_address': item['server_address'],
- }
+ 'character_name': item['character_name'],
+ 'character_profile': item['character_profile'],
+ 'name': item['name'],
+ 'level': item['level'],
+ 'uri': item['uri'],
+ 'server_name': item['server_name'],
+ 'server_address': item['server_address'],
+ }
else:
contact.gaming = {}
if contact.gaming:
- logger.log_roster_change(contact.bare_jid, 'is playing %s' % (common.format_gaming_string(contact.gaming)))
+ logger.log_roster_change(
+ contact.bare_jid, 'is playing %s' %
+ (common.format_gaming_string(contact.gaming)))
- if old_gaming != contact.gaming and config.get_by_tabname('display_gaming_notifications', contact.bare_jid):
+ if old_gaming != contact.gaming and config.get_by_tabname(
+ 'display_gaming_notifications', contact.bare_jid):
if contact.gaming:
- self.core.information('%s is playing %s' % (contact.bare_jid, common.format_gaming_string(contact.gaming)), 'Gaming')
+ self.core.information(
+ '%s is playing %s' %
+ (contact.bare_jid,
+ common.format_gaming_string(contact.gaming)), 'Gaming')
else:
- self.core.information(contact.bare_jid + ' stopped playing.', 'Gaming')
+ self.core.information(contact.bare_jid + ' stopped playing.',
+ 'Gaming')
def on_mood_event(self, message):
"""
@@ -518,13 +558,18 @@ class HandlerCore:
contact.mood = ''
if contact.mood:
- logger.log_roster_change(contact.bare_jid, 'has now the mood: %s' % contact.mood)
+ logger.log_roster_change(contact.bare_jid,
+ 'has now the mood: %s' % contact.mood)
- if old_mood != contact.mood and config.get_by_tabname('display_mood_notifications', contact.bare_jid):
+ if old_mood != contact.mood and config.get_by_tabname(
+ 'display_mood_notifications', contact.bare_jid):
if contact.mood:
- self.core.information('Mood from '+ contact.bare_jid + ': ' + contact.mood, 'Mood')
+ self.core.information(
+ 'Mood from ' + contact.bare_jid + ': ' + contact.mood,
+ 'Mood')
else:
- self.core.information(contact.bare_jid + ' stopped having his/her mood.', 'Mood')
+ self.core.information(
+ contact.bare_jid + ' stopped having his/her mood.', 'Mood')
def on_activity_event(self, message):
"""
@@ -537,7 +582,8 @@ class HandlerCore:
roster.modified()
item = message['pubsub_event']['items']['item']
old_activity = contact.activity
- if item.xml.find('{http://jabber.org/protocol/activity}activity') is not None:
+ if item.xml.find(
+ '{http://jabber.org/protocol/activity}activity') is not None:
try:
activity = item['activity']['value']
except ValueError:
@@ -557,13 +603,18 @@ class HandlerCore:
contact.activity = ''
if contact.activity:
- logger.log_roster_change(contact.bare_jid, 'has now the activity %s' % contact.activity)
+ logger.log_roster_change(
+ contact.bare_jid, 'has now the activity %s' % contact.activity)
- if old_activity != contact.activity and config.get_by_tabname('display_activity_notifications', contact.bare_jid):
+ if old_activity != contact.activity and config.get_by_tabname(
+ 'display_activity_notifications', contact.bare_jid):
if contact.activity:
- self.core.information('Activity from '+ contact.bare_jid + ': ' + contact.activity, 'Activity')
+ self.core.information('Activity from ' + contact.bare_jid +
+ ': ' + contact.activity, 'Activity')
else:
- self.core.information(contact.bare_jid + ' stopped doing his/her activity.', 'Activity')
+ self.core.information(
+ contact.bare_jid + ' stopped doing his/her activity.',
+ 'Activity')
def on_tune_event(self, message):
"""
@@ -579,27 +630,31 @@ class HandlerCore:
if item.xml.find('{http://jabber.org/protocol/tune}tune') is not None:
item = item['tune']
contact.tune = {
- 'artist': item['artist'],
- 'length': item['length'],
- 'rating': item['rating'],
- 'source': item['source'],
- 'title': item['title'],
- 'track': item['track'],
- 'uri': item['uri']
- }
+ 'artist': item['artist'],
+ 'length': item['length'],
+ 'rating': item['rating'],
+ 'source': item['source'],
+ 'title': item['title'],
+ 'track': item['track'],
+ 'uri': item['uri']
+ }
else:
contact.tune = {}
if contact.tune:
- logger.log_roster_change(message['from'].bare, 'is now listening to %s' % common.format_tune_string(contact.tune))
+ logger.log_roster_change(message['from'].bare,
+ 'is now listening to %s' %
+ common.format_tune_string(contact.tune))
- if old_tune != contact.tune and config.get_by_tabname('display_tune_notifications', contact.bare_jid):
+ if old_tune != contact.tune and config.get_by_tabname(
+ 'display_tune_notifications', contact.bare_jid):
if contact.tune:
self.core.information(
- 'Tune from '+ message['from'].bare + ': ' + common.format_tune_string(contact.tune),
- 'Tune')
+ 'Tune from ' + message['from'].bare + ': ' +
+ common.format_tune_string(contact.tune), 'Tune')
else:
- self.core.information(contact.bare_jid + ' stopped listening to music.', 'Tune')
+ self.core.information(
+ contact.bare_jid + ' stopped listening to music.', 'Tune')
def on_groupchat_message(self, message):
"""
@@ -609,14 +664,16 @@ class HandlerCore:
return
room_from = message['from'].bare
- if message['type'] == 'error': # Check if it's an error
+ if message['type'] == 'error': # Check if it's an error
self.core.room_error(message, room_from)
return
tab = self.core.get_tab_by_name(room_from, tabs.MucTab)
if not tab:
- self.core.information("message received for a non-existing room: %s" % (room_from))
- muc.leave_groupchat(self.core.xmpp, room_from, self.core.own_nick, msg='')
+ self.core.information(
+ "message received for a non-existing room: %s" % (room_from))
+ muc.leave_groupchat(
+ self.core.xmpp, room_from, self.core.own_nick, msg='')
return
nick_from = message['mucnick']
@@ -628,9 +685,11 @@ class HandlerCore:
use_xhtml = config.get_by_tabname('enable_xhtml_im', room_from)
tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images')
extract_images = config.get('extract_inline_images')
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
- tmp_dir=tmp_dir,
- extract_images=extract_images)
+ body = xhtml.get_body_from_message_stanza(
+ message,
+ use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
if not body:
return
@@ -639,18 +698,29 @@ class HandlerCore:
replaced = False
if message.xml.find('{urn:xmpp:message-correct:0}replace') is not None:
replaced_id = message['replace']['id']
- if replaced_id is not '' and config.get_by_tabname('group_corrections',
- message['from'].bare):
+ if replaced_id is not '' and config.get_by_tabname(
+ 'group_corrections', message['from'].bare):
try:
delayed_date = date or datetime.now()
- if tab.modify_message(body, replaced_id, message['id'],
+ if tab.modify_message(
+ body,
+ replaced_id,
+ message['id'],
time=delayed_date,
- nickname=nick_from, user=user):
+ nickname=nick_from,
+ user=user):
self.core.events.trigger('highlight', message, tab)
replaced = True
except CorrectionError:
log.debug('Unable to correct a message', exc_info=True)
- if not replaced and tab.add_message(body, date, nick_from, history=delayed, identifier=message['id'], jid=message['from'], typ=1):
+ if not replaced and tab.add_message(
+ body,
+ date,
+ nick_from,
+ history=delayed,
+ identifier=message['id'],
+ jid=message['from'],
+ typ=1):
self.core.events.trigger('highlight', message, tab)
if message['from'].resource == tab.own_nick:
@@ -693,45 +763,61 @@ class HandlerCore:
use_xhtml = config.get_by_tabname('enable_xhtml_im', jid.bare)
tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images')
extract_images = config.get('extract_inline_images')
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
- tmp_dir=tmp_dir,
- extract_images=extract_images)
- tab = self.core.get_tab_by_name(jid.full, tabs.PrivateTab) # get the tab with the private conversation
+ body = xhtml.get_body_from_message_stanza(
+ message,
+ use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
+ tab = self.core.get_tab_by_name(
+ jid.full,
+ tabs.PrivateTab) # get the tab with the private conversation
ignore = config.get_by_tabname('ignore_private', room_from)
- if not tab: # It's the first message we receive: create the tab
+ if not tab: # It's the first message we receive: create the tab
if body and not ignore:
- tab = self.core.open_private_window(room_from, nick_from, False)
+ tab = self.core.open_private_window(room_from, nick_from,
+ False)
if ignore:
self.core.events.trigger('ignored_private', message, tab)
msg = config.get_by_tabname('private_auto_response', room_from)
if msg and body:
- self.core.xmpp.send_message(mto=jid.full, mbody=msg, mtype='chat')
+ self.core.xmpp.send_message(
+ mto=jid.full, mbody=msg, mtype='chat')
return
tab.last_remote_message = datetime.now()
self.core.events.trigger('private_msg', message, tab)
- body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml,
- tmp_dir=tmp_dir,
- extract_images=extract_images)
+ body = xhtml.get_body_from_message_stanza(
+ message,
+ use_xhtml=use_xhtml,
+ tmp_dir=tmp_dir,
+ extract_images=extract_images)
if not body or not tab:
return
replaced = False
user = tab.parent_muc.get_user_by_name(nick_from)
if message.xml.find('{urn:xmpp:message-correct:0}replace') is not None:
replaced_id = message['replace']['id']
- if replaced_id is not '' and config.get_by_tabname('group_corrections',
- room_from):
+ if replaced_id is not '' and config.get_by_tabname(
+ 'group_corrections', room_from):
try:
- tab.modify_message(body, replaced_id, message['id'], user=user, jid=message['from'],
- nickname=nick_from)
+ tab.modify_message(
+ body,
+ replaced_id,
+ message['id'],
+ user=user,
+ jid=message['from'],
+ nickname=nick_from)
replaced = True
except CorrectionError:
log.debug('Unable to correct a message', exc_info=True)
if not replaced:
- tab.add_message(body, time=None, nickname=nick_from,
- forced_user=user,
- identifier=message['id'],
- jid=message['from'],
- typ=1)
+ tab.add_message(
+ body,
+ time=None,
+ nickname=nick_from,
+ forced_user=user,
+ identifier=message['id'],
+ jid=message['from'],
+ typ=1)
if tab.remote_wants_chatstates is None:
if message['chat_state']:
@@ -767,7 +853,8 @@ class HandlerCore:
def _on_chatstate(self, message, state):
if message['type'] == 'chat':
if not self._on_chatstate_normal_conversation(message, state):
- tab = self.core.get_tab_by_name(message['from'].full, tabs.PrivateTab)
+ tab = self.core.get_tab_by_name(message['from'].full,
+ tabs.PrivateTab)
if not tab:
return
self._on_chatstate_private_conversation(message, state)
@@ -862,10 +949,10 @@ class HandlerCore:
contact = roster.get_and_set(jid)
roster.update_contact_groups(contact)
contact.pending_in = True
- self.core.information('%s wants to subscribe to your presence, use '
- '/accept <jid> or /deny <jid> in the roster '
- 'tab to accept or reject the query.' % jid,
- 'Roster')
+ self.core.information(
+ '%s wants to subscribe to your presence, use '
+ '/accept <jid> or /deny <jid> in the roster '
+ 'tab to accept or reject the query.' % jid, 'Roster')
self.core.get_tab_by_number(0).state = 'highlight'
roster.modified()
if isinstance(self.core.current_tab(), tabs.RosterInfoTab):
@@ -876,7 +963,8 @@ class HandlerCore:
jid = presence['from'].bare
contact = roster[jid]
if contact.subscription not in ('both', 'from'):
- self.core.information('%s accepted your contact proposal' % jid, 'Roster')
+ self.core.information('%s accepted your contact proposal' % jid,
+ 'Roster')
if contact.pending_out:
contact.pending_out = False
@@ -892,7 +980,8 @@ class HandlerCore:
if not contact:
return
roster.modified()
- self.core.information('%s does not want to receive your status anymore.' % jid, 'Roster')
+ self.core.information(
+ '%s does not want to receive your status anymore.' % jid, 'Roster')
self.core.get_tab_by_number(0).state = 'highlight'
if isinstance(self.core.current_tab(), tabs.RosterInfoTab):
self.core.refresh_window()
@@ -905,10 +994,13 @@ class HandlerCore:
return
roster.modified()
if contact.pending_out:
- self.core.information('%s rejected your contact proposal' % jid, 'Roster')
+ self.core.information('%s rejected your contact proposal' % jid,
+ 'Roster')
contact.pending_out = False
else:
- self.core.information('%s does not want you to receive his/her/its status anymore.'%jid, 'Roster')
+ self.core.information(
+ '%s does not want you to receive his/her/its status anymore.' %
+ jid, 'Roster')
self.core.get_tab_by_number(0).state = 'highlight'
if isinstance(self.core.current_tab(), tabs.RosterInfoTab):
self.core.refresh_window()
@@ -916,7 +1008,8 @@ class HandlerCore:
### Presence-related handlers ###
def on_presence(self, presence):
- if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x') is not None:
+ if presence.match('presence/muc') or presence.xml.find(
+ '{http://jabber.org/protocol/muc#user}x') is not None:
return
jid = presence['from']
contact = roster[jid.bare]
@@ -930,10 +1023,12 @@ class HandlerCore:
return
roster.modified()
contact.error = None
- self.core.events.trigger('normal_presence', presence, contact[jid.full])
+ self.core.events.trigger('normal_presence', presence,
+ contact[jid.full])
tab = self.core.get_conversation_by_jid(jid, create=False)
if tab:
- tab.update_status(Status(show=presence['show'], message=presence['status']))
+ tab.update_status(
+ Status(show=presence['show'], message=presence['status']))
if isinstance(self.core.current_tab(), tabs.RosterInfoTab):
self.core.refresh_window()
elif self.core.current_tab() == tab:
@@ -956,7 +1051,8 @@ class HandlerCore:
"""
A JID got offline
"""
- if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x') is not None:
+ if presence.match('presence/muc') or presence.xml.find(
+ '{http://jabber.org/protocol/muc#user}x') is not None:
return
jid = presence['from']
if not logger.log_roster_change(jid.bare, 'got offline'):
@@ -970,9 +1066,12 @@ class HandlerCore:
if contact.name:
name = contact.name
if jid.resource:
- self.core.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x191}offline' % name)
- self.core.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x191}offline' % name)
- self.core.information('\x193}%s \x195}is \x191}offline' % name, 'Roster')
+ self.core.add_information_message_to_conversation_tab(
+ jid.full, '\x195}%s is \x191}offline' % name)
+ self.core.add_information_message_to_conversation_tab(
+ jid.bare, '\x195}%s is \x191}offline' % name)
+ self.core.information('\x193}%s \x195}is \x191}offline' % name,
+ 'Roster')
roster.modified()
if isinstance(self.core.current_tab(), tabs.RosterInfoTab):
self.core.refresh_window()
@@ -981,7 +1080,8 @@ class HandlerCore:
"""
A JID got online
"""
- if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x') is not None:
+ if presence.match('presence/muc') or presence.xml.find(
+ '{http://jabber.org/protocol/muc#user}x') is not None:
return
jid = presence['from']
contact = roster[jid.bare]
@@ -996,17 +1096,22 @@ class HandlerCore:
'priority': presence.get_priority() or 0,
'status': presence['status'],
'show': presence['show'],
- })
+ })
self.core.events.trigger('normal_presence', presence, resource)
name = contact.name if contact.name else jid.bare
- self.core.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x194}online' % name)
+ self.core.add_information_message_to_conversation_tab(
+ jid.full, '\x195}%s is \x194}online' % name)
if time.time() - self.core.connection_time > 10:
# We do not display messages if we recently logged in
if presence['status']:
- self.core.information("\x193}%s \x195}is \x194}online\x195} (\x19o%s\x195})" % (name, presence['status']), "Roster")
+ self.core.information(
+ "\x193}%s \x195}is \x194}online\x195} (\x19o%s\x195})" %
+ (name, presence['status']), "Roster")
else:
- self.core.information("\x193}%s \x195}is \x194}online\x195}" % name, "Roster")
- self.core.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x194}online' % name)
+ self.core.information(
+ "\x193}%s \x195}is \x194}online\x195}" % name, "Roster")
+ self.core.add_information_message_to_conversation_tab(
+ jid.bare, '\x195}%s is \x194}online' % name)
if isinstance(self.core.current_tab(), tabs.RosterInfoTab):
self.core.refresh_window()
@@ -1022,14 +1127,14 @@ class HandlerCore:
self.core.events.trigger('muc_presence', presence, tab)
tab.handle_presence(presence)
-
### Connection-related handlers ###
def on_failed_connection(self, error):
"""
We cannot contact the remote server
"""
- self.core.information("Connection to remote server failed: %s" % (error,), 'Error')
+ self.core.information("Connection to remote server failed: %s" %
+ (error, ), 'Error')
@asyncio.coroutine
def on_disconnected(self, event):
@@ -1046,10 +1151,12 @@ class HandlerCore:
tab.disconnect()
msg_typ = 'Error' if not self.core.legitimate_disconnect else 'Info'
self.core.information("Disconnected from server.", msg_typ)
- if self.core.legitimate_disconnect or not config.get('auto_reconnect', True):
+ if self.core.legitimate_disconnect or not config.get(
+ 'auto_reconnect', True):
return
- if (self.core.last_stream_error and
- self.core.last_stream_error[1]['condition'] in ('conflict', 'host-unknown')):
+ if (self.core.last_stream_error
+ and self.core.last_stream_error[1]['condition'] in (
+ 'conflict', 'host-unknown')):
return
yield from asyncio.sleep(1)
self.core.information("Auto-reconnecting.", 'Info')
@@ -1076,8 +1183,8 @@ class HandlerCore:
"""
Authentication failed (no mech)
"""
- self.core.information("Authentication failed, no login method available.",
- 'Error')
+ self.core.information(
+ "Authentication failed, no login method available.", 'Error')
self.core.legitimate_disconnect = True
def on_connected(self, event):
@@ -1092,10 +1199,11 @@ class HandlerCore:
Called when we are connected and authenticated
"""
self.core.connection_time = time.time()
- if not self.core.plugins_autoloaded: # Do not reload plugins on reconnection
+ if not self.core.plugins_autoloaded: # Do not reload plugins on reconnection
self.core.autoload_plugins()
self.core.information("Authentication success.", 'Info')
- self.core.information("Your JID is %s" % self.core.xmpp.boundjid.full, 'Info')
+ self.core.information("Your JID is %s" % self.core.xmpp.boundjid.full,
+ 'Info')
if not self.core.xmpp.anon:
# request the roster
self.core.xmpp.get_roster()
@@ -1112,7 +1220,8 @@ class HandlerCore:
self.core.join_initial_rooms(self.core.bookmarks)
if config.get('enable_user_nick'):
- self.core.xmpp.plugin['xep_0172'].publish_nick(nick=self.core.own_nick, callback=dumb_callback)
+ self.core.xmpp.plugin['xep_0172'].publish_nick(
+ nick=self.core.own_nick, callback=dumb_callback)
asyncio.ensure_future(self.core.xmpp.plugin['xep_0115'].update_caps())
# Start the ping's plugin regular event
self.core.xmpp.set_keepalive_values()
@@ -1126,9 +1235,14 @@ class HandlerCore:
"""
room_from = message['from']
tab = self.core.get_tab_by_name(room_from, tabs.MucTab)
- status_codes = {s.attrib['code'] for s in message.xml.findall('{%s}x/{%s}status' % (tabs.NS_MUC_USER, tabs.NS_MUC_USER))}
+ status_codes = {
+ s.attrib['code']
+ for s in message.xml.findall('{%s}x/{%s}status' % (
+ tabs.NS_MUC_USER, tabs.NS_MUC_USER))
+ }
if '101' in status_codes:
- self.core.information('Your affiliation in the room %s changed' % room_from, 'Info')
+ self.core.information(
+ 'Your affiliation in the room %s changed' % room_from, 'Info')
elif tab and status_codes:
show_unavailable = '102' in status_codes
hide_unavailable = '103' in status_codes
@@ -1141,38 +1255,70 @@ class HandlerCore:
modif = False
if show_unavailable or hide_unavailable or non_priv or logging_off\
or non_anon or semi_anon or full_anon:
- tab.add_message('\x19%(info_col)s}Info: A configuration change not privacy-related occured.' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x19%(info_col)s}Info: A configuration change not privacy-related occured.'
+ % {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
modif = True
if show_unavailable:
- tab.add_message('\x19%(info_col)s}Info: The unavailable members are now shown.' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x19%(info_col)s}Info: The unavailable members are now shown.'
+ % {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
elif hide_unavailable:
- tab.add_message('\x19%(info_col)s}Info: The unavailable members are now hidden.' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x19%(info_col)s}Info: The unavailable members are now hidden.'
+ % {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
if non_anon:
- tab.add_message('\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)'
+ % {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
elif semi_anon:
- tab.add_message('\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)'
+ % {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
elif full_anon:
- tab.add_message('\x19%(info_col)s}Info: The room is now fully anonymous.' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x19%(info_col)s}Info: The room is now fully anonymous.' %
+ {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
if logging_on:
- tab.add_message('\x191}Warning: \x19%(info_col)s}This room is publicly logged' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x191}Warning: \x19%(info_col)s}This room is publicly logged'
+ % {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
elif logging_off:
- tab.add_message('\x19%(info_col)s}Info: This room is not logged anymore.' %
- {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
- typ=2)
+ tab.add_message(
+ '\x19%(info_col)s}Info: This room is not logged anymore.' %
+ {
+ 'info_col': dump_tuple(
+ get_theme().COLOR_INFORMATION_TEXT)
+ },
+ typ=2)
if modif:
self.core.refresh_window()
@@ -1203,22 +1349,30 @@ class HandlerCore:
after = ''
if user:
user_col = dump_tuple(user.color)
- user_string = '\x19%s}%s\x19%s}%s' % (user_col, nick_from, fmt['info_col'], after)
+ user_string = '\x19%s}%s\x19%s}%s' % (user_col, nick_from,
+ fmt['info_col'],
+ after)
else:
- user_string = '\x19%s}%s%s' % (fmt['info_col'], nick_from, after)
+ user_string = '\x19%s}%s%s' % (fmt['info_col'], nick_from,
+ after)
fmt['user'] = user_string
if nick_from:
- tab.add_message("%(user)s set the subject to: \x19%(text_col)s}%(subject)s" % fmt,
- time=None,
- typ=2)
+ tab.add_message(
+ "%(user)s set the subject to: \x19%(text_col)s}%(subject)s"
+ % fmt,
+ time=None,
+ typ=2)
else:
- tab.add_message("\x19%(info_col)s}The subject is: \x19%(text_col)s}%(subject)s" % fmt,
- time=None,
- typ=2)
+ tab.add_message(
+ "\x19%(info_col)s}The subject is: \x19%(text_col)s}%(subject)s"
+ % fmt,
+ time=None,
+ typ=2)
tab.topic = subject
tab.topic_from = nick_from
- if self.core.get_tab_by_name(room_from, tabs.MucTab) is self.core.current_tab():
+ if self.core.get_tab_by_name(room_from,
+ tabs.MucTab) is self.core.current_tab():
self.core.refresh_window()
def on_receipt(self, message):
@@ -1231,7 +1385,8 @@ class HandlerCore:
return
conversation = self.core.get_tab_by_name(jid.full, tabs.OneToOneTab)
- conversation = conversation or self.core.get_tab_by_name(jid.bare, tabs.OneToOneTab)
+ conversation = conversation or self.core.get_tab_by_name(
+ jid.bare, tabs.OneToOneTab)
if not conversation:
log.error("Received ack from non-existing chat tab: %s", jid)
return
@@ -1272,15 +1427,21 @@ class HandlerCore:
if self.core.xml_tab:
if PYGMENTS:
xhtml_text = highlight('%s' % stanza, LEXER, FORMATTER)
- poezio_colored = xhtml.xhtml_to_poezio_colors(xhtml_text, force=True).rstrip('\x19o').strip()
+ poezio_colored = xhtml.xhtml_to_poezio_colors(
+ xhtml_text, force=True).rstrip('\x19o').strip()
else:
poezio_colored = '%s' % stanza
- self.core.add_message_to_text_buffer(self.core.xml_buffer, poezio_colored,
- nickname=get_theme().CHAR_XML_OUT)
+ self.core.add_message_to_text_buffer(
+ self.core.xml_buffer,
+ poezio_colored,
+ nickname=get_theme().CHAR_XML_OUT)
try:
- if self.core.xml_tab.match_stanza(ElementBase(ET.fromstring(stanza))):
- self.core.add_message_to_text_buffer(self.core.xml_tab.filtered_buffer, poezio_colored,
- nickname=get_theme().CHAR_XML_OUT)
+ if self.core.xml_tab.match_stanza(
+ ElementBase(ET.fromstring(stanza))):
+ self.core.add_message_to_text_buffer(
+ self.core.xml_tab.filtered_buffer,
+ poezio_colored,
+ nickname=get_theme().CHAR_XML_OUT)
except:
log.debug('', exc_info=True)
@@ -1295,15 +1456,20 @@ class HandlerCore:
if self.core.xml_tab:
if PYGMENTS:
xhtml_text = highlight('%s' % stanza, LEXER, FORMATTER)
- poezio_colored = xhtml.xhtml_to_poezio_colors(xhtml_text, force=True).rstrip('\x19o').strip()
+ poezio_colored = xhtml.xhtml_to_poezio_colors(
+ xhtml_text, force=True).rstrip('\x19o').strip()
else:
poezio_colored = '%s' % stanza
- self.core.add_message_to_text_buffer(self.core.xml_buffer, poezio_colored,
- nickname=get_theme().CHAR_XML_IN)
+ self.core.add_message_to_text_buffer(
+ self.core.xml_buffer,
+ poezio_colored,
+ nickname=get_theme().CHAR_XML_IN)
try:
if self.core.xml_tab.match_stanza(stanza):
- self.core.add_message_to_text_buffer(self.core.xml_tab.filtered_buffer, poezio_colored,
- nickname=get_theme().CHAR_XML_IN)
+ self.core.add_message_to_text_buffer(
+ self.core.xml_tab.filtered_buffer,
+ poezio_colored,
+ nickname=get_theme().CHAR_XML_IN)
except:
log.debug('', exc_info=True)
if isinstance(self.core.current_tab(), tabs.XMLTab):
@@ -1311,33 +1477,34 @@ class HandlerCore:
self.core.doupdate()
def ssl_invalid_chain(self, tb):
- self.core.information('The certificate sent by the server is invalid.', 'Error')
+ self.core.information('The certificate sent by the server is invalid.',
+ 'Error')
self.core.disconnect()
def _ssl_pop_tab(self, old_cert, new_cert):
def cb(result):
if result:
self.core.information(
- 'New certificate accepted:\nnew: %s\nold: %s' % (
- old_cert, new_cert),
- 'Info')
+ 'New certificate accepted:\nnew: %s\nold: %s' %
+ (old_cert, new_cert), 'Info')
log.debug('Setting certificate to %s', new_cert)
if not config.silent_set('certificate', new_cert):
- self.core.information(
- 'Unable to write in the config file',
- 'Error')
+ self.core.information('Unable to write in the config file',
+ 'Error')
else:
- self.core.information('You refused to validate the certificate.'
- ' You are now disconnected.', 'Info')
+ self.core.information(
+ 'You refused to validate the certificate.'
+ ' You are now disconnected.', 'Info')
self.core.disconnect()
- confirm_tab = tabs.ConfirmTab(self.core,
- 'Certificate check required',
- CERT_WARNING_TEXT % (self.core.xmpp.boundjid.domain, old_cert, new_cert),
- 'You need to accept or reject the certificate',
- cb,
- critical=True)
-
+ confirm_tab = tabs.ConfirmTab(
+ self.core,
+ 'Certificate check required',
+ CERT_WARNING_TEXT % (self.core.xmpp.boundjid.domain, old_cert,
+ new_cert),
+ 'You need to accept or reject the certificate',
+ cb,
+ critical=True)
self.core.add_tab(confirm_tab, True)
self.core.doupdate()
@@ -1356,21 +1523,28 @@ class HandlerCore:
cert = config.get('certificate')
# update the cert representation when it uses the old one
if cert and ':' not in cert:
- cert = ':'.join(i + j for i, j in zip(cert[::2], cert[1::2])).upper()
+ cert = ':'.join(
+ i + j for i, j in zip(cert[::2], cert[1::2])).upper()
config.set_and_save('certificate', cert)
der = ssl.PEM_cert_to_DER_cert(pem)
- asn1 = pyasn1.codec.der.decoder.decode(der, asn1Spec=pyasn1_modules.rfc2459.Certificate())[0]
- spki = asn1.getComponentByName("tbsCertificate").getComponentByName("subjectPublicKeyInfo")
- spki_digest = sha256(pyasn1.codec.der.encoder.encode(spki)).hexdigest().upper()
- spki_found_cert = ':'.join(i + j for i, j in zip(spki_digest[::2], spki_digest[1::2]))
+ asn1 = pyasn1.codec.der.decoder.decode(
+ der, asn1Spec=pyasn1_modules.rfc2459.Certificate())[0]
+ spki = asn1.getComponentByName("tbsCertificate").getComponentByName(
+ "subjectPublicKeyInfo")
+ spki_digest = sha256(
+ pyasn1.codec.der.encoder.encode(spki)).hexdigest().upper()
+ spki_found_cert = ':'.join(
+ i + j for i, j in zip(spki_digest[::2], spki_digest[1::2]))
sha2_digest = sha512(der).hexdigest().upper()
- sha2_found_cert = ':'.join(i + j for i, j in zip(sha2_digest[::2], sha2_digest[1::2]))
+ sha2_found_cert = ':'.join(
+ i + j for i, j in zip(sha2_digest[::2], sha2_digest[1::2]))
if cert:
if sha2_found_cert == cert:
- log.debug('Current hash is cert hash, moving to SPKI hash (%s)',
- spki_found_cert)
+ log.debug(
+ 'Current hash is cert hash, moving to SPKI hash (%s)',
+ spki_found_cert)
config.set_and_save('certificate', spki_found_cert)
return
elif spki_found_cert == cert:
@@ -1380,10 +1554,12 @@ class HandlerCore:
else:
log.debug('First time. Setting certificate to %s', spki_found_cert)
if not config.silent_set('certificate', spki_found_cert):
- self.core.information('Unable to write in the config file', 'Error')
+ self.core.information('Unable to write in the config file',
+ 'Error')
def http_confirm(self, stanza):
confirm = stanza['confirm']
+
def cb(result):
if result:
reply = stanza.reply()
@@ -1396,13 +1572,15 @@ class HandlerCore:
reply.append(stanza['confirm'])
reply.send()
- c_id, c_url, c_method = confirm['id'], confirm['url'], confirm['method']
- confirm_tab = tabs.ConfirmTab(self.core,
- 'HTTP Verification',
- HTTP_VERIF_TEXT % (c_method, c_url, c_id, stanza['from'].full),
- 'An HTTP verification was requested',
- cb,
- critical=False)
+ c_id, c_url, c_method = confirm['id'], confirm['url'], confirm[
+ 'method']
+ confirm_tab = tabs.ConfirmTab(
+ self.core,
+ 'HTTP Verification',
+ HTTP_VERIF_TEXT % (c_method, c_url, c_id, stanza['from'].full),
+ 'An HTTP verification was requested',
+ cb,
+ critical=False)
self.core.add_tab(confirm_tab, False)
self.core.refresh_window()
self.core.doupdate()
@@ -1411,19 +1589,24 @@ class HandlerCore:
def next_adhoc_step(self, iq, adhoc_session):
status = iq['command']['status']
- xform = iq.xml.find('{http://jabber.org/protocol/commands}command/{jabber:x:data}x')
+ xform = iq.xml.find(
+ '{http://jabber.org/protocol/commands}command/{jabber:x:data}x')
if xform is not None:
form = self.core.xmpp.plugin['xep_0004'].build_form(xform)
else:
form = None
if status == 'error':
- return self.core.information("An error occured while executing the command")
+ return self.core.information(
+ "An error occured while executing the command")
if status == 'executing':
if not form:
- self.core.information("Adhoc command step does not contain a data-form. Aborting the execution.", "Error")
- return self.core.xmpp.plugin['xep_0050'].cancel_command(adhoc_session)
+ self.core.information(
+ "Adhoc command step does not contain a data-form. Aborting the execution.",
+ "Error")
+ return self.core.xmpp.plugin['xep_0050'].cancel_command(
+ adhoc_session)
on_validate = self._validate_adhoc_step
on_cancel = self._cancel_adhoc_command
if status == 'completed':
@@ -1435,18 +1618,20 @@ class HandlerCore:
if form:
for note in iq['command']['notes']:
form.add_field(type='fixed', label=note[1])
- self.core.open_new_form(form, on_cancel, on_validate,
- session=adhoc_session)
- else: # otherwise, just display an information
- # message
+ self.core.open_new_form(
+ form, on_cancel, on_validate, session=adhoc_session)
+ else: # otherwise, just display an information
+ # message
notes = '\n'.join([note[1] for note in iq['command']['notes']])
- self.core.information("Adhoc command %s: %s" % (status, notes), "Info")
+ self.core.information("Adhoc command %s: %s" % (status, notes),
+ "Info")
def adhoc_error(self, iq, adhoc_session):
self.core.xmpp.plugin['xep_0050'].terminate_command(adhoc_session)
error_message = self.core.get_error_message(iq)
- self.core.information("An error occured while executing the command: %s" % (error_message),
- 'Error')
+ self.core.information(
+ "An error occured while executing the command: %s" %
+ (error_message), 'Error')
def _cancel_adhoc_command(self, form, session):
self.core.xmpp.plugin['xep_0050'].cancel_command(session)
@@ -1461,6 +1646,7 @@ class HandlerCore:
self.core.xmpp.plugin['xep_0050'].terminate_command(session)
self.core.close_tab()
+
def _composing_tab_state(tab, state):
"""
Set a tab state to or from the "composing" state
@@ -1473,7 +1659,7 @@ def _composing_tab_state(tab, state):
elif isinstance(tab, tabs.ConversationTab):
values = ('true', 'direct', 'conversation')
else:
- return # should not happen
+ return # should not happen
show = config.get('show_composing_tabs')
show = show in values
@@ -1486,4 +1672,3 @@ def _composing_tab_state(tab, state):
tab.state = 'composing'
elif tab.state == 'composing' and state != 'composing':
tab.restore_state()
-