diff options
Diffstat (limited to 'poezio/tabs/conversationtab.py')
-rw-r--r-- | poezio/tabs/conversationtab.py | 205 |
1 files changed, 148 insertions, 57 deletions
diff --git a/poezio/tabs/conversationtab.py b/poezio/tabs/conversationtab.py index 39411872..de1f988a 100644 --- a/poezio/tabs/conversationtab.py +++ b/poezio/tabs/conversationtab.py @@ -11,23 +11,27 @@ There are two different instances of a ConversationTab: the time. """ +import asyncio import curses import logging +from datetime import datetime from typing import Dict, Callable +from slixmpp import JID, InvalidJID, Message as SMessage + from poezio.tabs.basetabs import OneToOneTab, Tab from poezio import common from poezio import windows from poezio import xhtml -from poezio.common import safeJID -from poezio.config import config +from poezio.config import config, get_image_cache from poezio.core.structs import Command from poezio.decorators import refresh_wrapper from poezio.roster import roster -from poezio.text_buffer import CorrectionError from poezio.theming import get_theme, dump_tuple from poezio.decorators import command_args_parser +from poezio.ui.types import InfoMessage, Message +from poezio.text_buffer import CorrectionError log = logging.getLogger(__name__) @@ -37,18 +41,16 @@ class ConversationTab(OneToOneTab): The tab containing a normal conversation (not from a MUC) Must not be instantiated, use Static or Dynamic version only. """ - plugin_commands = {} # type: Dict[str, Command] - plugin_keys = {} # type: Dict[str, Callable] - additional_information = {} # type: Dict[str, Callable[[str], str]] + plugin_commands: Dict[str, Command] = {} + plugin_keys: Dict[str, Callable] = {} + additional_information: Dict[str, Callable[[str], str]] = {} message_type = 'chat' - def __init__(self, core, jid): - OneToOneTab.__init__(self, core, jid) + def __init__(self, core, jid, initial=None): + OneToOneTab.__init__(self, core, jid, initial=initial) self.nick = None self.nick_sent = False self.state = 'normal' - self.text_win = windows.TextWin() - self._text_buffer.add_window(self.text_win) self.upper_bar = windows.ConversationStatusMessageWin() self.input = windows.MessageInput() # keys @@ -81,8 +83,8 @@ class ConversationTab(OneToOneTab): self.update_keys() @property - def general_jid(self): - return self.jid.bare + def general_jid(self) -> JID: + return JID(self.jid.bare) def get_info_header(self): raise NotImplementedError @@ -103,10 +105,85 @@ class ConversationTab(OneToOneTab): def completion(self): self.complete_commands(self.input) + async def handle_message(self, message: SMessage, display: bool = True): + """Handle a received message. + + The message can come from us (carbon copy). + """ + + # Prevent messages coming from our own devices (1:1) to be reflected + if message['to'].bare == self.core.xmpp.boundjid.bare and \ + message['from'].bare == self.core.xmpp.boundjid.bare: + _, index = self._text_buffer._find_message(message['id']) + if index != -1: + return + + use_xhtml = config.get_by_tabname( + 'enable_xhtml_im', + message['from'].bare + ) + tmp_dir = get_image_cache() + + # 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 + self.last_remote_message = datetime.now() + remote_nick = self.get_nick() + # 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 + # we are not part of that message, drop it + else: + return + + await self.core.events.trigger_async('conversation_msg', message, self) + + 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) + + replaced = False + if message.get_plugin('replace', check=True): + replaced_id = message['replace']['id'] + if replaced_id and config.get_by_tabname('group_corrections', + conv_jid.bare): + try: + replaced = self.modify_message( + body, + replaced_id, + message['id'], + time=date, + jid=jid, + nickname=remote_nick) + except CorrectionError: + log.debug('Unable to correct the message: %s', message) + if not replaced: + msg = Message( + txt=body, + time=date, + nickname=remote_nick, + nick_color=color, + history=delayed, + identifier=message['id'], + jid=jid, + ) + if display: + self.add_message(msg) + else: + self.log_message(msg) + @refresh_wrapper.always @command_args_parser.raw - def command_say(self, line, attention=False, correct=False): - msg = self.core.xmpp.make_message( + async def command_say(self, line: str, attention: bool = False, correct: bool = False): + await self._initial_log.wait() + msg: SMessage = self.core.xmpp.make_message( mto=self.get_dest_jid(), mfrom=self.core.xmpp.boundjid ) @@ -122,9 +199,8 @@ class ConversationTab(OneToOneTab): self.core.events.trigger('conversation_say', msg, self) if not msg['body']: return - replaced = False if correct or msg['replace']['id']: - msg['replace']['id'] = self.last_sent_message['id'] + msg['replace']['id'] = self.last_sent_message['id'] # type: ignore else: del msg['replace'] if msg['body'].find('\x19') != -1: @@ -132,17 +208,20 @@ class ConversationTab(OneToOneTab): msg['html']['body'] = xhtml.poezio_colors_to_html(msg['body']) msg['body'] = xhtml.clean_text(msg['body']) if config.get_by_tabname('send_chat_states', self.general_jid): - needed = 'inactive' if self.inactive else 'active' - msg['chat_state'] = needed + if self.inactive: + self.send_chat_state('inactive', always_send=True) + else: + msg['chat_state'] = 'active' if attention: msg['attention'] = True self.core.events.trigger('conversation_say_after', msg, self) if not msg['body']: return - self.last_sent_message = msg - self.core.handler.on_normal_message(msg) - msg._add_receipt = True + self.set_last_sent_message(msg, correct=correct) + msg._add_receipt = True # type: ignore msg.send() + await self.core.handler.on_normal_message(msg) + # Our receipts slixmpp hack self.cancel_paused_delay() @command_args_parser.quoted(0, 1) @@ -167,7 +246,13 @@ class ConversationTab(OneToOneTab): status = iq['last_activity']['status'] from_ = iq['from'] msg = '\x19%s}The last activity of %s was %s ago%s' - if not safeJID(from_).user: + user = '' + try: + user = JID(from_).user + except InvalidJID: + pass + + if not user: msg = '\x19%s}The uptime of %s is %s.' % ( dump_tuple(get_theme().COLOR_INFORMATION_TEXT), from_, common.parse_secs_to_str(seconds)) @@ -179,7 +264,7 @@ class ConversationTab(OneToOneTab): (' and their last status was %s' % status) if status else '', ) - self.add_message(msg) + self.add_message(InfoMessage(msg)) self.core.refresh_window() self.core.xmpp.plugin['xep_0012'].get_last_activity( @@ -189,7 +274,10 @@ class ConversationTab(OneToOneTab): @command_args_parser.ignored def command_info(self): contact = roster[self.get_dest_jid()] - jid = safeJID(self.get_dest_jid()) + try: + jid = JID(self.get_dest_jid()) + except InvalidJID: + jid = JID('') if contact: if jid.resource: resource = contact[jid.full] @@ -198,35 +286,29 @@ class ConversationTab(OneToOneTab): else: resource = None if resource: - status = ( - 'Status: %s' % resource.status) if resource.status else '' - self._text_buffer.add_message( - "\x19%(info_col)s}Show: %(show)s, %(status)s\x19o" % { - 'show': resource.presence or 'available', - 'status': status, - 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) - }) - return True - else: - self._text_buffer.add_message( - "\x19%(info_col)s}No information available\x19o" % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}) + status = (f', Status: {resource.status}') if resource.status else '' + show = f"Show: {resource.presence or 'available'}" + self.add_message(InfoMessage(f'{show}{status}')) return True + self.add_message( + InfoMessage("No information available"), + ) + return True @command_args_parser.quoted(0, 1) - def command_version(self, args): + async def command_version(self, args): """ /version [jid] """ if args: - return self.core.command.version(args[0]) + return await self.core.command.version(args[0]) jid = self.jid if not jid.resource: if jid in roster: resource = roster[jid].get_highest_priority_resource() jid = resource.jid if resource else jid - self.core.xmpp.plugin['xep_0092'].get_version( - jid, callback=self.core.handler.on_version_result) + iq = await self.core.xmpp.plugin['xep_0092'].get_version(jid) + self.core.handler.on_version_result(iq) def resize(self): self.need_resize = False @@ -243,8 +325,10 @@ class ConversationTab(OneToOneTab): self.text_win.resize( self.height - 2 - bar_height - info_win_height - tab_win_height, - self.width, bar_height, 0) - self.text_win.rebuild_everything(self._text_buffer) + self.width, bar_height, 0, self._text_buffer, + force=self.ui_config_changed + ) + self.ui_config_changed = False if display_bar: self.upper_bar.resize(1, self.width, 0, 0) self.get_info_header().resize( @@ -285,7 +369,7 @@ class ConversationTab(OneToOneTab): else: if self.nick: return self.nick - return self.jid.user + return self.jid.user or self.jid.domain def on_input(self, key, raw): if not raw and key in self.key_func: @@ -300,7 +384,10 @@ class ConversationTab(OneToOneTab): def on_lose_focus(self): contact = roster[self.get_dest_jid()] - jid = safeJID(self.get_dest_jid()) + try: + jid = JID(self.get_dest_jid()) + except InvalidJID: + jid = JID('') if contact: if jid.resource: resource = contact[jid.full] @@ -321,7 +408,10 @@ class ConversationTab(OneToOneTab): def on_gain_focus(self): contact = roster[self.get_dest_jid()] - jid = safeJID(self.get_dest_jid()) + try: + jid = JID(self.get_dest_jid()) + except InvalidJID: + jid = JID('') if contact: if jid.resource: resource = contact[jid.full] @@ -348,9 +438,6 @@ class ConversationTab(OneToOneTab): 1, self.width, self.height - 2 - self.core.information_win_size - Tab.tab_win_height(), 0) - def get_text_window(self): - return self.text_win - def on_close(self): Tab.on_close(self) if config.get_by_tabname('send_chat_states', self.general_jid): @@ -374,12 +461,12 @@ class DynamicConversationTab(ConversationTab): bad idea so it has been removed. Only one DynamicConversationTab can be opened for a given jid. """ - plugin_commands = {} # type: Dict[str, Command] - plugin_keys = {} # type: Dict[str, Callable] + plugin_commands: Dict[str, Command] = {} + plugin_keys: Dict[str, Callable] = {} - def __init__(self, core, jid, resource=None): + def __init__(self, core, jid, initial=None): self.locked_resource = None - ConversationTab.__init__(self, core, jid) + ConversationTab.__init__(self, core, jid, initial=initial) self.jid.resource = None self.info_header = windows.DynamicConversationInfoWin() self.register_command( @@ -444,16 +531,20 @@ class StaticConversationTab(ConversationTab): A conversation tab associated with one Full JID. It cannot be locked to an different resource or unlocked. """ - plugin_commands = {} # type: Dict[str, Command] - plugin_keys = {} # type: Dict[str, Callable] + plugin_commands: Dict[str, Command] = {} + plugin_keys: Dict[str, Callable] = {} - def __init__(self, core, jid): - ConversationTab.__init__(self, core, jid) + def __init__(self, core, jid, initial=None): + ConversationTab.__init__(self, core, jid, initial=initial) assert jid.resource self.info_header = windows.ConversationInfoWin() self.resize() self.update_commands() self.update_keys() + async def init_logs(self, initial=None) -> None: + # Disable local logs becauseā¦ + pass + def get_info_header(self): return self.info_header |