summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormathieui <mathieui@mathieui.net>2021-06-22 23:16:42 +0200
committermathieui <mathieui@mathieui.net>2021-06-22 23:26:49 +0200
commit2b76f72e4d58eb540aa19173a050142f5199ff81 (patch)
treeccca7dbf36c20e980e3a69c0028af264702c75f6
parent3035f4ed4d5ef5d581c7f224833392526bfd1bc5 (diff)
downloadpoezio-2b76f72e4d58eb540aa19173a050142f5199ff81.tar.gz
poezio-2b76f72e4d58eb540aa19173a050142f5199ff81.tar.bz2
poezio-2b76f72e4d58eb540aa19173a050142f5199ff81.tar.xz
poezio-2b76f72e4d58eb540aa19173a050142f5199ff81.zip
Fix the message doubling situation
Add an "initial" parameter for onetoonetabs, and delay the reception of the message until the sync is done (and possibly ignore the message if it is fetched by the sync). Fixes #3542
-rw-r--r--poezio/core/handlers.py142
-rw-r--r--poezio/log_loader.py2
-rw-r--r--poezio/tabs/basetabs.py17
-rw-r--r--poezio/tabs/conversationtab.py88
-rw-r--r--poezio/tabs/privatetab.py66
5 files changed, 183 insertions, 132 deletions
diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py
index 6262a1d5..d4625b4b 100644
--- a/poezio/core/handlers.py
+++ b/poezio/core/handlers.py
@@ -346,95 +346,34 @@ 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.getbool('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:
- if not conversation.nick:
- conversation.nick = remote_nick
- else:
- remote_nick = conversation.get_nick()
-
- 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'],
- time=date,
- 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(
- PMessage(
- txt=body,
- time=date,
- nickname=remote_nick,
- nick_color=color,
- history=delayed,
- identifier=message['id'],
- jid=jid,
- )
+ 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:
+ conversation.handle_message(message)
if not own and 'private' in config.getstr('beep_on').split():
if not config.get_by_tabname('disable_beep', conv_jid.bare):
@@ -679,7 +618,10 @@ class HandlerCore:
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)
@@ -687,14 +629,6 @@ 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)
msg = config.get_by_tabname('private_auto_response', room_from)
@@ -702,44 +636,18 @@ class HandlerCore:
self.core.xmpp.send_message(
mto=jid.full, mbody=msg, mtype='chat')
return
- if not sent:
- 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(
- PMessage(
- txt=body,
- nickname=sender_nick,
- nick_color=get_theme().COLOR_OWN_NICK if sent else None,
- user=user,
- identifier=message['id'],
- jid=message['from'],
+ 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,
)
- )
- if sent:
- tab.set_last_sent_message(message, correct=replaced)
+ self.core.tabs.append(tab)
+ tab.parent_muc.privates.append(tab)
else:
- tab.last_remote_message = datetime.now()
+ tab.handle_message(message)
if not sent and 'private' in config.getstr('beep_on').split():
if not config.get_by_tabname('disable_beep', jid.full):
diff --git a/poezio/log_loader.py b/poezio/log_loader.py
index 8fd0ed82..aa645c30 100644
--- a/poezio/log_loader.py
+++ b/poezio/log_loader.py
@@ -309,6 +309,7 @@ class MAMFiller:
self.future = asyncio.ensure_future(self.fetch_routine())
self.done = asyncio.Event()
self.limit = limit
+ self.result = 0
def cancel(self) -> None:
"""Cancel the routine and signal the end."""
@@ -334,6 +335,7 @@ class MAMFiller:
'Fetched %s messages to fill local logs for %s',
len(messages), self.tab.jid,
)
+ self.result = len(messages)
except NoMAMSupportException:
log.debug('The entity %s does not support MAM', self.tab.jid)
return
diff --git a/poezio/tabs/basetabs.py b/poezio/tabs/basetabs.py
index 43ab237f..f52122a5 100644
--- a/poezio/tabs/basetabs.py
+++ b/poezio/tabs/basetabs.py
@@ -999,7 +999,7 @@ class ChatTab(Tab):
class OneToOneTab(ChatTab):
- def __init__(self, core, jid):
+ def __init__(self, core, jid, initial=None):
ChatTab.__init__(self, core, jid)
self.__status = Status("", "")
@@ -1018,18 +1018,29 @@ class OneToOneTab(ChatTab):
shortdesc='Request the attention.',
desc='Attention: Request the attention of the contact. Can also '
'send a message along with the attention.')
- self.init_logs()
+ self.init_logs(initial=initial)
- def init_logs(self) -> None:
+ def init_logs(self, initial=None) -> None:
use_log = config.get_by_tabname('use_log', self.jid)
mam_sync = config.get_by_tabname('mam_sync', self.jid)
if use_log and mam_sync:
limit = config.get_by_tabname('mam_sync_limit', self.jid)
self.mam_filler = MAMFiller(logger, self, limit)
+
+ async def fallback_no_mam():
+ await self.mam_filler.done.wait()
+ if self.mam_filler.result == 0:
+ self.handle_message(initial)
+ asyncio.ensure_future(fallback_no_mam())
+ elif use_log and initial:
+ self.handle_message(initial, display=False)
asyncio.ensure_future(
LogLoader(logger, self, use_log).tab_open()
)
+ def handle_message(self, msg: SMessage, display: bool = True):
+ pass
+
def remote_user_color(self):
return dump_tuple(get_theme().COLOR_REMOTE_USER)
diff --git a/poezio/tabs/conversationtab.py b/poezio/tabs/conversationtab.py
index 78faf49a..eb6e97b5 100644
--- a/poezio/tabs/conversationtab.py
+++ b/poezio/tabs/conversationtab.py
@@ -13,22 +13,25 @@ There are two different instances of a ConversationTab:
"""
import curses
import logging
+from datetime import datetime
from typing import Dict, Callable
-from slixmpp import JID, InvalidJID
+from slixmpp import JID, InvalidJID, Message as SMessage
from poezio.tabs.basetabs import OneToOneTab, Tab
from poezio import common
+from poezio import tabs
from poezio import windows
from poezio import xhtml
-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.theming import get_theme, dump_tuple
from poezio.decorators import command_args_parser
-from poezio.ui.types import InfoMessage
+from poezio.ui.types import InfoMessage, Message
+from poezio.text_buffer import CorrectionError
log = logging.getLogger(__name__)
@@ -43,8 +46,8 @@ class ConversationTab(OneToOneTab):
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'
@@ -102,6 +105,71 @@ class ConversationTab(OneToOneTab):
def completion(self):
self.complete_commands(self.input)
+ def handle_message(self, message: SMessage, display: bool = True):
+ """Handle a received message.
+
+ The message can come from us (carbon copy).
+ """
+ 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
+
+ self.core.events.trigger('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: str, attention: bool = False, correct: bool = False):
@@ -396,9 +464,9 @@ class DynamicConversationTab(ConversationTab):
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(
@@ -466,15 +534,15 @@ class StaticConversationTab(ConversationTab):
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()
- def init_logs(self) -> None:
+ def init_logs(self, initial=None) -> None:
# Disable local logs becauseā€¦
pass
diff --git a/poezio/tabs/privatetab.py b/poezio/tabs/privatetab.py
index daf81733..439ab5f8 100644
--- a/poezio/tabs/privatetab.py
+++ b/poezio/tabs/privatetab.py
@@ -12,19 +12,23 @@ the ConversationTab (such as tab-completion on nicks from the room).
"""
import curses
import logging
+from datetime import datetime
from typing import Dict, Callable
from slixmpp import JID
+from slixmpp.stanza import Message as SMessage
from poezio.tabs import OneToOneTab, MucTab, Tab
+from poezio import common
from poezio import windows
from poezio import xhtml
-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.theming import get_theme, dump_tuple
from poezio.decorators import command_args_parser
+from poezio.text_buffer import CorrectionError
from poezio.ui.types import (
Message,
PersistentInfoMessage,
@@ -42,7 +46,7 @@ class PrivateTab(OneToOneTab):
message_type = 'chat'
additional_information: Dict[str, Callable[[str], str]] = {}
- def __init__(self, core, jid, nick):
+ def __init__(self, core, jid, nick, initial=None):
OneToOneTab.__init__(self, core, jid)
self.own_nick = nick
self.info_header = windows.PrivateInfoWin()
@@ -137,6 +141,64 @@ class PrivateTab(OneToOneTab):
and not self.input.get_text().startswith('//'))
self.send_composing_chat_state(empty_after)
+ def handle_message(self, message: SMessage, display: bool = True):
+ sent = message['from'].bare == self.core.xmpp.boundjid.bare
+ jid = message['to'] if sent else message['from']
+ with_nick = jid.resource
+ sender_nick = with_nick
+ if sent:
+ sender_nick = (self.own_nick or self.core.own_nick)
+ room_from = jid.bare
+ use_xhtml = config.get_by_tabname(
+ 'enable_xhtml_im',
+ jid.bare
+ )
+ tmp_dir = get_image_cache()
+ if not sent:
+ self.core.events.trigger('private_msg', message, self)
+ body = xhtml.get_body_from_message_stanza(
+ message, use_xhtml=use_xhtml, extract_images_to=tmp_dir)
+ if not body or not self:
+ return
+ delayed, date = common.find_delayed_tag(message)
+ replaced = False
+ user = self.parent_muc.get_user_by_name(with_nick)
+ if message.get_plugin('replace', check=True):
+ replaced_id = message['replace']['id']
+ if replaced_id != '' and config.get_by_tabname(
+ 'group_corrections', room_from):
+ try:
+ self.modify_message(
+ body,
+ replaced_id,
+ message['id'],
+ user=user,
+ time=date,
+ jid=message['from'],
+ nickname=sender_nick)
+ replaced = True
+ except CorrectionError:
+ log.debug('Unable to correct a message', exc_info=True)
+ if not replaced:
+ msg = Message(
+ txt=body,
+ time=date,
+ history=delayed,
+ nickname=sender_nick,
+ nick_color=get_theme().COLOR_OWN_NICK if sent else None,
+ user=user,
+ identifier=message['id'],
+ jid=message['from'],
+ )
+ if display:
+ self.add_message(msg)
+ else:
+ self.log_message(msg)
+ if sent:
+ self.set_last_sent_message(message, correct=replaced)
+ else:
+ self.last_remote_message = datetime.now()
+
@refresh_wrapper.always
@command_args_parser.raw
def command_say(self, line: str, attention: bool = False, correct: bool = False) -> None: