summaryrefslogtreecommitdiff
path: root/poezio/core/handlers.py
diff options
context:
space:
mode:
Diffstat (limited to 'poezio/core/handlers.py')
-rw-r--r--poezio/core/handlers.py845
1 files changed, 267 insertions, 578 deletions
diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py
index 620d854c..e92e4aac 100644
--- a/poezio/core/handlers.py
+++ b/poezio/core/handlers.py
@@ -3,42 +3,41 @@ XMPP-related handlers for the Core class
"""
import logging
-log = logging.getLogger(__name__)
from typing import Optional
import asyncio
import curses
-import functools
import select
+import signal
import ssl
import sys
import time
-from datetime import datetime
from hashlib import sha1, sha256, sha512
-from os import path
import pyasn1.codec.der.decoder
import pyasn1.codec.der.encoder
import pyasn1_modules.rfc2459
-from slixmpp import InvalidJID, JID, Message
+from slixmpp import InvalidJID, JID, Message, Iq, Presence
from slixmpp.xmlstream.stanzabase import StanzaBase, ElementBase
from xml.etree import ElementTree as ET
-from poezio import common
-from poezio import fixes
-from poezio import pep
from poezio import tabs
from poezio import xhtml
from poezio import multiuserchat as muc
-from poezio.common import safeJID
+from poezio.common import get_error_message
from poezio.config import config, get_image_cache
from poezio.core.structs import Status
from poezio.contact import Resource
from poezio.logger import logger
from poezio.roster import roster
-from poezio.text_buffer import CorrectionError, AckError
+from poezio.text_buffer import AckError
from poezio.theming import dump_tuple, get_theme
+from poezio.ui.types import (
+ XMLLog,
+ InfoMessage,
+ PersistentInfoMessage,
+)
from poezio.core.commands import dumb_callback
@@ -52,6 +51,8 @@ try:
except ImportError:
PYGMENTS = False
+log = logging.getLogger(__name__)
+
CERT_WARNING_TEXT = """
WARNING: CERTIFICATE FOR %s CHANGED
@@ -78,30 +79,27 @@ class HandlerCore:
def __init__(self, core):
self.core = core
- def on_session_start_features(self, _):
+ async def on_session_start_features(self, _):
"""
Enable carbons & blocking on session start if wanted and possible
"""
-
- def callback(iq):
- if not iq:
- return
- features = iq['disco_info']['features']
- rostertab = self.core.tabs.by_name_and_class(
- 'Roster', tabs.RosterInfoTab)
- rostertab.check_blocking(features)
- rostertab.check_saslexternal(features)
- self.core.check_blocking(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)
+ iq = await self.core.xmpp.plugin['xep_0030'].get_info(
+ jid=self.core.xmpp.boundjid.domain
+ )
+ features = iq['disco_info']['features']
+
+ rostertab = self.core.tabs.by_name_and_class(
+ 'Roster', tabs.RosterInfoTab)
+ rostertab.check_saslexternal(features)
+ rostertab.check_blocking(features)
+ self.core.check_blocking(features)
+ if (config.getbool('enable_carbons')
+ and 'urn:xmpp:carbons:2' in features):
+ self.core.xmpp.plugin['xep_0280'].enable()
+ await self.core.check_bookmark_storage(features)
def find_identities(self, _):
- asyncio.ensure_future(
+ asyncio.create_task(
self.core.xmpp['xep_0030'].get_info_from_domain(),
)
@@ -111,7 +109,7 @@ class HandlerCore:
"""
# first, look for the x (XEP-0045 version 1.28)
- if message.xml.find('{http://jabber.org/protocol/muc#user}x') is not None:
+ if message.match('message/muc'):
log.debug('MUC-PM from %s with <x>', with_jid)
return True
@@ -152,79 +150,64 @@ class HandlerCore:
return None
- def on_carbon_received(self, message):
+ async def on_carbon_received(self, message: 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']:
- return self.on_receipt(recv)
- self.on_normal_message(recv)
-
recv = message['carbon_received']
is_muc_pm = self.is_known_muc_pm(recv, recv['from'])
if is_muc_pm:
log.debug('%s sent a MUC-PM, ignoring carbon', recv['from'])
- return
- if is_muc_pm is None:
- fixes.has_identity(
- self.core.xmpp,
+ elif is_muc_pm is None:
+ is_muc = await self.core.xmpp.plugin['xep_0030'].has_identity(
recv['from'].bare,
- identity='conference',
- on_true=functools.partial(ignore_message, recv),
- on_false=functools.partial(receive_message, recv))
- return
+ node='conference',
+ )
+ if is_muc:
+ log.debug('%s has category conference, ignoring carbon',
+ recv['from'].server)
+ else:
+ recv['to'] = self.core.xmpp.boundjid.full
+ if recv['receipt']:
+ await self.on_receipt(recv)
+ else:
+ await self.on_normal_message(recv)
else:
- receive_message(recv)
+ recv['to'] = self.core.xmpp.boundjid.full
+ await self.on_normal_message(recv)
- def on_carbon_sent(self, message):
+ async def on_carbon_sent(self, message: Message):
"""
Carbon <sent/> received
"""
-
- def groupchat_private_message(sent):
- self.on_groupchat_private_message(sent, sent=True)
-
- def send_message(sent):
- sent['from'] = self.core.xmpp.boundjid.full
- self.on_normal_message(sent)
-
sent = message['carbon_sent']
is_muc_pm = self.is_known_muc_pm(sent, sent['to'])
if is_muc_pm:
- groupchat_private_message(sent)
- return
- if is_muc_pm is None:
- fixes.has_identity(
- self.core.xmpp,
- sent['to'].server,
- identity='conference',
- on_true=functools.partial(groupchat_private_message, sent),
- on_false=functools.partial(send_message, sent))
+ await self.on_groupchat_private_message(sent, sent=True)
+ elif is_muc_pm is None:
+ is_muc = await self.core.xmpp.plugin['xep_0030'].has_identity(
+ sent['to'].bare,
+ node='conference',
+ )
+ if is_muc:
+ await self.on_groupchat_private_message(sent, sent=True)
+ else:
+ sent['from'] = self.core.xmpp.boundjid.full
+ await self.on_normal_message(sent)
else:
- send_message(sent)
+ sent['from'] = self.core.xmpp.boundjid.full
+ await self.on_normal_message(sent)
### Invites ###
- def on_groupchat_invitation(self, message):
+ async def on_groupchat_invitation(self, message: Message):
"""
Mediated invitation received
"""
jid = message['from']
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 = message['muc']['invite']
# TODO: find out why pylint thinks "inviter" is a list
#pylint: disable=no-member
inviter = invite['from']
@@ -236,20 +219,23 @@ class HandlerCore:
if password:
msg += ". The password is \"%s\"." % password
self.core.information(msg, 'Info')
- if 'invite' in config.get('beep_on').split():
+ if 'invite' in config.getstr('beep_on').split():
curses.beep()
logger.log_roster_change(inviter.full, 'invited you to %s' % jid.full)
self.core.pending_invites[jid.bare] = inviter.full
- def on_groupchat_decline(self, decline):
+ async def on_groupchat_decline(self, decline):
"Mediated invitation declined; skip for now"
pass
- def on_groupchat_direct_invitation(self, message):
+ async def on_groupchat_direct_invitation(self, message: Message):
"""
Direct invitation received
"""
- room = safeJID(message['groupchat_invite']['jid'])
+ try:
+ room = JID(message['groupchat_invite']['jid'])
+ except InvalidJID:
+ return
if room.bare in self.core.pending_invites:
return
@@ -267,7 +253,7 @@ class HandlerCore:
msg += "\nreason: %s" % reason
self.core.information(msg, 'Info')
- if 'invite' in config.get('beep_on').split():
+ if 'invite' in config.getstr('beep_on').split():
curses.beep()
self.core.pending_invites[room.bare] = inviter.full
@@ -275,32 +261,30 @@ class HandlerCore:
### "classic" messages ###
- def on_message(self, message):
+ async def on_message(self, message: Message):
"""
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.match('message/muc/invite'):
return
if message['type'] == 'groupchat':
return
# Differentiate both type of messages, and call the appropriate handler.
if self.is_known_muc_pm(message, message['from']):
- self.on_groupchat_private_message(message, sent=False)
- return
- self.on_normal_message(message)
+ await self.on_groupchat_private_message(message, sent=False)
+ else:
+ await self.on_normal_message(message)
- def on_encrypted_message(self, message):
+ async def on_encrypted_message(self, message: Message):
"""
When receiving an encrypted message
"""
if message["body"]:
return # Already being handled by on_message.
- self.on_message(message)
+ await self.on_message(message)
- def on_error_message(self, message):
+ async def on_error_message(self, message: Message):
"""
When receiving any message with type="error"
"""
@@ -310,7 +294,7 @@ class HandlerCore:
if jid_from.full == jid_from.bare:
self.core.room_error(message, jid_from.bare)
else:
- text = self.core.get_error_message(message)
+ text = get_error_message(message)
p_tab = self.core.tabs.by_name_and_class(
jid_from.full, tabs.PrivateTab)
if p_tab:
@@ -319,17 +303,17 @@ class HandlerCore:
self.core.information(text, 'Error')
return
tab = self.core.get_conversation_by_jid(message['from'], create=False)
- error_msg = self.core.get_error_message(message, deprecated=True)
+ error_msg = get_error_message(message, deprecated=True)
if not tab:
self.core.information(error_msg, 'Error')
return
error = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_CHAR_NACK),
error_msg)
if not tab.nack_message('\n' + error, message['id'], message['to']):
- tab.add_message(error, typ=0)
+ tab.add_message(InfoMessage(error))
self.core.refresh_window()
- def on_normal_message(self, message):
+ async def on_normal_message(self, message: Message):
"""
When receiving "normal" messages (not a private message from a
muc participant)
@@ -343,94 +327,36 @@ class HandlerCore:
use_xhtml = config.get_by_tabname('enable_xhtml_im',
message['from'].bare)
tmp_dir = get_image_cache()
- body = xhtml.get_body_from_message_stanza(
- message, use_xhtml=use_xhtml, extract_images_to=tmp_dir)
- if not body:
+ if not xhtml.get_body_from_message_stanza(
+ message, use_xhtml=use_xhtml, extract_images_to=tmp_dir):
if not self.core.xmpp.plugin['xep_0380'].has_eme(message):
return
self.core.xmpp.plugin['xep_0380'].replace_body_with_eme(message)
- body = message['body']
- remote_nick = ''
# normal message, we are the recipient
if message['to'].bare == self.core.xmpp.boundjid.bare:
conv_jid = message['from']
- jid = conv_jid
- color = get_theme().COLOR_REMOTE_USER
- # check for a name
- if conv_jid.bare in roster:
- 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:
- remote_nick = message['nick']['nick']
- if not remote_nick:
- remote_nick = conv_jid.user
- if not remote_nick:
- remote_nick = conv_jid.full
own = False
# we wrote the message (happens with carbons)
elif message['from'].bare == self.core.xmpp.boundjid.bare:
conv_jid = message['to']
- jid = self.core.xmpp.boundjid
- color = get_theme().COLOR_OWN_NICK
- remote_nick = self.core.own_nick
own = True
# we are not part of that message, drop it
else:
return
- conversation = self.core.get_conversation_by_jid(conv_jid, create=True)
- if isinstance(conversation,
- tabs.DynamicConversationTab) and conv_jid.resource:
- conversation.lock(conv_jid.resource)
-
- if not own and not conversation.nick:
- conversation.nick = remote_nick
- elif not own:
- remote_nick = conversation.get_nick()
-
- if not own:
- conversation.last_remote_message = datetime.now()
-
- 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, extract_images_to=tmp_dir)
- delayed, date = common.find_delayed_tag(message)
-
- def try_modify():
- if message.xml.find('{urn:xmpp:message-correct:0}replace') is None:
- return False
- replaced_id = message['replace']['id']
- 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)
- return True
- except CorrectionError:
- log.debug('Unable to correct a message', exc_info=True)
- return False
+ conversation = self.core.get_conversation_by_jid(conv_jid, create=False)
+ if conversation is None:
+ conversation = tabs.DynamicConversationTab(
+ self.core,
+ JID(conv_jid.bare),
+ initial=message,
+ )
+ self.core.tabs.append(conversation)
+ else:
+ await conversation.handle_message(message)
- if not try_modify():
- conversation.add_message(
- body,
- date,
- nickname=remote_nick,
- nick_color=color,
- history=delayed,
- identifier=message['id'],
- jid=jid,
- typ=1)
-
- if not own and 'private' in config.get('beep_on').split():
+ if not own and 'private' in config.getstr('beep_on').split():
if not config.get_by_tabname('disable_beep', conv_jid.bare):
curses.beep()
if self.core.tabs.current_tab is not conversation:
@@ -443,7 +369,7 @@ class HandlerCore:
else:
self.core.refresh_window()
- async def on_0084_avatar(self, msg):
+ async def on_0084_avatar(self, msg: Message):
jid = msg['from'].bare
contact = roster[jid]
if not contact:
@@ -493,7 +419,7 @@ class HandlerCore:
exc_info=True)
return
- async def on_vcard_avatar(self, pres):
+ async def on_vcard_avatar(self, pres: Presence):
jid = pres['from'].bare
contact = roster[jid]
if not contact:
@@ -529,7 +455,7 @@ class HandlerCore:
log.debug(
'Failed writing %s’s avatar to cache:', jid, exc_info=True)
- def on_nick_received(self, message):
+ async def on_nick_received(self, message: Message):
"""
Called when a pep notification for a user nickname
is received
@@ -543,172 +469,7 @@ class HandlerCore:
else:
contact.name = ''
- def on_gaming_event(self, message):
- """
- Called when a pep notification for user gaming
- is received
- """
- contact = roster[message['from'].bare]
- if not contact:
- return
- item = message['pubsub_event']['items']['item']
- old_gaming = contact.gaming
- if item.xml.find('{urn:xmpp:gaming:0}gaming') is not None:
- 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'],
- }
- else:
- contact.gaming = {}
-
- if 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 contact.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')
-
- def on_mood_event(self, message):
- """
- Called when a pep notification for a user mood
- is received.
- """
- contact = roster[message['from'].bare]
- if not contact:
- return
- roster.modified()
- item = message['pubsub_event']['items']['item']
- old_mood = contact.mood
- if item.xml.find('{http://jabber.org/protocol/mood}mood') is not None:
- mood = item['mood']['value']
- if mood:
- mood = pep.MOODS.get(mood, mood)
- text = item['mood']['text']
- if text:
- mood = '%s (%s)' % (mood, text)
- contact.mood = mood
- else:
- contact.mood = ''
- else:
- contact.mood = ''
-
- if 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 contact.mood:
- self.core.information(
- 'Mood from ' + contact.bare_jid + ': ' + contact.mood,
- 'Mood')
- else:
- self.core.information(
- contact.bare_jid + ' stopped having their mood.', 'Mood')
-
- def on_activity_event(self, message):
- """
- Called when a pep notification for a user activity
- is received.
- """
- contact = roster[message['from'].bare]
- if not contact:
- return
- 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:
- try:
- activity = item['activity']['value']
- except ValueError:
- return
- if activity[0]:
- general = pep.ACTIVITIES.get(activity[0])
- s = general['category']
- if activity[1]:
- s = s + '/' + general.get(activity[1], 'other')
- text = item['activity']['text']
- if text:
- s = '%s (%s)' % (s, text)
- contact.activity = s
- else:
- contact.activity = ''
- else:
- contact.activity = ''
-
- if 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 contact.activity:
- self.core.information(
- 'Activity from ' + contact.bare_jid + ': ' +
- contact.activity, 'Activity')
- else:
- self.core.information(
- contact.bare_jid + ' stopped doing their activity.',
- 'Activity')
-
- def on_tune_event(self, message):
- """
- Called when a pep notification for a user tune
- is received
- """
- contact = roster[message['from'].bare]
- if not contact:
- return
- roster.modified()
- item = message['pubsub_event']['items']['item']
- old_tune = contact.tune
- 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']
- }
- else:
- contact.tune = {}
-
- if 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 contact.tune:
- self.core.information(
- 'Tune from ' + message['from'].bare + ': ' +
- common.format_tune_string(contact.tune), 'Tune')
- else:
- self.core.information(
- contact.bare_jid + ' stopped listening to music.', 'Tune')
-
- def on_groupchat_message(self, message):
+ async def on_groupchat_message(self, message: Message) -> None:
"""
Triggered whenever a message is received from a multi-user chat room.
"""
@@ -725,88 +486,33 @@ class HandlerCore:
muc.leave_groupchat(
self.core.xmpp, room_from, self.core.own_nick, msg='')
return
-
- nick_from = message['mucnick']
- user = tab.get_user_by_name(nick_from)
- if user and user in tab.ignores:
- return
-
- self.core.events.trigger('muc_msg', message, tab)
- use_xhtml = config.get_by_tabname('enable_xhtml_im', room_from)
- tmp_dir = get_image_cache()
- body = xhtml.get_body_from_message_stanza(
- message, use_xhtml=use_xhtml, extract_images_to=tmp_dir)
- if not body:
- return
-
- old_state = tab.state
- delayed, date = common.find_delayed_tag(message)
- replaced = False
- if message.xml.find('{urn:xmpp:message-correct:0}replace') is not None:
- replaced_id = message['replace']['id']
- if replaced_id != '' 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'],
- time=delayed_date,
- 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):
- self.core.events.trigger('highlight', message, tab)
-
- if message['from'].resource == tab.own_nick:
- tab.last_sent_message = message
-
- if tab is self.core.tabs.current_tab:
- tab.text_win.refresh()
- tab.info_header.refresh(tab, tab.text_win, user=tab.own_user)
- tab.input.refresh()
- self.core.doupdate()
- elif tab.state != old_state:
- self.core.refresh_tab_win()
- current = self.core.tabs.current_tab
- if hasattr(current, 'input') and current.input:
- current.input.refresh()
- self.core.doupdate()
-
- if 'message' in config.get('beep_on').split():
+ valid_message = await tab.handle_message(message)
+ if valid_message and 'message' in config.getstr('beep_on').split():
if (not config.get_by_tabname('disable_beep', room_from)
and self.core.own_nick != message['from'].resource):
curses.beep()
- def on_muc_own_nickchange(self, muc):
+ def on_muc_own_nickchange(self, muc: tabs.MucTab):
"We changed our nick in a MUC"
for tab in self.core.get_tabs(tabs.PrivateTab):
if tab.parent_muc == muc:
tab.own_nick = muc.own_nick
- def on_groupchat_private_message(self, message, sent):
+ async def on_groupchat_private_message(self, message: Message, sent: bool):
"""
We received a Private Message (from someone in a Muc)
"""
jid = message['to'] if sent else message['from']
with_nick = jid.resource
if not with_nick:
- self.on_groupchat_message(message)
+ await self.on_groupchat_message(message)
return
room_from = jid.bare
- use_xhtml = config.get_by_tabname('enable_xhtml_im', jid.bare)
+ use_xhtml = config.get_by_tabname(
+ 'enable_xhtml_im',
+ jid.bare
+ )
tmp_dir = get_image_cache()
body = xhtml.get_body_from_message_stanza(
message, use_xhtml=use_xhtml, extract_images_to=tmp_dir)
@@ -814,59 +520,27 @@ class HandlerCore:
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 body and not ignore:
- tab = self.core.open_private_window(room_from, with_nick,
- False)
- # Tab can still be None here, when receiving carbons of a MUC-PM for
- # example
- sender_nick = (tab and tab.own_nick
- or self.core.own_nick) if sent else with_nick
if ignore and not sent:
- self.core.events.trigger('ignored_private', message, tab)
+ await self.core.events.trigger_async('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')
return
- self.core.events.trigger('private_msg', message, tab)
- body = xhtml.get_body_from_message_stanza(
- message, use_xhtml=use_xhtml, extract_images_to=tmp_dir)
- if not body or not tab:
- return
- replaced = False
- user = tab.parent_muc.get_user_by_name(with_nick)
- if message.xml.find('{urn:xmpp:message-correct:0}replace') is not None:
- replaced_id = message['replace']['id']
- if replaced_id != '' and config.get_by_tabname(
- 'group_corrections', room_from):
- try:
- tab.modify_message(
- body,
- replaced_id,
- message['id'],
- user=user,
- jid=message['from'],
- nickname=sender_nick)
- replaced = True
- except CorrectionError:
- log.debug('Unable to correct a message', exc_info=True)
- if not replaced:
- tab.add_message(
- body,
- time=None,
- nickname=sender_nick,
- nick_color=get_theme().COLOR_OWN_NICK if sent else None,
- forced_user=user,
- identifier=message['id'],
- jid=message['from'],
- typ=1)
- if sent:
- tab.last_sent_message = message
+ if tab is None: # It's the first message we receive: create the tab
+ if body and not ignore:
+ tab = tabs.PrivateTab(
+ self.core,
+ jid,
+ self.core.own_nick,
+ initial=message,
+ )
+ self.core.tabs.append(tab)
+ tab.parent_muc.privates.append(tab)
else:
- tab.last_remote_message = datetime.now()
+ await tab.handle_message(message)
- if not sent and 'private' in config.get('beep_on').split():
+ if not sent and 'private' in config.getstr('beep_on').split():
if not config.get_by_tabname('disable_beep', jid.full):
curses.beep()
if tab is self.core.tabs.current_tab:
@@ -877,37 +551,37 @@ class HandlerCore:
### Chatstates ###
- def on_chatstate_active(self, message):
- self._on_chatstate(message, "active")
+ async def on_chatstate_active(self, message: Message):
+ await self._on_chatstate(message, "active")
- def on_chatstate_inactive(self, message):
- self._on_chatstate(message, "inactive")
+ async def on_chatstate_inactive(self, message: Message):
+ await self._on_chatstate(message, "inactive")
- def on_chatstate_composing(self, message):
- self._on_chatstate(message, "composing")
+ async def on_chatstate_composing(self, message: Message):
+ await self._on_chatstate(message, "composing")
- def on_chatstate_paused(self, message):
- self._on_chatstate(message, "paused")
+ async def on_chatstate_paused(self, message: Message):
+ await self._on_chatstate(message, "paused")
- def on_chatstate_gone(self, message):
- self._on_chatstate(message, "gone")
+ async def on_chatstate_gone(self, message: Message):
+ await self._on_chatstate(message, "gone")
- def _on_chatstate(self, message, state):
+ async def _on_chatstate(self, message: Message, state: str):
if message['type'] == 'chat':
- if not self._on_chatstate_normal_conversation(message, state):
+ if not await self._on_chatstate_normal_conversation(message, state):
tab = self.core.tabs.by_name_and_class(message['from'].full,
tabs.PrivateTab)
if not tab:
return
- self._on_chatstate_private_conversation(message, state)
+ await self._on_chatstate_private_conversation(message, state)
elif message['type'] == 'groupchat':
- self.on_chatstate_groupchat_conversation(message, state)
+ await self.on_chatstate_groupchat_conversation(message, state)
- def _on_chatstate_normal_conversation(self, message, state):
+ async def _on_chatstate_normal_conversation(self, message: Message, state: str):
tab = self.core.get_conversation_by_jid(message['from'], False)
if not tab:
return False
- self.core.events.trigger('normal_chatstate', message, tab)
+ await self.core.events.trigger_async('normal_chatstate', message, tab)
tab.chatstate = state
if state == 'gone' and isinstance(tab, tabs.DynamicConversationTab):
tab.unlock()
@@ -919,7 +593,7 @@ class HandlerCore:
self.core.refresh_tab_win()
return True
- def _on_chatstate_private_conversation(self, message, state):
+ async def _on_chatstate_private_conversation(self, message: Message, state: str):
"""
Chatstate received in a private conversation from a MUC
"""
@@ -927,7 +601,7 @@ class HandlerCore:
tabs.PrivateTab)
if not tab:
return
- self.core.events.trigger('private_chatstate', message, tab)
+ await self.core.events.trigger_async('private_chatstate', message, tab)
tab.chatstate = state
if tab == self.core.tabs.current_tab:
tab.refresh_info_header()
@@ -936,7 +610,7 @@ class HandlerCore:
_composing_tab_state(tab, state)
self.core.refresh_tab_win()
- def on_chatstate_groupchat_conversation(self, message, state):
+ async def on_chatstate_groupchat_conversation(self, message: Message, state: str):
"""
Chatstate received in a MUC
"""
@@ -944,7 +618,7 @@ class HandlerCore:
room_from = message.get_mucroom()
tab = self.core.tabs.by_name_and_class(room_from, tabs.MucTab)
if tab and tab.get_user_by_name(nick):
- self.core.events.trigger('muc_chatstate', message, tab)
+ await self.core.events.trigger_async('muc_chatstate', message, tab)
tab.get_user_by_name(nick).chatstate = state
if tab == self.core.tabs.current_tab:
if not self.core.size.tab_degrade_x:
@@ -962,7 +636,7 @@ class HandlerCore:
return '%s: %s' % (error_condition,
error_text) if error_text else error_condition
- def on_version_result(self, iq):
+ def on_version_result(self, iq: Iq):
"""
Handle the result of a /version command.
"""
@@ -979,7 +653,7 @@ class HandlerCore:
'an unknown platform'))
self.core.information(version, 'Info')
- def on_bookmark_result(self, iq):
+ def on_bookmark_result(self, iq: Iq):
"""
Handle the result of a /bookmark commands.
"""
@@ -991,7 +665,7 @@ class HandlerCore:
### subscription-related handlers ###
- def on_roster_update(self, iq):
+ async def on_roster_update(self, iq: Iq):
"""
The roster was received.
"""
@@ -1010,7 +684,7 @@ class HandlerCore:
if isinstance(self.core.tabs.current_tab, tabs.RosterInfoTab):
self.core.refresh_window()
- def on_subscription_request(self, presence):
+ async def on_subscription_request(self, presence: Presence):
"""subscribe received"""
jid = presence['from'].bare
contact = roster[jid]
@@ -1033,7 +707,7 @@ class HandlerCore:
if isinstance(self.core.tabs.current_tab, tabs.RosterInfoTab):
self.core.refresh_window()
- def on_subscription_authorized(self, presence):
+ async def on_subscription_authorized(self, presence: Presence):
"""subscribed received"""
jid = presence['from'].bare
contact = roster[jid]
@@ -1048,7 +722,7 @@ class HandlerCore:
if isinstance(self.core.tabs.current_tab, tabs.RosterInfoTab):
self.core.refresh_window()
- def on_subscription_remove(self, presence):
+ async def on_subscription_remove(self, presence: Presence):
"""unsubscribe received"""
jid = presence['from'].bare
contact = roster[jid]
@@ -1061,7 +735,7 @@ class HandlerCore:
if isinstance(self.core.tabs.current_tab, tabs.RosterInfoTab):
self.core.refresh_window()
- def on_subscription_removed(self, presence):
+ async def on_subscription_removed(self, presence: Presence):
"""unsubscribed received"""
jid = presence['from'].bare
contact = roster[jid]
@@ -1082,9 +756,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:
+ async def on_presence(self, presence: Presence):
+ if presence.match('presence/muc'):
return
jid = presence['from']
contact = roster[jid.bare]
@@ -1098,8 +771,8 @@ class HandlerCore:
return
roster.modified()
contact.error = None
- self.core.events.trigger('normal_presence', presence,
- contact[jid.full])
+ await self.core.events.trigger_async('normal_presence', presence,
+ contact[jid.full])
tab = self.core.get_conversation_by_jid(jid, create=False)
if tab:
tab.update_status(
@@ -1110,21 +783,20 @@ class HandlerCore:
tab.refresh()
self.core.doupdate()
- def on_presence_error(self, presence):
+ async def on_presence_error(self, presence: Presence):
jid = presence['from']
contact = roster[jid.bare]
if not contact:
return
roster.modified()
- contact.error = presence['error']['type'] + ': ' + presence['error']['condition']
+ contact.error = presence['error']['text'] or presence['error']['type'] + ': ' + presence['error']['condition']
# TODO: reset chat states status on presence error
- def on_got_offline(self, presence):
+ async def on_got_offline(self, presence: Presence):
"""
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'):
return
jid = presence['from']
status = presence['status']
@@ -1152,12 +824,11 @@ class HandlerCore:
if isinstance(self.core.tabs.current_tab, tabs.RosterInfoTab):
self.core.refresh_window()
- def on_got_online(self, presence):
+ async def on_got_online(self, presence: Presence):
"""
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'):
return
jid = presence['from']
contact = roster[jid.bare]
@@ -1174,7 +845,7 @@ class HandlerCore:
'status': presence['status'],
'show': presence['show'],
})
- self.core.events.trigger('normal_presence', presence, resource)
+ await self.core.events.trigger_async('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)
@@ -1192,7 +863,7 @@ class HandlerCore:
if isinstance(self.core.tabs.current_tab, tabs.RosterInfoTab):
self.core.refresh_window()
- def on_groupchat_presence(self, presence):
+ async def on_groupchat_presence(self, presence: Presence):
"""
Triggered whenever a presence stanza is received from a user in a multi-user chat room.
Display the presence on the room window and update the
@@ -1201,19 +872,19 @@ class HandlerCore:
from_room = presence['from'].bare
tab = self.core.tabs.by_name_and_class(from_room, tabs.MucTab)
if tab:
- self.core.events.trigger('muc_presence', presence, tab)
+ await self.core.events.trigger_async('muc_presence', presence, tab)
tab.handle_presence(presence)
### Connection-related handlers ###
- def on_failed_connection(self, error):
+ async def on_failed_connection(self, error: str):
"""
We cannot contact the remote server
"""
self.core.information(
"Connection to remote server failed: %s" % (error, ), 'Error')
- def on_session_end(self, event):
+ async def on_session_end(self, event):
"""
Called when a session is terminated (e.g. due to a manual disconnect or a 0198 resume fail)
"""
@@ -1222,7 +893,7 @@ class HandlerCore:
for tab in self.core.get_tabs(tabs.MucTab):
tab.disconnect()
- def on_session_resumed(self, event):
+ async def on_session_resumed(self, event):
"""
Called when a session is successfully resumed by 0198
"""
@@ -1233,22 +904,23 @@ class HandlerCore:
"""
When we are disconnected from remote server
"""
- if 'disconnect' in config.get('beep_on').split():
+ if 'disconnect' in config.getstr('beep_on').split():
curses.beep()
# Stop the ping plugin. It would try to send stanza on regular basis
self.core.xmpp.plugin['xep_0199'].disable_keepalive()
msg_typ = 'Error' if not self.core.legitimate_disconnect else 'Info'
self.core.information("Disconnected from server%s." % (event and ": %s" % event or ""), msg_typ)
- if self.core.legitimate_disconnect or not config.get(
- 'auto_reconnect', True):
+ if self.core.legitimate_disconnect or not config.getbool(
+ 'auto_reconnect'):
return
if (self.core.last_stream_error
and self.core.last_stream_error[1]['condition'] in (
'conflict', 'host-unknown')):
return
await asyncio.sleep(1)
- self.core.information("Auto-reconnecting.", 'Info')
- self.core.xmpp.start()
+ if not self.core.xmpp.is_connecting() and not self.core.xmpp.is_connected():
+ self.core.information("Auto-reconnecting.", 'Info')
+ self.core.xmpp.start()
async def on_reconnect_delay(self, event):
"""
@@ -1256,7 +928,7 @@ class HandlerCore:
"""
self.core.information("Reconnecting in %d seconds..." % (event), 'Info')
- def on_stream_error(self, event):
+ async def on_stream_error(self, event):
"""
When we receive a stream error
"""
@@ -1265,7 +937,7 @@ class HandlerCore:
if event:
self.core.last_stream_error = (time.time(), event)
- def on_failed_all_auth(self, event):
+ async def on_failed_all_auth(self, event):
"""
Authentication failed
"""
@@ -1273,7 +945,7 @@ class HandlerCore:
'Error')
self.core.legitimate_disconnect = True
- def on_no_auth(self, event):
+ async def on_no_auth(self, event):
"""
Authentication failed (no mech)
"""
@@ -1281,14 +953,14 @@ class HandlerCore:
"Authentication failed, no login method available.", 'Error')
self.core.legitimate_disconnect = True
- def on_connected(self, event):
+ async def on_connected(self, event):
"""
Remote host responded, but we are not yet authenticated
"""
self.core.information("Connected to server.", 'Info')
self.core.legitimate_disconnect = False
- def on_session_start(self, event):
+ async def on_session_start(self, event):
"""
Called when we are connected and authenticated
"""
@@ -1303,26 +975,26 @@ class HandlerCore:
self.core.xmpp.get_roster()
roster.update_contact_groups(self.core.xmpp.boundjid.bare)
# send initial presence
- if config.get('send_initial_presence'):
+ if config.getbool('send_initial_presence'):
pres = self.core.xmpp.make_presence()
pres['show'] = self.core.status.show
pres['status'] = self.core.status.message
- self.core.events.trigger('send_normal_presence', pres)
+ await self.core.events.trigger_async('send_normal_presence', pres)
pres.send()
self.core.bookmarks.get_local()
# join all the available bookmarks. As of yet, this is just the local ones
- self.core.join_initial_rooms(self.core.bookmarks)
+ self.core.join_initial_rooms(self.core.bookmarks.local())
- if config.get('enable_user_nick'):
+ if config.getbool('enable_user_nick'):
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())
+ asyncio.create_task(self.core.xmpp.plugin['xep_0115'].update_caps())
# Start the ping's plugin regular event
self.core.xmpp.set_keepalive_values()
### Other handlers ###
- def on_status_codes(self, message):
+ async def on_status_codes(self, message: Message):
"""
Handle groupchat messages with status codes.
Those are received when a room configuration change occurs.
@@ -1351,41 +1023,57 @@ class HandlerCore:
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 occurred.' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ 'Info: A configuration change not privacy-related occurred.'
+ ),
+ )
modif = True
if show_unavailable:
tab.add_message(
- '\x19%(info_col)s}Info: The unavailable members are now shown.' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ 'Info: The unavailable members are now shown.'
+ ),
+ )
elif hide_unavailable:
tab.add_message(
- '\x19%(info_col)s}Info: The unavailable members are now hidden.' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ 'Info: The unavailable members are now hidden.',
+ ),
+ )
if non_anon:
tab.add_message(
- '\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ '\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % info_col
+ ),
+ )
elif semi_anon:
tab.add_message(
- '\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ 'Info: The room is now semi-anonymous. (moderators-only JID)',
+ ),
+ )
elif full_anon:
tab.add_message(
- '\x19%(info_col)s}Info: The room is now fully anonymous.' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ 'Info: The room is now fully anonymous.',
+ ),
+ )
if logging_on:
tab.add_message(
- '\x191}Warning: \x19%(info_col)s}This room is publicly logged' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ '\x191}Warning: \x19%(info_col)s}This room is publicly logged' % info_col
+ ),
+ )
elif logging_off:
tab.add_message(
- '\x19%(info_col)s}Info: This room is not logged anymore.' % info_col,
- typ=2)
+ PersistentInfoMessage(
+ 'Info: This room is not logged anymore.',
+ ),
+ )
if modif:
self.core.refresh_window()
- def on_groupchat_subject(self, message):
+ async def on_groupchat_subject(self, message: Message):
"""
Triggered when the topic is changed.
"""
@@ -1424,23 +1112,25 @@ class HandlerCore:
if nick_from:
tab.add_message(
- "%(user)s set the subject to: \x19%(text_col)s}%(subject)s"
- % fmt,
- str_time=time,
- typ=2)
+ PersistentInfoMessage(
+ "%(user)s set the subject to: \x19%(text_col)s}%(subject)s" % fmt,
+ time=time,
+ ),
+ )
else:
tab.add_message(
- "\x19%(info_col)s}The subject is: \x19%(text_col)s}%(subject)s"
- % fmt,
- str_time=time,
- typ=2)
+ PersistentInfoMessage(
+ "The subject is: \x19%(text_col)s}%(subject)s" % fmt,
+ time=time,
+ ),
+ )
tab.topic = subject
tab.topic_from = nick_from
if self.core.tabs.by_name_and_class(
room_from, tabs.MucTab) is self.core.tabs.current_tab:
self.core.refresh_window()
- def on_receipt(self, message):
+ async def on_receipt(self, message):
"""
When a delivery receipt is received (XEP-0184)
"""
@@ -1462,57 +1152,54 @@ class HandlerCore:
except AckError:
log.debug('Error while receiving an ack', exc_info=True)
- def on_data_form(self, message):
+ async def on_data_form(self, message: Message):
"""
When a data form is received
"""
self.core.information(str(message))
- def on_attention(self, message):
+ async def on_attention(self, message: Message):
"""
Attention probe received.
"""
jid_from = message['from']
self.core.information('%s requests your attention!' % jid_from, 'Info')
- for tab in self.core.tabs:
- if tab.jid == jid_from:
- tab.state = 'attention'
- self.core.refresh_tab_win()
- return
- for tab in self.core.tabs:
- if tab.jid.bare == jid_from.bare:
- tab.state = 'attention'
- self.core.refresh_tab_win()
- return
- self.core.information('%s tab not found.' % jid_from, 'Error')
+ tab = (
+ self.core.tabs.by_name_and_class(
+ jid_from.full, tabs.ChatTab
+ ) or self.core.tabs.by_name_and_class(
+ jid_from.bare, tabs.ChatTab
+ )
+ )
+ if tab and tab is not self.core.tabs.current_tab:
+ tab.state = "attention"
+ self.core.refresh_tab_win()
- def outgoing_stanza(self, stanza):
+ def outgoing_stanza(self, stanza: StanzaBase):
"""
We are sending a new stanza, write it in the xml buffer if needed.
"""
if self.core.xml_tab:
+ stanza_str = str(stanza)
if PYGMENTS:
- xhtml_text = highlight(str(stanza), LEXER, FORMATTER)
+ xhtml_text = highlight(stanza_str, LEXER, FORMATTER)
poezio_colored = xhtml.xhtml_to_poezio_colors(
xhtml_text, force=True).rstrip('\x19o').strip()
else:
- poezio_colored = str(stanza)
- char = get_theme().CHAR_XML_OUT
- self.core.add_message_to_text_buffer(
- self.core.xml_buffer,
- poezio_colored,
- nickname=char)
+ poezio_colored = stanza_str
+ self.core.xml_buffer.add_message(
+ XMLLog(txt=poezio_colored, incoming=False),
+ )
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=char)
+ ElementBase(ET.fromstring(stanza_str))):
+ self.core.xml_tab.filtered_buffer.add_message(
+ XMLLog(txt=poezio_colored, incoming=False),
+ )
except:
# Most of the time what gets logged is whitespace pings. Skip.
# And also skip tab updates.
- if stanza.strip() != '':
+ if stanza_str.strip() == '':
return None
log.debug('', exc_info=True)
@@ -1520,7 +1207,7 @@ class HandlerCore:
self.core.tabs.current_tab.refresh()
self.core.doupdate()
- def incoming_stanza(self, stanza):
+ def incoming_stanza(self, stanza: StanzaBase):
"""
We are receiving a new stanza, write it in the xml buffer if needed.
"""
@@ -1531,17 +1218,14 @@ class HandlerCore:
xhtml_text, force=True).rstrip('\x19o').strip()
else:
poezio_colored = str(stanza)
- char = get_theme().CHAR_XML_IN
- self.core.add_message_to_text_buffer(
- self.core.xml_buffer,
- poezio_colored,
- nickname=char)
+ self.core.xml_buffer.add_message(
+ XMLLog(txt=poezio_colored, incoming=True),
+ )
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=char)
+ self.core.xml_tab.filtered_buffer.add_message(
+ XMLLog(txt=poezio_colored, incoming=True),
+ )
except:
log.debug('', exc_info=True)
if isinstance(self.core.tabs.current_tab, tabs.XMLTab):
@@ -1580,19 +1264,24 @@ class HandlerCore:
self.core.add_tab(confirm_tab, True)
self.core.doupdate()
+ # handle resize
+ prev_value = signal.signal(signal.SIGWINCH, self.core.sigwinch_handler)
while not confirm_tab.done:
- sel = select.select([sys.stdin], [], [], 5)[0]
-
- if sel:
- self.core.on_input_readable()
+ try:
+ sel = select.select([sys.stdin], [], [], 0.5)[0]
+ if sel:
+ self.core.on_input_readable()
+ except:
+ continue
+ signal.signal(signal.SIGWINCH, prev_value)
def validate_ssl(self, pem):
"""
Check the server certificate using the slixmpp ssl_cert event
"""
- if config.get('ignore_certificate'):
+ if config.getbool('ignore_certificate'):
return
- cert = config.get('certificate')
+ cert = config.getstr('certificate')
# update the cert representation when it uses the old one
if cert and ':' not in cert:
cert = ':'.join(
@@ -1701,7 +1390,7 @@ class HandlerCore:
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)
+ error_message = get_error_message(iq)
self.core.information(
"An error occurred while executing the command: %s" %
(error_message), 'Error')
@@ -1734,7 +1423,7 @@ def _composing_tab_state(tab, state):
else:
return # should not happen
- show = config.get('show_composing_tabs')
+ show = config.getstr('show_composing_tabs').lower()
show = show in values
if tab.state != 'composing' and state == 'composing':