Compare commits
12 commits
Author | SHA1 | Date | |
---|---|---|---|
b505c371b0 | |||
3b81e36242 | |||
8dc4eabdad | |||
|
a87366aa9d | ||
8d178d9af8 | |||
f4185b7485 | |||
2efdbf0490 | |||
|
c4112346d4 | ||
|
b7b221c988 | ||
|
e4b21822d3 | ||
|
b61db7d0bb | ||
|
cf3d06fb0a |
19 changed files with 226 additions and 179 deletions
|
@ -1,7 +1,6 @@
|
|||
from typing import Tuple, Dict, List, Union
|
||||
import curses
|
||||
import hashlib
|
||||
import math
|
||||
|
||||
from . import hsluv
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ from datetime import (
|
|||
timezone,
|
||||
)
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
@ -23,7 +23,7 @@ import string
|
|||
import logging
|
||||
import itertools
|
||||
|
||||
from slixmpp import JID, InvalidJID, Message
|
||||
from slixmpp import Message
|
||||
from poezio.poezio_shlex import shlex
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -8,7 +8,7 @@ from xml.etree import ElementTree as ET
|
|||
from typing import List, Optional, Tuple
|
||||
import logging
|
||||
|
||||
from slixmpp import Iq, JID, InvalidJID
|
||||
from slixmpp import JID, InvalidJID
|
||||
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
|
||||
from slixmpp.xmlstream.xmlstream import NotConnectedError
|
||||
from slixmpp.xmlstream.stanzabase import StanzaBase
|
||||
|
@ -446,13 +446,18 @@ class CommandCore:
|
|||
"""
|
||||
/bookmark_local [room][/nick] [password]
|
||||
"""
|
||||
if not args and not isinstance(self.core.tabs.current_tab,
|
||||
tabs.MucTab):
|
||||
tab = self.core.tabs.current_tab
|
||||
if not args and not isinstance(tab, tabs.MucTab):
|
||||
return
|
||||
|
||||
room, nick = self._parse_join_jid(args[0] if args else '')
|
||||
password = args[1] if len(args) > 1 else None
|
||||
|
||||
if not room:
|
||||
room = tab.jid.bare
|
||||
if password is None and tab.password is not None:
|
||||
password = tab.password
|
||||
|
||||
asyncio.create_task(
|
||||
self._add_bookmark(
|
||||
room=room,
|
||||
|
@ -468,8 +473,8 @@ class CommandCore:
|
|||
"""
|
||||
/bookmark [room][/nick] [autojoin] [password]
|
||||
"""
|
||||
if not args and not isinstance(self.core.tabs.current_tab,
|
||||
tabs.MucTab):
|
||||
tab = self.core.tabs.current_tab
|
||||
if not args and not isinstance(tab, tabs.MucTab):
|
||||
return
|
||||
room, nick = self._parse_join_jid(args[0] if args else '')
|
||||
password = args[2] if len(args) > 2 else None
|
||||
|
@ -478,13 +483,18 @@ class CommandCore:
|
|||
autojoin = (method == 'local' or
|
||||
(len(args) > 1 and args[1].lower() == 'true'))
|
||||
|
||||
if not room:
|
||||
room = tab.jid.bare
|
||||
if password is None and tab.password is not None:
|
||||
password = tab.password
|
||||
|
||||
asyncio.create_task(
|
||||
self._add_bookmark(room, nick, autojoin, password, method)
|
||||
)
|
||||
|
||||
async def _add_bookmark(
|
||||
self,
|
||||
room: Optional[str],
|
||||
room: str,
|
||||
nick: Optional[str],
|
||||
autojoin: bool,
|
||||
password: str,
|
||||
|
@ -503,16 +513,8 @@ class CommandCore:
|
|||
method: 'local' or 'remote'.
|
||||
'''
|
||||
|
||||
# No room Jid was specified. A nick may have been specified. Set the
|
||||
# room Jid to be bookmarked to the current tab bare jid.
|
||||
if not room:
|
||||
tab = self.core.tabs.current_tab
|
||||
if not isinstance(tab, tabs.MucTab):
|
||||
return
|
||||
room = tab.jid.bare
|
||||
if password is None and tab.password is not None:
|
||||
password = tab.password
|
||||
elif room == '*':
|
||||
|
||||
if room == '*':
|
||||
return await self._add_wildcard_bookmarks(method)
|
||||
|
||||
# Once we found which room to bookmark, find corresponding tab if it
|
||||
|
@ -524,13 +526,14 @@ class CommandCore:
|
|||
|
||||
# Validate / Normalize
|
||||
try:
|
||||
if nick is None:
|
||||
if not nick:
|
||||
jid = JID(room)
|
||||
else:
|
||||
jid = JID('{}/{}'.format(room, nick))
|
||||
room = jid.bare
|
||||
nick = jid.resource or None
|
||||
except InvalidJID:
|
||||
self.core.information(f'Invalid address for bookmark: {room}/{nick}', 'Error')
|
||||
return
|
||||
|
||||
bookmark = self.core.bookmarks[room]
|
||||
|
|
|
@ -15,7 +15,6 @@ import pipes
|
|||
import sys
|
||||
import shutil
|
||||
import time
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from typing import (
|
||||
Any,
|
||||
|
|
|
@ -4,19 +4,16 @@ XMPP-related handlers for the Core class
|
|||
|
||||
import logging
|
||||
|
||||
from typing import Optional, Union
|
||||
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
|
||||
|
@ -25,8 +22,6 @@ 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 tabs
|
||||
from poezio import xhtml
|
||||
from poezio import multiuserchat as muc
|
||||
|
@ -36,12 +31,10 @@ 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,
|
||||
Message as PMessage,
|
||||
BaseMessage,
|
||||
InfoMessage,
|
||||
PersistentInfoMessage,
|
||||
)
|
||||
|
@ -363,7 +356,7 @@ class HandlerCore:
|
|||
)
|
||||
self.core.tabs.append(conversation)
|
||||
else:
|
||||
conversation.handle_message(message)
|
||||
await 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):
|
||||
|
@ -478,7 +471,7 @@ class HandlerCore:
|
|||
else:
|
||||
contact.name = ''
|
||||
|
||||
async def on_groupchat_message(self, message: Message):
|
||||
async def on_groupchat_message(self, message: Message) -> None:
|
||||
"""
|
||||
Triggered whenever a message is received from a multi-user chat room.
|
||||
"""
|
||||
|
@ -495,99 +488,8 @@ 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
|
||||
|
||||
await self.core.events.trigger_async('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)
|
||||
|
||||
# TODO: #3314. Is this a MUC reflection?
|
||||
# Is this an encrypted message? Is so ignore.
|
||||
# It is not possible in the OMEMO case to decrypt these messages
|
||||
# since we don't encrypt for our own device (something something
|
||||
# forward secrecy), but even for non-FS encryption schemes anyway
|
||||
# messages shouldn't have changed after a round-trip to the room.
|
||||
# Otherwire replace the matching message we sent.
|
||||
|
||||
if not body:
|
||||
return
|
||||
|
||||
old_state = tab.state
|
||||
delayed, date = common.find_delayed_tag(message)
|
||||
is_history = not tab.joined and delayed
|
||||
|
||||
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,
|
||||
delayed=delayed,
|
||||
nickname=nick_from,
|
||||
user=user):
|
||||
await self.core.events.trigger_async('highlight', message, tab)
|
||||
replaced = True
|
||||
except CorrectionError:
|
||||
log.debug('Unable to correct a message', exc_info=True)
|
||||
|
||||
if not replaced:
|
||||
ui_msg: Union[InfoMessage, PMessage]
|
||||
# Messages coming from MUC barejid (Server maintenance, IRC mode
|
||||
# changes from biboumi, etc.) are displayed as info messages.
|
||||
highlight = False
|
||||
if message['from'].resource:
|
||||
highlight = tab.message_is_highlight(body, nick_from, is_history)
|
||||
ui_msg = PMessage(
|
||||
txt=body,
|
||||
time=date,
|
||||
nickname=nick_from,
|
||||
history=is_history,
|
||||
delayed=delayed,
|
||||
identifier=message['id'],
|
||||
jid=message['from'],
|
||||
user=user,
|
||||
highlight=highlight,
|
||||
)
|
||||
typ = 1
|
||||
else:
|
||||
ui_msg = InfoMessage(
|
||||
txt=body,
|
||||
time=date,
|
||||
identifier=message['id'],
|
||||
)
|
||||
typ = 2
|
||||
tab.add_message(ui_msg)
|
||||
if highlight:
|
||||
await self.core.events.trigger_async('highlight', message, tab)
|
||||
|
||||
if message['from'].resource == tab.own_nick:
|
||||
tab.set_last_sent_message(message, correct=replaced)
|
||||
|
||||
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.getstr('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()
|
||||
|
@ -638,7 +540,7 @@ class HandlerCore:
|
|||
self.core.tabs.append(tab)
|
||||
tab.parent_muc.privates.append(tab)
|
||||
else:
|
||||
tab.handle_message(message)
|
||||
await 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):
|
||||
|
|
|
@ -5,17 +5,14 @@ upstream.
|
|||
TODO: Check that they are fixed and remove those hacks
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Callable, Any
|
||||
from slixmpp import Message, Iq, ClientXMPP
|
||||
from slixmpp.xmlstream import ET
|
||||
from slixmpp import ClientXMPP, Message
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _filter_add_receipt_request(self, stanza):
|
||||
def _filter_add_receipt_request(self: ClientXMPP, stanza):
|
||||
"""
|
||||
Auto add receipt requests to outgoing messages, if:
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from hashlib import md5
|
||||
|
|
|
@ -148,9 +148,9 @@ class E2EEPlugin(BasePlugin):
|
|||
|
||||
# Ensure decryption is done before everything, so that other handlers
|
||||
# don't have to know about the encryption mechanism.
|
||||
self.api.add_event_handler('muc_msg', self._decrypt, priority=0)
|
||||
self.api.add_event_handler('conversation_msg', self._decrypt, priority=0)
|
||||
self.api.add_event_handler('private_msg', self._decrypt, priority=0)
|
||||
self.api.add_event_handler('muc_msg', self._decrypt_wrapper, priority=0)
|
||||
self.api.add_event_handler('conversation_msg', self._decrypt_wrapper, priority=0)
|
||||
self.api.add_event_handler('private_msg', self._decrypt_wrapper, priority=0)
|
||||
|
||||
# Ensure encryption is done after everything, so that whatever can be
|
||||
# encrypted is encrypted, and no plain element slips in.
|
||||
|
@ -344,6 +344,28 @@ class E2EEPlugin(BasePlugin):
|
|||
result = await self._encrypt(stanza, passthrough=True)
|
||||
except NothingToEncrypt:
|
||||
return stanza
|
||||
except Exception as exc:
|
||||
jid = stanza['from']
|
||||
tab = self.core.tabs.by_name_and_class(jid, ChatTab)
|
||||
msg = ' \n\x19%s}Could not decrypt message: %s' % (
|
||||
dump_tuple(get_theme().COLOR_CHAR_NACK),
|
||||
exc,
|
||||
)
|
||||
# XXX: check before commit. Do we not nack in MUCs?
|
||||
if tab and not isinstance(tab, MucTab):
|
||||
tab.nack_message(msg, stanza['id'], stanza['to'])
|
||||
# TODO: display exceptions to the user properly
|
||||
log.error('Exception in encrypt:', exc_info=True)
|
||||
return None
|
||||
return result
|
||||
|
||||
async def _decrypt_wrapper(self, stanza: StanzaBase, tab: ChatTabs) -> Optional[StanzaBase]:
|
||||
"""
|
||||
Wrapper around _decrypt() to handle errors and display the message after encryption.
|
||||
"""
|
||||
try:
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
result = await self._decrypt(stanza, tab, passthrough=True)
|
||||
except Exception as exc:
|
||||
jid = stanza['to']
|
||||
tab = self.core.tabs.by_name_and_class(jid, ChatTab)
|
||||
|
@ -352,14 +374,15 @@ class E2EEPlugin(BasePlugin):
|
|||
exc,
|
||||
)
|
||||
# XXX: check before commit. Do we not nack in MUCs?
|
||||
if not isinstance(tab, MucTab):
|
||||
if tab and not isinstance(tab, MucTab):
|
||||
tab.nack_message(msg, stanza['id'], stanza['from'])
|
||||
# TODO: display exceptions to the user properly
|
||||
log.error('Exception in encrypt:', exc_info=True)
|
||||
log.error('Exception in decrypt:', exc_info=True)
|
||||
return None
|
||||
return result
|
||||
|
||||
def _decrypt(self, message: Message, tab: ChatTabs) -> None:
|
||||
|
||||
async def _decrypt(self, message: Message, tab: ChatTabs, passthrough: bool = True) -> None:
|
||||
|
||||
has_eme = False
|
||||
if message.xml.find('{%s}%s' % (EME_NS, EME_TAG)) is not None and \
|
||||
|
@ -397,7 +420,14 @@ class E2EEPlugin(BasePlugin):
|
|||
if user is not None:
|
||||
jid = user.jid or None
|
||||
|
||||
self.decrypt(message, jid, tab)
|
||||
# Call the enabled encrypt method
|
||||
func = self.decrypt
|
||||
if iscoroutinefunction(func):
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
await func(message, jid, tab, passthrough=True)
|
||||
else:
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
func(message, jid, tab)
|
||||
|
||||
log.debug('Decrypted %s message: %r', self.encryption_name, message['body'])
|
||||
return None
|
||||
|
@ -412,10 +442,15 @@ class E2EEPlugin(BasePlugin):
|
|||
# MUCs). Let the plugin decide what to do with this information.
|
||||
jids: Optional[List[JID]] = [message['to']]
|
||||
tab = self.core.tabs.by_jid(message['to'])
|
||||
if tab is None: # When does that ever happen?
|
||||
log.debug('Attempting to encrypt a message to \'%s\' '
|
||||
'that is not attached to a Tab. ?! Aborting '
|
||||
'encryption.', message['to'])
|
||||
if tab is None and message['to'].resource:
|
||||
# Redo the search with the bare JID
|
||||
tab = self.core.tabs.by_jid(message['to'].bare)
|
||||
|
||||
if tab is None: # Possible message sent directly by the e2ee lib?
|
||||
log.debug(
|
||||
'A message we do not have a tab for '
|
||||
'is being sent to \'%s\'. Abort.', message['to'],
|
||||
)
|
||||
return None
|
||||
|
||||
parent = None
|
||||
|
@ -449,7 +484,7 @@ class E2EEPlugin(BasePlugin):
|
|||
if user.jid.bare:
|
||||
jids.append(user.jid)
|
||||
|
||||
if not self._encryption_enabled(tab.jid):
|
||||
if tab and not self._encryption_enabled(tab.jid):
|
||||
raise NothingToEncrypt()
|
||||
|
||||
log.debug('Sending %s message', self.encryption_name)
|
||||
|
|
|
@ -390,12 +390,6 @@ class Tab:
|
|||
"""
|
||||
return self.name
|
||||
|
||||
def get_text_window(self) -> Optional[windows.TextWin]:
|
||||
"""
|
||||
Returns the principal TextWin window, if there's one
|
||||
"""
|
||||
return None
|
||||
|
||||
def on_input(self, key: str, raw: bool):
|
||||
"""
|
||||
raw indicates if the key should activate the associated command or not.
|
||||
|
@ -1020,7 +1014,7 @@ class OneToOneTab(ChatTab):
|
|||
'send a message along with the attention.')
|
||||
self.init_logs(initial=initial)
|
||||
|
||||
def init_logs(self, initial=None) -> None:
|
||||
def init_logs(self, initial: Optional[SMessage] = 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:
|
||||
|
@ -1031,19 +1025,19 @@ class OneToOneTab(ChatTab):
|
|||
if initial is not None:
|
||||
# If there is an initial message, throw it back into the
|
||||
# text buffer if it cannot be fetched from mam
|
||||
async def fallback_no_mam():
|
||||
async def fallback_no_mam() -> None:
|
||||
await mam_filler.done.wait()
|
||||
if mam_filler.result == 0:
|
||||
self.handle_message(initial)
|
||||
await self.handle_message(initial)
|
||||
|
||||
asyncio.create_task(fallback_no_mam())
|
||||
elif use_log and initial:
|
||||
self.handle_message(initial, display=False)
|
||||
asyncio.create_task(self.handle_message(initial, display=False))
|
||||
asyncio.create_task(
|
||||
LogLoader(logger, self, use_log).tab_open()
|
||||
)
|
||||
|
||||
def handle_message(self, msg: SMessage, display: bool = True):
|
||||
async def handle_message(self, msg: SMessage, display: bool = True):
|
||||
pass
|
||||
|
||||
def remote_user_color(self):
|
||||
|
|
|
@ -22,7 +22,6 @@ 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, get_image_cache
|
||||
|
@ -106,7 +105,7 @@ class ConversationTab(OneToOneTab):
|
|||
def completion(self):
|
||||
self.complete_commands(self.input)
|
||||
|
||||
def handle_message(self, message: SMessage, display: bool = True):
|
||||
async def handle_message(self, message: SMessage, display: bool = True):
|
||||
"""Handle a received message.
|
||||
|
||||
The message can come from us (carbon copy).
|
||||
|
@ -133,7 +132,7 @@ class ConversationTab(OneToOneTab):
|
|||
else:
|
||||
return
|
||||
|
||||
self.core.events.trigger('conversation_msg', message, self)
|
||||
await self.core.events.trigger_async('conversation_msg', message, self)
|
||||
|
||||
if not message['body']:
|
||||
return
|
||||
|
@ -439,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):
|
||||
|
|
|
@ -4,6 +4,7 @@ A MucListTab is a tab listing the rooms on a conference server.
|
|||
It has no functionality except scrolling the list, and allowing the
|
||||
user to join the rooms.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Callable
|
||||
|
||||
|
@ -74,4 +75,4 @@ class MucListTab(ListTab):
|
|||
row = self.listview.get_selected_row()
|
||||
if not row:
|
||||
return
|
||||
self.core.command.join(row[1])
|
||||
asyncio.ensure_future(self.core.command.join(row[1]))
|
||||
|
|
|
@ -18,6 +18,7 @@ import random
|
|||
import re
|
||||
import functools
|
||||
from copy import copy
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
cast,
|
||||
|
@ -44,12 +45,13 @@ from poezio import timed_events
|
|||
from poezio import windows
|
||||
from poezio import xhtml
|
||||
from poezio.common import to_utc
|
||||
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, command_args_parser
|
||||
from poezio.logger import logger
|
||||
from poezio.log_loader import LogLoader, MAMFiller
|
||||
from poezio.roster import roster
|
||||
from poezio.text_buffer import CorrectionError
|
||||
from poezio.theming import get_theme, dump_tuple
|
||||
from poezio.user import User
|
||||
from poezio.core.structs import Completion, Status
|
||||
|
@ -73,6 +75,18 @@ NS_MUC_USER = 'http://jabber.org/protocol/muc#user'
|
|||
COMPARE_USERS_LAST_TALKED = lambda x: x.last_talked
|
||||
|
||||
|
||||
@dataclass
|
||||
class MessageData:
|
||||
message: SMessage
|
||||
delayed: bool
|
||||
date: Optional[datetime]
|
||||
nick: str
|
||||
user: Optional[User]
|
||||
room_from: str
|
||||
body: str
|
||||
is_history: bool
|
||||
|
||||
|
||||
class MucTab(ChatTab):
|
||||
"""
|
||||
The tab containing a multi-user-chat room.
|
||||
|
@ -452,9 +466,6 @@ class MucTab(ChatTab):
|
|||
# TODO: send the disco#info identity name here, if it exists.
|
||||
return self.jid.node
|
||||
|
||||
def get_text_window(self) -> windows.TextWin:
|
||||
return self.text_win
|
||||
|
||||
def on_lose_focus(self) -> None:
|
||||
if self.joined:
|
||||
if self.input.text:
|
||||
|
@ -482,6 +493,126 @@ class MucTab(ChatTab):
|
|||
self.general_jid) and not self.input.get_text():
|
||||
self.send_chat_state('active')
|
||||
|
||||
async def handle_message(self, message: SMessage) -> bool:
|
||||
"""Parse an incoming message
|
||||
|
||||
Returns False if the message was dropped silently.
|
||||
"""
|
||||
room_from = message['from'].bare
|
||||
nick_from = message['mucnick']
|
||||
user = self.get_user_by_name(nick_from)
|
||||
if user and user in self.ignores:
|
||||
return False
|
||||
|
||||
await self.core.events.trigger_async('muc_msg', message, self)
|
||||
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)
|
||||
|
||||
# TODO: #3314. Is this a MUC reflection?
|
||||
# Is this an encrypted message? Is so ignore.
|
||||
# It is not possible in the OMEMO case to decrypt these messages
|
||||
# since we don't encrypt for our own device (something something
|
||||
# forward secrecy), but even for non-FS encryption schemes anyway
|
||||
# messages shouldn't have changed after a round-trip to the room.
|
||||
# Otherwire replace the matching message we sent.
|
||||
if not body:
|
||||
return False
|
||||
|
||||
old_state = self.state
|
||||
delayed, date = common.find_delayed_tag(message)
|
||||
is_history = not self.joined and delayed
|
||||
|
||||
mdata = MessageData(
|
||||
message, delayed, date, nick_from, user, room_from, body,
|
||||
is_history
|
||||
)
|
||||
|
||||
replaced = False
|
||||
if message.xml.find('{urn:xmpp:message-correct:0}replace') is not None:
|
||||
replaced = await self._handle_correction_message(mdata)
|
||||
if not replaced:
|
||||
await self._handle_normal_message(mdata)
|
||||
if mdata.nick == self.own_nick:
|
||||
self.set_last_sent_message(message, correct=replaced)
|
||||
self._refresh_after_message(old_state)
|
||||
return True
|
||||
|
||||
def _refresh_after_message(self, old_state: str) -> None:
|
||||
"""Refresh the appropriate UI after a message is received"""
|
||||
if self is self.core.tabs.current_tab:
|
||||
self.refresh()
|
||||
elif self.state != old_state:
|
||||
self.core.refresh_tab_win()
|
||||
current = self.core.tabs.current_tab
|
||||
current.refresh_input()
|
||||
self.core.doupdate()
|
||||
|
||||
async def _handle_correction_message(self, message: MessageData) -> bool:
|
||||
"""Process a correction message.
|
||||
|
||||
Returns true if a message was actually corrected.
|
||||
"""
|
||||
replaced_id = message.message['replace']['id']
|
||||
if replaced_id != '' and config.get_by_tabname(
|
||||
'group_corrections', message.room_from):
|
||||
try:
|
||||
delayed_date = message.date or datetime.now()
|
||||
modify_hl = self.modify_message(
|
||||
message.body,
|
||||
replaced_id,
|
||||
message.message['id'],
|
||||
time=delayed_date,
|
||||
delayed=message.delayed,
|
||||
nickname=message.nick,
|
||||
user=message.user
|
||||
)
|
||||
if modify_hl:
|
||||
await self.core.events.trigger_async(
|
||||
'highlight',
|
||||
message.message,
|
||||
self
|
||||
)
|
||||
return True
|
||||
except CorrectionError:
|
||||
log.debug('Unable to correct a message', exc_info=True)
|
||||
return False
|
||||
|
||||
async def _handle_normal_message(self, message: MessageData) -> None:
|
||||
"""
|
||||
Process the non-correction groupchat message.
|
||||
"""
|
||||
ui_msg: Union[InfoMessage, Message]
|
||||
# Messages coming from MUC barejid (Server maintenance, IRC mode
|
||||
# changes from biboumi, etc.) have no nick/resource and are displayed
|
||||
# as info messages.
|
||||
highlight = False
|
||||
if message.nick:
|
||||
highlight = self.message_is_highlight(
|
||||
message.body, message.nick, message.is_history
|
||||
)
|
||||
ui_msg = Message(
|
||||
txt=message.body,
|
||||
time=message.date,
|
||||
nickname=message.nick,
|
||||
history=message.is_history,
|
||||
delayed=message.delayed,
|
||||
identifier=message.message['id'],
|
||||
jid=message.message['from'],
|
||||
user=message.user,
|
||||
highlight=highlight,
|
||||
)
|
||||
else:
|
||||
ui_msg = InfoMessage(
|
||||
txt=message.body,
|
||||
time=message.date,
|
||||
identifier=message.message['id'],
|
||||
)
|
||||
self.add_message(ui_msg)
|
||||
if highlight:
|
||||
await self.core.events.trigger_async('highlight', message, self)
|
||||
|
||||
def handle_presence(self, presence: Presence) -> None:
|
||||
"""Handle MUC presence"""
|
||||
self.reset_lag()
|
||||
|
|
|
@ -142,7 +142,7 @@ 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):
|
||||
async 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
|
||||
|
@ -156,7 +156,7 @@ class PrivateTab(OneToOneTab):
|
|||
)
|
||||
tmp_dir = get_image_cache()
|
||||
if not sent:
|
||||
self.core.events.trigger('private_msg', message, self)
|
||||
await self.core.events.trigger_async('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:
|
||||
|
@ -361,9 +361,6 @@ class PrivateTab(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
|
||||
|
||||
@refresh_wrapper.conditional
|
||||
def rename_user(self, old_nick, user):
|
||||
"""
|
||||
|
|
|
@ -10,7 +10,6 @@ log = logging.getLogger(__name__)
|
|||
|
||||
import curses
|
||||
import os
|
||||
from typing import Union, Optional
|
||||
from slixmpp import JID, InvalidJID
|
||||
from slixmpp.xmlstream import matcher, StanzaBase
|
||||
from slixmpp.xmlstream.tostring import tostring
|
||||
|
|
|
@ -73,7 +73,6 @@ except ImportError:
|
|||
|
||||
import curses
|
||||
import functools
|
||||
import os
|
||||
from typing import Dict, List, Union, Tuple, Optional, cast
|
||||
from pathlib import Path
|
||||
from os import path
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from datetime import datetime
|
||||
|
||||
FORMAT_CHAR = '\x19'
|
||||
# These are non-printable chars, so they should never appear in the input,
|
||||
# I guess. But maybe we can find better chars that are even less risky.
|
||||
|
|
|
@ -12,7 +12,6 @@ A user is a MUC participant, not a roster contact (see contact.py)
|
|||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
from hashlib import md5
|
||||
from random import choice
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from poezio import xhtml, colors
|
||||
|
|
|
@ -5,7 +5,7 @@ Text inputs.
|
|||
import curses
|
||||
import logging
|
||||
import string
|
||||
from typing import List, Dict, Callable, Optional, ClassVar, Union
|
||||
from typing import List, Dict, Callable, Optional, ClassVar
|
||||
|
||||
from poezio import keyboard
|
||||
from poezio import common
|
||||
|
|
|
@ -6,11 +6,10 @@ import logging
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Union, Dict
|
||||
from typing import Optional, List, Union
|
||||
|
||||
from poezio.windows.base_wins import Win
|
||||
|
||||
from poezio import common
|
||||
from poezio.config import config
|
||||
from poezio.contact import Contact, Resource
|
||||
from poezio.roster import Roster, RosterGroup
|
||||
|
|
Loading…
Reference in a new issue