summaryrefslogtreecommitdiff
path: root/poezio/tabs/conversationtab.py
diff options
context:
space:
mode:
Diffstat (limited to 'poezio/tabs/conversationtab.py')
-rw-r--r--poezio/tabs/conversationtab.py205
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