Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlovetox <philipp@hoerist.com>2022-08-26 22:34:04 +0300
committerlovetox <philipp@hoerist.com>2022-08-31 22:46:40 +0300
commit5de6f7ea075b2a728f42d0b2f779d15963315e73 (patch)
tree77b6363164c3f4f118c14c164ca337773d8c6b4d
parent307b7bafdec7102c7e5a513893f4ef1113a18039 (diff)
new: Add event storagearchive_cache
-rw-r--r--gajim/common/app.py2
-rw-r--r--gajim/common/application.py4
-rw-r--r--gajim/common/events.py101
-rw-r--r--gajim/common/modules/contacts.py61
-rw-r--r--gajim/common/modules/muc.py64
-rw-r--r--gajim/common/storage/base.py21
-rw-r--r--gajim/common/storage/events.py132
-rw-r--r--gajim/gtk/chat_banner.py10
-rw-r--r--gajim/gtk/chat_stack.py9
-rw-r--r--gajim/gtk/control.py657
-rw-r--r--gajim/gtk/conversation/view.py2
-rw-r--r--gajim/gtk/groupchat_nick_completion.py8
-rw-r--r--gajim/gtk/groupchat_roster.py8
13 files changed, 755 insertions, 324 deletions
diff --git a/gajim/common/app.py b/gajim/common/app.py
index b27ed70bb..fa2155e97 100644
--- a/gajim/common/app.py
+++ b/gajim/common/app.py
@@ -59,6 +59,7 @@ if typing.TYPE_CHECKING:
from gajim.gui.application import GajimApplication
from gajim.common.storage.cache import CacheStorage
from gajim.common.storage.archive import MessageArchiveStorage
+ from gajim.common.storage.events import EventStorage
from gajim.common.cert_store import CertificateStore
from gajim.common.call_manager import CallManager
from gajim.common.preview import PreviewManager
@@ -88,6 +89,7 @@ class Storage:
def __init__(self):
self.cache: CacheStorage = None
self.archive: MessageArchiveStorage = None
+ self.events: EventStorage = None
storage = Storage()
diff --git a/gajim/common/application.py b/gajim/common/application.py
index 5817d9eb1..41971bfd4 100644
--- a/gajim/common/application.py
+++ b/gajim/common/application.py
@@ -44,6 +44,7 @@ from gajim.common.events import AllowGajimUpdateCheck
from gajim.common.events import GajimUpdateAvailable
from gajim.common.client import Client
from gajim.common.helpers import make_http_request
+from gajim.common.storage.events import EventStorage
from gajim.common.task_manager import TaskManager
from gajim.common.settings import Settings
from gajim.common.settings import LegacyConfig
@@ -74,6 +75,9 @@ class CoreApplication:
app.storage.cache = CacheStorage()
app.storage.cache.init()
+ app.storage.events = EventStorage()
+ app.storage.events.init()
+
app.storage.archive = MessageArchiveStorage()
app.storage.archive.init()
diff --git a/gajim/common/events.py b/gajim/common/events.py
index a4fac93d3..8640b2ebe 100644
--- a/gajim/common/events.py
+++ b/gajim/common/events.py
@@ -32,7 +32,10 @@ from nbxmpp.structs import ModerationData
from nbxmpp.structs import LocationData
from nbxmpp.structs import RosterItem
from nbxmpp.structs import TuneData
+from nbxmpp.const import Affiliation
from nbxmpp.const import InviteType
+from nbxmpp.const import Role
+from nbxmpp.const import StatusCode
from gajim.common.file_props import FileProp
from gajim.common.const import JingleState
@@ -676,3 +679,101 @@ class AllowGajimUpdateCheck(ApplicationEvent):
class GajimUpdateAvailable(ApplicationEvent):
name: str = field(init=False, default='gajim-update-available')
version: str
+
+
+@dataclass
+class MUCNicknameChanged(ApplicationEvent):
+ name: str = field(init=False, default='muc-nickname-changed')
+ is_self: bool
+ new_name: str
+ old_name: str
+ timestamp: float
+
+
+@dataclass
+class MUCRoomConfigChanged(ApplicationEvent):
+ name: str = field(init=False, default='muc-room-config-changed')
+ timestamp: float
+ status_codes: set[StatusCode]
+
+
+@dataclass
+class MUCRoomConfigFinished(ApplicationEvent):
+ name: str = field(init=False, default='muc-room-config-finished')
+ timestamp: float
+
+
+@dataclass
+class MUCRoomPresenceError(ApplicationEvent):
+ name: str = field(init=False, default='muc-room-presence-error')
+ timestamp: float
+ error: Any
+
+
+@dataclass
+class MUCRoomKicked(ApplicationEvent):
+ name: str = field(init=False, default='muc-room-kicked')
+ timestamp: float
+ status_codes: Optional[set[StatusCode]]
+ reason: Optional[str]
+ actor: Optional[str]
+
+
+@dataclass
+class MUCRoomDestroyed(ApplicationEvent):
+ name: str = field(init=False, default='muc-room-destroyed')
+ timestamp: float
+ reason: Optional[str]
+ alternate: Optional[JID]
+
+
+@dataclass
+class MUCUserJoined(ApplicationEvent):
+ name: str = field(init=False, default='muc-user-joined')
+ timestamp: float
+ is_self: bool
+ nick: str
+ status_codes: Optional[set[StatusCode]]
+
+
+@dataclass
+class MUCUserLeft(ApplicationEvent):
+ name: str = field(init=False, default='muc-user-left')
+ timestamp: float
+ is_self: bool
+ nick: str
+ status_codes: Optional[set[StatusCode]]
+ reason: Optional[str]
+ actor: Optional[str]
+
+
+@dataclass
+class MUCUserRoleChanged(ApplicationEvent):
+ name: str = field(init=False, default='muc-user-role-changed')
+ timestamp: float
+ is_self: bool
+ nick: str
+ role: Role
+ reason: Optional[str]
+ actor: Optional[str]
+
+
+@dataclass
+class MUCUserAffiliationChanged(ApplicationEvent):
+ name: str = field(init=False, default='muc-user-affiliation-changed')
+ timestamp: float
+ is_self: bool
+ nick: str
+ affiliation: Affiliation
+ reason: Optional[str]
+ actor: Optional[str]
+
+
+@dataclass
+class MUCUserStatusShowChanged(ApplicationEvent):
+ name: str = field(init=False, default='muc-user-status-show-changed')
+ timestamp: float
+ is_self: bool
+ nick: str
+ status: str
+ show_value: str
diff --git a/gajim/common/modules/contacts.py b/gajim/common/modules/contacts.py
index e8e65fb39..8d89c2a9a 100644
--- a/gajim/common/modules/contacts.py
+++ b/gajim/common/modules/contacts.py
@@ -15,11 +15,14 @@
from __future__ import annotations
from typing import Any
+from typing import cast
from typing import Iterator
from typing import Optional
from typing import Union
from typing import overload
+import time
+
import cairo
from nbxmpp.const import Affiliation
from nbxmpp.const import Chatstate
@@ -29,9 +32,12 @@ from nbxmpp.protocol import JID
from nbxmpp.structs import DiscoInfo
from nbxmpp.structs import LocationData
from nbxmpp.structs import TuneData
+from nbxmpp.structs import MessageProperties
from nbxmpp.structs import MucSubject
+from nbxmpp.structs import PresenceProperties
from gajim.common import app
+from gajim.common import events
from gajim.common import types
from gajim.common.const import PresenceShowExt
from gajim.common.const import SimpleClientState
@@ -942,12 +948,32 @@ class GroupchatParticipant(CommonContact):
if not self._presence.available and presence.available:
self._presence = presence
- self.notify('user-joined', *args)
+
+ properties = cast(MessageProperties, args[0])
+ event = events.MUCUserJoined(
+ timestamp=time.time(),
+ is_self=properties.is_muc_self_presence,
+ nick=self.name,
+ status_codes=properties.muc_status_codes)
+
+ app.storage.events.store(self.room, event)
+ self.notify('user-joined', event)
return
if not presence.available:
self._presence = presence
- self.notify('user-left', *args)
+
+ properties = cast(MessageProperties, args[0])
+ event = events.MUCUserLeft(
+ timestamp=time.time(),
+ is_self=properties.is_muc_self_presence,
+ nick=self.name,
+ status_codes=properties.muc_status_codes,
+ reason=properties.muc_user.reason,
+ actor=properties.muc_user.actor)
+
+ app.storage.events.store(self.room, event)
+ self.notify('user-left', event)
return
signals: list[str] = []
@@ -962,8 +988,37 @@ class GroupchatParticipant(CommonContact):
signals.append('user-status-show-changed')
self._presence = presence
+
for signal in signals:
- self.notify(signal, *args)
+ properties = cast(PresenceProperties, args[0])
+ if signal == 'user-affiliation-changed':
+ event = events.MUCUserAffiliationChanged(
+ timestamp=time.time(),
+ is_self=properties.is_muc_self_presence,
+ nick=self.name,
+ affiliation=self.affiliation,
+ reason=properties.muc_user.reason,
+ actor=properties.muc_user.actor)
+
+ if signal == 'user-role-changed':
+ event = events.MUCUserRoleChanged(
+ timestamp=time.time(),
+ is_self=properties.is_muc_self_presence,
+ nick=self.name,
+ role=self.role,
+ reason=properties.muc_user.reason,
+ actor=properties.muc_user.actor)
+
+ if signal == 'user-status-show-changed':
+ event = events.MUCUserStatusShowChanged(
+ timestamp=time.time(),
+ is_self=properties.is_muc_self_presence,
+ nick=self.name,
+ status=properties.status,
+ show_value=properties.show.value)
+
+ app.storage.events.store(self.room, event)
+ self.notify(signal, event)
def update_avatar(self, *args: Any) -> None:
app.app.avatar_storage.invalidate_cache(self._jid)
diff --git a/gajim/common/modules/muc.py b/gajim/common/modules/muc.py
index 4521cacbd..98e7107b0 100644
--- a/gajim/common/modules/muc.py
+++ b/gajim/common/modules/muc.py
@@ -44,6 +44,7 @@ from nbxmpp.task import Task
from gi.repository import GLib
from gajim.common import app
+from gajim.common import events
from gajim.common import helpers
from gajim.common import types
from gajim.common.const import ClientState, KindConstant
@@ -456,7 +457,11 @@ class MUC(BaseModule):
self._log.info('Configuration finished: %s', jid)
room = self._get_contact(jid.bare)
- room.notify('room-config-finished')
+ event = events.MUCRoomConfigFinished(timestamp=time.time())
+
+ assert isinstance(room, GroupchatContact)
+ app.storage.events.store(room, event)
+ room.notify('room-config-finished', event)
def update_presence(self) -> None:
mucs = self._get_mucs_with_state([MUCJoinedState.JOINED,
@@ -536,6 +541,11 @@ class MUC(BaseModule):
self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
else:
+ event = events.MUCRoomPresenceError(
+ timestamp=time.time(),
+ error=properties.error)
+ assert isinstance(room, GroupchatContact)
+ app.storage.events.store(room, event)
room.notify('room-presence-error', properties)
def _on_muc_user_presence(self,
@@ -560,7 +570,15 @@ class MUC(BaseModule):
self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
self._con.get_module('Bookmarks').remove(room_jid)
room.set_not_joined()
- room.notify('room-destroyed', properties)
+
+ event = events.MUCRoomDestroyed(
+ timestamp=time.time(),
+ reason=properties.muc_destroyed.reason,
+ alternate=properties.muc_destroyed.alternate)
+ assert isinstance(room, GroupchatContact)
+ app.storage.events.store(room, event)
+
+ room.notify('room-destroyed', event)
return
if properties.is_nickname_changed:
@@ -586,10 +604,16 @@ class MUC(BaseModule):
presence = self._process_user_presence(properties)
occupant.update_presence(presence, properties)
- room.notify('user-nickname-changed',
- occupant,
- new_occupant,
- properties)
+ event = events.MUCNicknameChanged(
+ timestamp=time.time(),
+ is_self=properties.is_muc_self_presence,
+ new_name=new_occupant.name,
+ old_name=occupant.name)
+
+ assert isinstance(room, GroupchatContact)
+ app.storage.events.store(room, event)
+
+ room.notify('user-nickname-changed', event, occupant, new_occupant)
return
is_joined = self._is_user_joined(properties.jid)
@@ -616,7 +640,16 @@ class MUC(BaseModule):
if properties.is_muc_self_presence and properties.is_kicked:
self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
room.set_not_joined()
- room.notify('room-kicked', properties)
+
+ event = events.MUCRoomKicked(
+ timestamp=time.time(),
+ status_codes=properties.muc_status_codes,
+ reason=properties.muc_user.reason,
+ actor=properties.muc_user.actor)
+ assert isinstance(room, GroupchatContact)
+ app.storage.events.store(room, event)
+ room.notify('room-kicked', event)
+
status_codes = properties.muc_status_codes or []
if StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
self._start_rejoin_timeout(room_jid)
@@ -889,12 +922,21 @@ class MUC(BaseModule):
if not properties.is_muc_config_change:
return
- room_jid = str(properties.muc_jid)
self._log.info('Received config change: %s %s',
- room_jid, properties.muc_status_codes)
+ properties.muc_jid, properties.muc_status_codes)
- room = self._get_contact(room_jid)
- room.notify('room-config-changed', properties)
+ assert properties.muc_status_codes is not None
+ event = events.MUCRoomConfigChanged(
+ timestamp=time.time(),
+ status_codes=properties.muc_status_codes)
+
+ assert properties.muc_jid is not None
+ room = self._get_contact(properties.muc_jid)
+
+ assert isinstance(room, GroupchatContact)
+ app.storage.events.store(room, event)
+
+ room.notify('room-config-changed', event)
raise nbxmpp.NodeProcessed
diff --git a/gajim/common/storage/base.py b/gajim/common/storage/base.py
index 8d59fcd8e..929c15232 100644
--- a/gajim/common/storage/base.py
+++ b/gajim/common/storage/base.py
@@ -30,11 +30,15 @@ from pathlib import Path
from gi.repository import GLib
+import nbxmpp.const
from nbxmpp.protocol import Iq
from nbxmpp.protocol import JID
from nbxmpp.structs import RosterItem
from nbxmpp.structs import DiscoInfo
from nbxmpp.structs import CommonError
+from nbxmpp.const import Role
+from nbxmpp.const import Affiliation
+from nbxmpp.const import StatusCode
from nbxmpp.modules.discovery import parse_disco_info
@@ -98,6 +102,13 @@ sqlite3.register_converter('disco_info', _convert_disco_info)
sqlite3.register_adapter(DiscoInfo, _adapt_disco_info)
+def _convert_json(json_string: bytes) -> dict[str, Any]:
+ return json.loads(json_string, object_hook=json_decoder)
+
+
+sqlite3.register_converter('JSON', _convert_json)
+
+
class Encoder(json.JSONEncoder):
def default(self, o: Any) -> Any:
if isinstance(o, set):
@@ -111,6 +122,10 @@ class Encoder(json.JSONEncoder):
dct['__type'] = 'RosterItem'
return dct
+ if isinstance(o, (Affiliation, Role, StatusCode)):
+ return {'value': o.value,
+ '__type': o.__class__.__name__}
+
return json.JSONEncoder.default(self, o)
@@ -118,8 +133,10 @@ def json_decoder(dct: dict[str, Any]) -> Any:
type_ = dct.get('__type')
if type_ is None:
return dct
+
if type_ == 'JID':
return JID.from_string(dct['value'])
+
if type_ == 'RosterItem':
return RosterItem(jid=dct['jid'],
name=dct['name'],
@@ -127,6 +144,10 @@ def json_decoder(dct: dict[str, Any]) -> Any:
subscription=dct['subscription'],
approved=dct['approved'],
groups=set(dct['groups']))
+
+ if type_ in ('Affiliation', 'Role', 'StatusCode'):
+ return getattr(nbxmpp.const, type_)(dct['value'])
+
return dct
diff --git a/gajim/common/storage/events.py b/gajim/common/storage/events.py
new file mode 100644
index 000000000..38b128dba
--- /dev/null
+++ b/gajim/common/storage/events.py
@@ -0,0 +1,132 @@
+# This file is part of Gajim.
+#
+# Gajim is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published
+# by the Free Software Foundation; version 3 only.
+#
+# Gajim is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import annotations
+import dataclasses
+
+from typing import Any
+from typing import NamedTuple
+
+import json
+import sqlite3
+import logging
+from collections import namedtuple
+
+from nbxmpp.protocol import JID
+
+from gajim.common import events
+from gajim.common.storage.base import SqliteStorage
+from gajim.common.storage.base import Encoder
+from gajim.common.types import ChatContactT
+
+
+EVENTS_SQL_STATEMENT = '''
+ CREATE TABLE events (
+ account TEXT,
+ jid TEXT,
+ event TEXT,
+ timestamp REAL,
+ data TEXT);
+
+ CREATE INDEX idx_account_jid ON events(account, jid);
+
+ PRAGMA user_version=1;
+ '''
+
+EVENT_CLASSES: dict[str, Any] = {
+ 'muc-nickname-changed': events.MUCNicknameChanged,
+ 'muc-room-config-changed': events.MUCRoomConfigChanged,
+ 'muc-room-config-finished': events.MUCRoomConfigFinished,
+ 'muc-room-presence-error': events.MUCRoomPresenceError,
+ 'muc-room-kicked': events.MUCRoomKicked,
+ 'muc-room-destroyed': events.MUCRoomDestroyed,
+ 'muc-user-joined': events.MUCUserJoined,
+ 'muc-user-left': events.MUCUserLeft,
+ 'muc-user-role-changed': events.MUCUserRoleChanged,
+ 'muc-user-affiliation-changed': events.MUCUserAffiliationChanged,
+ 'muc-user-status-show-changed': events.MUCUserStatusShowChanged,
+}
+
+log = logging.getLogger('gajim.c.storage.events')
+
+
+class EventRow(NamedTuple):
+ account: str
+ jid: JID
+ event: str
+ timestamp: float
+ data: dict[str, Any]
+
+
+class EventStorage(SqliteStorage):
+ def __init__(self):
+ SqliteStorage.__init__(self,
+ log,
+ None,
+ EVENTS_SQL_STATEMENT)
+
+ def init(self, **kwargs: Any) -> None:
+ SqliteStorage.init(self,
+ detect_types=sqlite3.PARSE_COLNAMES)
+ self._con.row_factory = self._namedtuple_factory
+
+ @staticmethod
+ def _namedtuple_factory(cursor: sqlite3.Cursor,
+ row: tuple[Any, ...]) -> NamedTuple:
+
+ assert cursor.description is not None
+ fields = [col[0] for col in cursor.description]
+ Row = namedtuple('Row', fields) # type: ignore
+ return Row(*row)
+
+ def _migrate(self) -> None:
+ pass
+
+ def store(self,
+ contact: ChatContactT,
+ event: Any
+ ) -> None:
+
+ event_dict = dataclasses.asdict(event)
+ name = event_dict.pop('name')
+ timestamp = event_dict.pop('timestamp')
+
+ insert_sql = '''
+ INSERT INTO events(account, jid, event, timestamp, data)
+ VALUES(?, ?, ?, ?, ?)'''
+
+ self._con.execute(insert_sql, (contact.account,
+ contact.jid,
+ name,
+ timestamp,
+ json.dumps(event_dict, cls=Encoder)))
+
+ def load(self,
+ contact: ChatContactT,
+ before: bool,
+ timestamp: float
+ ) -> list[EventRow]:
+
+ time_operator = '<' if before else '>'
+
+ insert_sql = '''
+ SELECT account, jid, event, timestamp, data as "data [JSON]"
+ FROM events WHERE account=? AND jid=? AND timestamp %s ?
+ ''' % time_operator
+
+ for row in self._con.execute(insert_sql, (contact.account,
+ contact.jid,
+ timestamp)).fetchall():
+ event_class = EVENT_CLASSES[row.event]
+ yield event_class(**row.data, timestamp=row.timestamp)
diff --git a/gajim/gtk/chat_banner.py b/gajim/gtk/chat_banner.py
index a6d48ed31..8e010c18d 100644
--- a/gajim/gtk/chat_banner.py
+++ b/gajim/gtk/chat_banner.py
@@ -21,8 +21,6 @@ from gi.repository import Gtk
import cairo
-from nbxmpp.structs import PresenceProperties
-
from gajim.common import app
from gajim.common import ged
from gajim.common import types
@@ -184,13 +182,7 @@ class ChatBanner(Gtk.Box, EventHelper):
if contact.is_joined:
self._update_content()
- def _on_user_role_changed(self,
- _contact: GroupchatContact,
- _signal_name: str,
- user_contact: GroupchatParticipant,
- properties: PresenceProperties
- ) -> None:
-
+ def _on_user_role_changed(self, *args: Any) -> None:
self._update_content()
def _on_user_state_changed(self, *args: Any) -> None:
diff --git a/gajim/gtk/chat_stack.py b/gajim/gtk/chat_stack.py
index 21faf766e..c10327b11 100644
--- a/gajim/gtk/chat_stack.py
+++ b/gajim/gtk/chat_stack.py
@@ -28,7 +28,6 @@ from gi.repository import Gtk
from nbxmpp.errors import StanzaError
from nbxmpp.protocol import JID
from nbxmpp.structs import MessageProperties
-from nbxmpp.structs import PresenceProperties
from gajim.common import app
from gajim.common import events
@@ -297,23 +296,25 @@ class ChatStack(Gtk.Stack, EventHelper):
contact: GroupchatContact,
_signal_name: str,
_user_contact: GroupchatParticipant,
- _properties: PresenceProperties
+ _event: events.MUCUserJoined
) -> None:
+
self._update_group_chat_actions(contact)
def _on_user_role_changed(self,
contact: GroupchatContact,
_signal_name: str,
_user_contact: GroupchatParticipant,
- _properties: PresenceProperties
+ _event: events.MUCUserRoleChanged
) -> None:
+
self._update_group_chat_actions(contact)
def _on_user_affiliation_changed(self,
contact: GroupchatContact,
_signal_name: str,
_user_contact: GroupchatParticipant,
- _properties: PresenceProperties
+ _event: events.MUCUserAffiliationChanged
) -> None:
self._update_group_chat_actions(contact)
diff --git a/gajim/gtk/control.py b/gajim/gtk/control.py
index 6236b4ef6..705d848cc 100644
--- a/gajim/gtk/control.py
+++ b/gajim/gtk/control.py
@@ -19,9 +19,6 @@ from typing import Optional
from typing import Union
from typing import cast
-from collections import defaultdict
-from collections import deque
-from functools import partial
import os
import logging
import time
@@ -105,12 +102,6 @@ class ChatControl(EventHelper):
self._muc_subjects: dict[
types.ChatContactT, tuple[float, MucSubject]] = {}
- # Store 100 info messages per contact on a FIFO basis
- self._info_messages: dict[
- types.ChatContactT,
- deque[tuple[float, str]]] = defaultdict(
- partial(deque, maxlen=100)) # pyright: ignore
-
self.widget = cast(Gtk.Box, self._ui.get_object('control_box'))
self.widget.show_all()
@@ -212,9 +203,6 @@ class ChatControl(EventHelper):
for transfer in transfers:
self.add_file_transfer(transfer)
- for timestamp, message in self._info_messages[contact]:
- self.add_info_message(message, timestamp)
-
if isinstance(contact, GroupchatContact):
if (app.settings.get('show_subject_on_join') or
not contact.is_joining):
@@ -515,10 +503,6 @@ class ChatControl(EventHelper):
timestamp: Optional[float] = None
) -> None:
- if timestamp is None:
- assert self._contact is not None
- self._info_messages[self._contact].append((time.time(), text))
-
self.conversation_view.add_info_message(text, timestamp)
def add_file_transfer(self, transfer: HTTPFileTransfer) -> None:
@@ -639,6 +623,42 @@ class ChatControl(EventHelper):
# if self.conversation_view.reduce_message_count(before):
# self._scrolled_view.set_history_complete(before, False)
+ for event in app.storage.events.load(self._contact, before, timestamp):
+ if isinstance(event, events.MUCUserJoined):
+ self._process_muc_user_joined(event)
+
+ elif isinstance(event, events.MUCUserLeft):
+ self._process_muc_user_left(event)
+
+ elif isinstance(event, events.MUCNicknameChanged):
+ self._process_muc_nickname_changed(event)
+
+ elif isinstance(event, events.MUCRoomKicked):
+ self._process_muc_room_kicked(event)
+
+ elif isinstance(event, events.MUCUserAffiliationChanged):
+ self._process_muc_user_affiliation_changed(event)
+
+ elif isinstance(event, events.MUCUserRoleChanged):
+ self._process_muc_user_role_changed(event)
+
+ elif isinstance(event, events.MUCUserStatusShowChanged):
+ self._process_muc_user_status_show_changed(event)
+
+ elif isinstance(event, events.MUCRoomConfigChanged):
+ self._process_muc_room_config_changed(event)
+
+ elif isinstance(event, events.MUCRoomConfigFinished):
+ self._process_muc_room_config_finished(event)
+
+ elif isinstance(event, events.MUCRoomPresenceError):
+ self._process_muc_room_presence_error(event)
+
+ elif isinstance(event, events.MUCRoomDestroyed):
+ self._process_muc_room_destroyed(event)
+ else:
+ raise ValueError('Unknown event: %s' % event)
+
self._scrolled_view.block_signals(False)
def add_messages(self, messages: list[ConversationRow]):
@@ -723,143 +743,213 @@ class ChatControl(EventHelper):
additional_data=additional_data)
def _on_user_nickname_changed(self,
- contact: types.GroupchatContact,
+ _contact: types.GroupchatContact,
_signal_name: str,
- old_contact: types.GroupchatParticipant,
- new_contact: types.GroupchatParticipant,
- properties: PresenceProperties
+ event: events.MUCNicknameChanged,
+ _old_contact: types.GroupchatParticipant,
+ _new_contact: types.GroupchatParticipant
) -> None:
- if properties.is_muc_self_presence:
- message = _('You are now known as %s') % new_contact.name
+ self._process_muc_nickname_changed(event)
+
+ def _process_muc_nickname_changed(self,
+ event: events.MUCNicknameChanged
+ ) -> None:
+
+ if event.is_self:
+ message = _('You are now known as %s') % event.new_name
else:
message = _('{nick} is now known '
- 'as {new_nick}').format(nick=old_contact.name,
- new_nick=new_contact.name)
+ 'as {new_nick}').format(nick=event.old_name,
+ new_nick=event.new_name)
+ self.add_info_message(message, event.timestamp)
- self.add_info_message(message)
+ def _on_room_kicked(self,
+ _contact: GroupchatContact,
+ _signal_name: str,
+ event: events.MUCRoomKicked
+ ) -> None:
- def _on_muc_user_status_show_changed(self,
- contact: GroupchatContact,
- _signal_name: str,
- user_contact: GroupchatParticipant,
- properties: PresenceProperties
- ) -> None:
+ self._process_muc_room_kicked(event)
- if not contact.settings.get('print_status'):
+ def _process_muc_room_kicked(self, event: events.MUCRoomKicked) -> None:
+ status_codes = event.status_codes or []
+
+ reason = event.reason
+ reason = '' if reason is None else f': {reason}'
+
+ actor = event.actor
+ # Group Chat: You have been kicked by Alice
+ actor = '' if actor is None else _(' by {actor}').format(
+ actor=actor)
+
+ # Group Chat: We have been removed from the room by Alice: reason
+ message = _('You have been removed from the '
+ 'group chat{actor}{reason}')
+
+ if StatusCode.REMOVED_ERROR in status_codes:
+ # Handle 333 before 307, some MUCs add both
+ # Group Chat: Server kicked us because of an server error
+ message = _('You have left due '
+ 'to an error{reason}').format(reason=reason)
+
+ elif StatusCode.REMOVED_KICKED in status_codes:
+ # Group Chat: We have been kicked by Alice: reason
+ message = _('You have been '
+ 'kicked{actor}{reason}').format(actor=actor,
+ reason=reason)
+
+ elif StatusCode.REMOVED_BANNED in status_codes:
+ # Group Chat: We have been banned by Alice: reason
+ message = _('You have been '
+ 'banned{actor}{reason}').format(actor=actor,
+ reason=reason)
+
+ elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
+ # Group Chat: We were removed because of an affiliation change
+ reason = _(': Affiliation changed')
+ message = message.format(actor=actor, reason=reason)
+
+ elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
+ # Group Chat: Room configuration changed
+ reason = _(': Group chat configuration changed to members-only')
+ message = message.format(actor=actor, reason=reason)
+
+ elif StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
+ # Group Chat: Kicked because of server shutdown
+ reason = ': System shutdown'
+ message = message.format(actor=actor, reason=reason)
+
+ else:
+ # No formatted message available
return
- self.conversation_view.add_user_status(user_contact.name,
- user_contact.show.value,
- user_contact.status)
+ self.add_info_message(message, event.timestamp)
- def _on_user_status_show_changed(self,
- _user_contact: GroupchatParticipant,
+ def _on_user_affiliation_changed(self,
+ _contact: GroupchatContact,
_signal_name: str,
- properties: PresenceProperties
+ user_contact: GroupchatParticipant,
+ event: events.MUCUserAffiliationChanged
) -> None:
- nick = properties.muc_nickname
- status = properties.status
- status = '' if not status else f' - {status}'
- assert properties.show is not None
- show = helpers.get_uf_show(properties.show.value)
+ self._process_muc_user_affiliation_changed(event)
- assert isinstance(self.contact, GroupchatParticipant)
- if not self.contact.room.settings.get('print_status'):
- return
+ def _process_muc_user_affiliation_changed(
+ self,
+ event: events.MUCUserAffiliationChanged) -> None:
- if properties.is_muc_self_presence:
- message = _('You are now {show}{status}').format(show=show,
- status=status)
+ affiliation = helpers.get_uf_affiliation(event.affiliation)
+
+ reason = event.reason
+ reason = '' if reason is None else f': {reason}'
+
+ actor = event.actor
+ # Group Chat: You have been kicked by Alice
+ actor = '' if actor is None else _(' by {actor}').format(
+ actor=actor)
+ if event.is_self:
+ message = _('** Your Affiliation has been set to '
+ '{affiliation}{actor}{reason}').format(
+ affiliation=affiliation,
+ actor=actor,
+ reason=reason)
else:
- message = _('{nick} is now {show}{status}').format(nick=nick,
- show=show,
- status=status)
- self.add_info_message(message)
+ message = _('** Affiliation of {nick} has been set to '
+ '{affiliation}{actor}{reason}').format(
+ nick=event.nick,
+ affiliation=affiliation,
+ actor=actor,
+ reason=reason)
- def _on_room_voice_request(self,
- _contact: GroupchatContact,
- _signal_name: str,
- properties: MessageProperties
- ) -> None:
- voice_request = properties.voice_request
- assert voice_request is not None
+ self.add_info_message(message, event.timestamp)
- def on_approve() -> None:
- self.client.get_module('MUC').approve_voice_request(
- self.contact.jid, voice_request)
+ def _on_user_role_changed(self,
+ _contact: GroupchatContact,
+ _signal_name: str,
+ user_contact: GroupchatParticipant,
+ event: events.MUCUserRoleChanged
+ ) -> None:
- ConfirmationDialog(
- _('Voice Request'),
- _('Voice Request'),
- _('<b>%(nick)s</b> from <b>%(room_name)s</b> requests voice') % {
- 'nick': voice_request.nick, 'room_name': self.contact.name},
- [DialogButton.make('Cancel'),
- DialogButton.make('Accept',
- text=_('_Approve'),
- callback=on_approve)],
- modal=False).show()
+ self._process_muc_user_role_changed(event)
- def add_muc_message(self,
- text: str,
- tim: float,
- contact: str = '',
- displaymarking: Optional[Displaymarking] = None,
- message_id: Optional[str] = None,
- stanza_id: Optional[str] = None,
- additional_data: Optional[AdditionalDataDict] = None,
- ) -> None:
+ def _process_muc_user_role_changed(self,
+ event: events.MUCUserRoleChanged
+ ) -> None:
- assert isinstance(self._contact, GroupchatContact)
+ role = helpers.get_uf_role(event.role)
+ nick = event.nick
- if contact == self._contact.nickname:
- kind = 'outgoing'
+ reason = event.reason
+ reason = '' if reason is None else f': {reason}'
+
+ actor = event.actor
+ # Group Chat: You have been kicked by Alice
+ actor = '' if actor is None else _(' by {actor}').format(actor=actor)
+
+ if event.is_self:
+ message = _('** Your Role has been set to '
+ '{role}{actor}{reason}').format(role=role,
+ actor=actor,
+ reason=reason)
else:
- kind = 'incoming'
- # muc-specific chatstate
+ message = _('** Role of {nick} has been set to '
+ '{role}{actor}{reason}').format(nick=nick,
+ role=role,
+ actor=actor,
+ reason=reason)
+ self.add_info_message(message, event.timestamp)
- self._add_message(text,
- kind,
- contact,
- tim,
- displaymarking=displaymarking,
- message_id=message_id,
- stanza_id=stanza_id,
- additional_data=additional_data)
+ def _on_user_status_show_changed(self,
+ _user_contact: GroupchatParticipant,
+ _signal_name: str,
+ event: events.MUCUserStatusShowChanged
+ ) -> None:
- def _on_room_subject(self,
- contact: GroupchatContact,
- _signal_name: str,
- subject: Optional[MucSubject]
- ) -> None:
+ self._process_muc_user_status_show_changed(event)
- if subject is None:
- return
+ def _process_muc_user_status_show_changed(
+ self,
+ event: events.MUCUserStatusShowChanged) -> None:
- _timestamp, old_subject = self._muc_subjects.get(contact, (None, None))
- if old_subject is not None and old_subject.text == subject.text:
- # Probably a rejoin, we already showed that subject
+ nick = event.nick
+ status = event.status
+ status = '' if not status else f' - {status}'
+ show = helpers.get_uf_show(event.show_value)
+
+ assert isinstance(self.contact, GroupchatParticipant)
+ if not self.contact.room.settings.get('print_status'):
return
- self._muc_subjects[contact] = (time.time(), subject)
+ if event.is_self:
+ message = _('You are now {show}{status}').format(show=show,
+ status=status)
- if (app.settings.get('show_subject_on_join') or
- not contact.is_joining):
- self.conversation_view.add_muc_subject(subject)
+ else:
+ message = _('{nick} is now {show}{status}').format(
+ nick=nick,
+ show=show,
+ status=status)
+
+ self.add_info_message(message, event.timestamp)
def _on_room_config_changed(self,
_contact: GroupchatContact,
_signal_name: str,
- properties: MessageProperties
+ event: events.MUCRoomConfigChanged
) -> None:
- # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
- status_codes = properties.muc_status_codes
- assert status_codes is not None
+ self._process_muc_room_config_changed(event)
+
+ def _process_muc_room_config_changed(self,
+ event: events.MUCRoomConfigChanged
+ ) -> None:
+ # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
+ status_codes = event.status_codes
changes: list[str] = []
+
if StatusCode.SHOWING_UNAVAILABLE in status_codes:
changes.append(_('Group chat now shows unavailable members'))
@@ -873,7 +963,8 @@ class ChatControl(EventHelper):
self.client.get_module('Discovery').disco_muc(self.contact.jid)
if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
- # Can be a presence (see chg_contact_status in groupchat_control.py)
+ # Can be a presence
+ # (see chg_contact_status in groupchat_control.py)
changes.append(_('Conversations are stored on the server'))
if StatusCode.CONFIG_NO_ROOM_LOGGING in status_codes:
@@ -888,194 +979,130 @@ class ChatControl(EventHelper):
if StatusCode.CONFIG_FULL_ANONYMOUS in status_codes:
changes.append(_('Group chat is now fully anonymous'))
- for change in changes:
- self.add_info_message(change)
-
- def rejoin(self) -> None:
- self.client.get_module('MUC').join(self.contact.jid)
-
- def _on_user_joined(self,
- contact: GroupchatContact,
- _signal_name: str,
- user_contact: GroupchatParticipant,
- properties: PresenceProperties
- ) -> None:
-
- nick = user_contact.name
- if not properties.is_muc_self_presence:
- if contact.is_joined:
- self.conversation_view.add_muc_user_joined(nick)
- return
-
- status_codes = properties.muc_status_codes or []
-
- if not contact.is_joined:
- # We just joined the room
- self.add_info_message(_('You (%s) joined the group chat') % nick)
-
- if StatusCode.NON_ANONYMOUS in status_codes:
- self.add_info_message(
- _('Any participant is allowed to see your full XMPP Address'))
-
- if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
- self.add_info_message(_('Conversations are stored on the server'))
-
- if StatusCode.NICKNAME_MODIFIED in status_codes:
- self.add_info_message(
- _('The server has assigned or modified your nickname in this '
- 'group chat'))
+ for message in changes:
+ self.add_info_message(message, event.timestamp)
def _on_room_config_finished(self,
_contact: GroupchatContact,
- _signal_name: str
+ _signal_name: str,
+ event: events.MUCRoomConfigFinished
) -> None:
+ self._process_muc_room_config_finished(event)
+
+ def _process_muc_room_config_finished(self,
+ event: events.MUCRoomConfigFinished
+ ) -> None:
+
self.add_info_message(_('A new group chat has been created'))
- def _on_user_affiliation_changed(self,
- _contact: GroupchatContact,
- _signal_name: str,
- user_contact: GroupchatParticipant,
- properties: PresenceProperties
- ) -> None:
- affiliation = helpers.get_uf_affiliation(user_contact.affiliation)
- nick = user_contact.name
+ def _on_room_presence_error(self,
+ _contact: GroupchatContact,
+ _signal_name: str,
+ event: events.MUCRoomPresenceError
+ ) -> None:
- assert properties.muc_user is not None
- reason = properties.muc_user.reason
- reason = '' if reason is None else f': {reason}'
+ self._process_muc_room_presence_error(event)
- actor = properties.muc_user.actor
- # Group Chat: You have been kicked by Alice
- actor = '' if actor is None else _(' by {actor}').format(actor=actor)
+ def _process_muc_room_presence_error(self,
+ event: events.MUCRoomPresenceError
+ ) -> None:
- if properties.is_muc_self_presence:
- message = _('** Your Affiliation has been set to '
- '{affiliation}{actor}{reason}').format(
- affiliation=affiliation,
- actor=actor,
- reason=reason)
- else:
- message = _('** Affiliation of {nick} has been set to '
- '{affiliation}{actor}{reason}').format(
- nick=nick,
- affiliation=affiliation,
- actor=actor,
- reason=reason)
+ error_message = to_user_string(event.error)
+ self.add_info_message(_('Error: %s') % error_message)
- self.add_info_message(message)
+ def _on_room_destroyed(self,
+ _contact: GroupchatContact,
+ _signal_name: str,
+ event: events.MUCRoomDestroyed
+ ) -> None:
- def _on_user_role_changed(self,
- _contact: GroupchatContact,
- _signal_name: str,
- user_contact: GroupchatParticipant,
- properties: PresenceProperties
- ) -> None:
- role = helpers.get_uf_role(user_contact.role)
- nick = user_contact.name
+ self._process_muc_room_destroyed(event)
+
+ def _process_muc_room_destroyed(self,
+ event: events.MUCRoomDestroyed
+ ) -> None:
- assert properties.muc_user is not None
- reason = properties.muc_user.reason
+ reason = event.reason
reason = '' if reason is None else f': {reason}'
- actor = properties.muc_user.actor
- # Group Chat: You have been kicked by Alice
- actor = '' if actor is None else _(' by {actor}').format(actor=actor)
+ message = _('Group chat has been destroyed')
- if properties.is_muc_self_presence:
- message = _('** Your Role has been set to '
- '{role}{actor}{reason}').format(role=role,
- actor=actor,
- reason=reason)
- else:
- message = _('** Role of {nick} has been set to '
- '{role}{actor}{reason}').format(nick=nick,
- role=role,
- actor=actor,
- reason=reason)
+ if event.alternate is not None:
+ message += '\n' + _('You can join this group chat instead: '
+ 'xmpp:%s?join') % str(event.alternate)
- self.add_info_message(message)
+ self.add_info_message(message, event.timestamp)
- def _on_room_kicked(self,
+ def _on_user_joined(self,
_contact: GroupchatContact,
_signal_name: str,
- properties: MessageProperties
+ _user_contact: GroupchatParticipant,
+ event: events.MUCUserJoined
) -> None:
- status_codes = properties.muc_status_codes or []
- assert properties.muc_user is not None
- reason = properties.muc_user.reason
- reason = '' if reason is None else f': {reason}'
+ self._process_muc_user_joined(event)
- actor = properties.muc_user.actor
- # Group Chat: You have been kicked by Alice
- actor = '' if actor is None else _(' by {actor}').format(actor=actor)
+ def _process_muc_user_joined(self, event: events.MUCUserJoined) -> None:
+ assert isinstance(self.contact, GroupchatContact)
- # Group Chat: We have been removed from the room by Alice: reason
- message = _('You have been removed from the group chat{actor}{reason}')
+ if not event.is_self:
+ if self.contact.is_joined:
+ self.conversation_view.add_muc_user_joined(event.nick)
+ return
- if StatusCode.REMOVED_ERROR in status_codes:
- # Handle 333 before 307, some MUCs add both
- # Group Chat: Server kicked us because of an server error
- message = _('You have left due '
- 'to an error{reason}').format(reason=reason)
- self.add_info_message(message)
+ status_codes = event.status_codes or []
- elif StatusCode.REMOVED_KICKED in status_codes:
- # Group Chat: We have been kicked by Alice: reason
- message = _('You have been '
- 'kicked{actor}{reason}').format(actor=actor,
- reason=reason)
- self.add_info_message(message)
+ message = None
+ if not self.contact.is_joined:
+ # We just joined the room
+ message = _('You (%s) joined the group chat') % event.nick
- elif StatusCode.REMOVED_BANNED in status_codes:
- # Group Chat: We have been banned by Alice: reason
- message = _('You have been '
- 'banned{actor}{reason}').format(actor=actor,
- reason=reason)
- self.add_info_message(message)
+ if StatusCode.NON_ANONYMOUS in status_codes:
+ message = _('Any participant is allowed to see your full '
+ 'XMPP Address')
- elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
- # Group Chat: We were removed because of an affiliation change
- reason = _(': Affiliation changed')
- message = message.format(actor=actor, reason=reason)
- self.add_info_message(message)
+ if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
+ message = _('Conversations are stored on the server')
- elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
- # Group Chat: Room configuration changed
- reason = _(': Group chat configuration changed to members-only')
- message = message.format(actor=actor, reason=reason)
- self.add_info_message(message)
+ if StatusCode.NICKNAME_MODIFIED in status_codes:
+ message = _('The server has assigned or modified your '
+ 'nickname in this group chat')
- elif StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
- # Group Chat: Kicked because of server shutdown
- reason = ': System shutdown'
- message = message.format(actor=actor, reason=reason)
- self.add_info_message(message)
+ if message is not None:
+ self.add_info_message(message, event.timestamp)
def _on_user_left(self,
_contact: GroupchatContact,
_signal_name: str,
- user_contact: GroupchatParticipant,
- properties: MessageProperties
+ _user_contact: GroupchatParticipant,
+ event: events.MUCUserLeft
) -> None:
- status_codes = properties.muc_status_codes or []
- nick = user_contact.name
- assert properties.muc_user is not None
- reason = properties.muc_user.reason
+ self._process_muc_user_left(event)
+
+ def _process_muc_user_left(self, event: events.MUCUserLeft) -> None:
+ if event.is_self:
+ return
+
+ status_codes = event.status_codes or []
+ nick = event.nick
+
+ reason = event.reason
reason = '' if reason is None else f': {reason}'
- actor = properties.muc_user.actor
+ actor = event.actor
# Group Chat: You have been kicked by Alice
- actor = '' if actor is None else _(' by {actor}').format(actor=actor)
+ actor = '' if actor is None else _(' by {actor}').format(
+ actor=actor)
# Group Chat: We have been removed from the room
- message = _('{nick} has been removed from the group chat{by}{reason}')
+ message = _('{nick} has been removed from the group '
+ 'chat{by}{reason}')
if StatusCode.REMOVED_ERROR in status_codes:
# Handle 333 before 307, some MUCs add both
self.conversation_view.add_muc_user_left(
- nick, properties.muc_user.reason, error=True)
+ nick, event.reason, error=True)
elif StatusCode.REMOVED_KICKED in status_codes:
# Group Chat: User was kicked by Alice: reason
@@ -1083,7 +1110,6 @@ class ChatControl(EventHelper):
'kicked{actor}{reason}').format(nick=nick,
actor=actor,
reason=reason)
- self.add_info_message(message)
elif StatusCode.REMOVED_BANNED in status_codes:
# Group Chat: User was banned by Alice: reason
@@ -1091,49 +1117,104 @@ class ChatControl(EventHelper):
'banned{actor}{reason}').format(nick=nick,
actor=actor,
reason=reason)
- self.add_info_message(message)
elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
reason = _(': Affiliation changed')
message = message.format(nick=nick, by=actor, reason=reason)
- self.add_info_message(message)
elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
reason = _(': Group chat configuration changed to members-only')
message = message.format(nick=nick, by=actor, reason=reason)
- self.add_info_message(message)
else:
- self.conversation_view.add_muc_user_left(
- nick, properties.muc_user.reason)
+ self.conversation_view.add_muc_user_left(nick, event.reason)
+ return
- def _on_room_presence_error(self,
- _contact: GroupchatContact,
- _signal_name: str,
- properties: PresenceProperties
- ) -> None:
+ self.add_info_message(message, event.timestamp)
- assert properties.error is not None
- error_message = to_user_string(properties.error)
- self.add_info_message(_('Error: %s') % error_message)
+ def _on_muc_user_status_show_changed(self,
+ contact: GroupchatContact,
+ _signal_name: str,
+ user_contact: GroupchatParticipant,
+ properties: PresenceProperties
+ ) -> None:
- def _on_room_destroyed(self,
- _contact: GroupchatContact,
- _signal_name: str,
- properties: PresenceProperties
- ) -> None:
+ if not contact.settings.get('print_status'):
+ return
- destroyed = properties.muc_destroyed
- assert destroyed is not None
+ self.conversation_view.add_user_status(user_contact.name,
+ user_contact.show.value,
+ user_contact.status)
- reason = destroyed.reason
- reason = '' if reason is None else f': {reason}'
+ def _on_room_voice_request(self,
+ _contact: GroupchatContact,
+ _signal_name: str,
+ properties: MessageProperties
+ ) -> None:
+ voice_request = properties.voice_request
+ assert voice_request is not None
- message = _('Group chat has been destroyed')
- self.add_info_message(message)
+ def on_approve() -> None:
+ self.client.get_module('MUC').approve_voice_request(
+ self.contact.jid, voice_request)
+
+ ConfirmationDialog(
+ _('Voice Request'),
+ _('Voice Request'),
+ _('<b>%(nick)s</b> from <b>%(room_name)s</b> requests voice') % {
+ 'nick': voice_request.nick, 'room_name': self.contact.name},
+ [DialogButton.make('Cancel'),
+ DialogButton.make('Accept',
+ text=_('_Approve'),
+ callback=on_approve)],
+ modal=False).show()
- alternate = destroyed.alternate
- if alternate is not None:
- join_message = _('You can join this group chat '
- 'instead: xmpp:%s?join') % str(alternate)
- self.add_info_message(join_message)
+ def add_muc_message(self,
+ text: str,
+ tim: float,
+ contact: str = '',
+ displaymarking: Optional[Displaymarking] = None,
+ message_id: Optional[str] = None,
+ stanza_id: Optional[str] = None,
+ additional_data: Optional[AdditionalDataDict] = None,
+ ) -> None:
+
+ assert isinstance(self._contact, GroupchatContact)
+
+ if contact == self._contact.nickname:
+ kind = 'outgoing'
+ else:
+ kind = 'incoming'
+ # muc-specific chatstate
+
+ self._add_message(text,
+ kind,
+ contact,
+ tim,
+ displaymarking=displaymarking,
+ message_id=message_id,
+ stanza_id=stanza_id,
+ additional_data=additional_data)
+
+ def _on_room_subject(self,
+ contact: GroupchatContact,
+ _signal_name: str,
+ subject: Optional[MucSubject]
+ ) -> None:
+
+ if subject is None:
+ return
+
+ _timestamp, old_subject = self._muc_subjects.get(contact, (None, None))
+ if old_subject is not None and old_subject.text == subject.text:
+ # Probably a rejoin, we already showed that subject
+ return
+
+ self._muc_subjects[contact] = (time.time(), subject)
+
+ if (app.settings.get('show_subject_on_join') or
+ not contact.is_joining):
+ self.conversation_view.add_muc_subject(subject)
+
+ def rejoin(self) -> None:
+ self.client.get_module('MUC').join(self.contact.jid)
diff --git a/gajim/gtk/conversation/view.py b/gajim/gtk/conversation/view.py
index 8c2f170f0..7808c3bc1 100644
--- a/gajim/gtk/conversation/view.py
+++ b/gajim/gtk/conversation/view.py
@@ -176,7 +176,7 @@ class ConversationView(Gtk.ListBox):
def add_muc_user_left(self,
nick: str,
- reason: str,
+ reason: Optional[str],
error: bool = False) -> None:
assert isinstance(self._contact, GroupchatContact)
if not self._contact.settings.get('print_join_left'):
diff --git a/gajim/gtk/groupchat_nick_completion.py b/gajim/gtk/groupchat_nick_completion.py
index fceb49bc4..89513aca1 100644
--- a/gajim/gtk/groupchat_nick_completion.py
+++ b/gajim/gtk/groupchat_nick_completion.py
@@ -18,7 +18,6 @@ from typing import Optional
import logging
-from nbxmpp.structs import PresenceProperties
from gi.repository import Gtk
from gi.repository import Gdk
@@ -27,6 +26,7 @@ from gajim.common import app
from gajim.common import ged
from gajim.common import types
from gajim.common.events import GcMessageReceived
+from gajim.common.events import MUCNicknameChanged
from gajim.common.ged import EventHelper
from gajim.common.helpers import jid_is_blocked
from gajim.common.helpers import message_needs_highlight
@@ -76,12 +76,12 @@ class GroupChatNickCompletion(EventHelper):
def _on_user_nickname_changed(self,
_contact: types.GroupchatContact,
_signal_name: str,
+ event: MUCNicknameChanged,
old_contact: types.GroupchatParticipant,
- new_contact: types.GroupchatParticipant,
- properties: PresenceProperties
+ new_contact: types.GroupchatParticipant
) -> None:
- if properties.is_muc_self_presence:
+ if event.is_self:
return
old_name = old_contact.name
diff --git a/gajim/gtk/groupchat_roster.py b/gajim/gtk/groupchat_roster.py
index 37365aa44..6a067d62c 100644
--- a/gajim/gtk/groupchat_roster.py
+++ b/gajim/gtk/groupchat_roster.py
@@ -25,7 +25,6 @@ from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GLib
from nbxmpp.const import Affiliation
-from nbxmpp.structs import PresenceProperties
from gajim.common import app
from gajim.common import ged
@@ -36,6 +35,7 @@ from gajim.common.helpers import jid_is_blocked
from gajim.common.const import AvatarSize
from gajim.common.const import StyleAttr
from gajim.common.events import ApplicationEvent
+from gajim.common.events import MUCNicknameChanged
from gajim.common.modules.contacts import GroupchatContact
from .menus import get_groupchat_roster_menu
@@ -281,11 +281,11 @@ class GroupchatRoster(Gtk.Revealer, EventHelper):
self._add_contact(user_contact)
def _on_user_nickname_changed(self,
- contact: types.GroupchatContact,
+ _contact: types.GroupchatContact,
_signal_name: str,
+ _event: MUCNicknameChanged,
old_contact: types.GroupchatParticipant,
- new_contact: types.GroupchatParticipant,
- properties: PresenceProperties
+ new_contact: types.GroupchatParticipant
) -> None:
self._remove_contact(old_contact)