Philipp Hörist pushed to branch archive_cache at gajim / gajim
Commits:
-
307b7baf
by wurstsalat at 2022-08-29T11:59:48+02:00
-
5de6f7ea
by lovetox at 2022-08-31T21:46:40+02:00
19 changed files:
- gajim/common/app.py
- gajim/common/application.py
- gajim/common/events.py
- gajim/common/modules/contacts.py
- gajim/common/modules/muc.py
- gajim/common/storage/base.py
- + gajim/common/storage/events.py
- gajim/gtk/chat_banner.py
- gajim/gtk/chat_list_stack.py
- gajim/gtk/chat_stack.py
- gajim/gtk/control.py
- gajim/gtk/conversation/view.py
- gajim/gtk/groupchat_nick_completion.py
- gajim/gtk/groupchat_roster.py
- gajim/gtk/main.py
- gajim/gtk/menus.py
- gajim/gtk/structs.py
- gajim/gtk/workspace_dialog.py
- gajim/gtk/workspace_side_bar.py
Changes:
| ... | ... | @@ -59,6 +59,7 @@ |
| 59 | 59 | from gajim.gui.application import GajimApplication
|
| 60 | 60 | from gajim.common.storage.cache import CacheStorage
|
| 61 | 61 | from gajim.common.storage.archive import MessageArchiveStorage
|
| 62 | + from gajim.common.storage.events import EventStorage
|
|
| 62 | 63 | from gajim.common.cert_store import CertificateStore
|
| 63 | 64 | from gajim.common.call_manager import CallManager
|
| 64 | 65 | from gajim.common.preview import PreviewManager
|
| ... | ... | @@ -88,6 +89,7 @@ class Storage: |
| 88 | 89 | def __init__(self):
|
| 89 | 90 | self.cache: CacheStorage = None
|
| 90 | 91 | self.archive: MessageArchiveStorage = None
|
| 92 | + self.events: EventStorage = None
|
|
| 91 | 93 | |
| 92 | 94 | |
| 93 | 95 | storage = Storage()
|
| ... | ... | @@ -44,6 +44,7 @@ |
| 44 | 44 | from gajim.common.events import GajimUpdateAvailable
|
| 45 | 45 | from gajim.common.client import Client
|
| 46 | 46 | from gajim.common.helpers import make_http_request
|
| 47 | +from gajim.common.storage.events import EventStorage
|
|
| 47 | 48 | from gajim.common.task_manager import TaskManager
|
| 48 | 49 | from gajim.common.settings import Settings
|
| 49 | 50 | from gajim.common.settings import LegacyConfig
|
| ... | ... | @@ -74,6 +75,9 @@ def _init_core(self) -> None: |
| 74 | 75 | app.storage.cache = CacheStorage()
|
| 75 | 76 | app.storage.cache.init()
|
| 76 | 77 | |
| 78 | + app.storage.events = EventStorage()
|
|
| 79 | + app.storage.events.init()
|
|
| 80 | + |
|
| 77 | 81 | app.storage.archive = MessageArchiveStorage()
|
| 78 | 82 | app.storage.archive.init()
|
| 79 | 83 |
| ... | ... | @@ -32,7 +32,10 @@ |
| 32 | 32 | from nbxmpp.structs import LocationData
|
| 33 | 33 | from nbxmpp.structs import RosterItem
|
| 34 | 34 | from nbxmpp.structs import TuneData
|
| 35 | +from nbxmpp.const import Affiliation
|
|
| 35 | 36 | from nbxmpp.const import InviteType
|
| 37 | +from nbxmpp.const import Role
|
|
| 38 | +from nbxmpp.const import StatusCode
|
|
| 36 | 39 | |
| 37 | 40 | from gajim.common.file_props import FileProp
|
| 38 | 41 | from gajim.common.const import JingleState
|
| ... | ... | @@ -676,3 +679,101 @@ class AllowGajimUpdateCheck(ApplicationEvent): |
| 676 | 679 | class GajimUpdateAvailable(ApplicationEvent):
|
| 677 | 680 | name: str = field(init=False, default='gajim-update-available')
|
| 678 | 681 | version: str
|
| 682 | + |
|
| 683 | + |
|
| 684 | +@dataclass
|
|
| 685 | +class MUCNicknameChanged(ApplicationEvent):
|
|
| 686 | + name: str = field(init=False, default='muc-nickname-changed')
|
|
| 687 | + is_self: bool
|
|
| 688 | + new_name: str
|
|
| 689 | + old_name: str
|
|
| 690 | + timestamp: float
|
|
| 691 | + |
|
| 692 | + |
|
| 693 | +@dataclass
|
|
| 694 | +class MUCRoomConfigChanged(ApplicationEvent):
|
|
| 695 | + name: str = field(init=False, default='muc-room-config-changed')
|
|
| 696 | + timestamp: float
|
|
| 697 | + status_codes: set[StatusCode]
|
|
| 698 | + |
|
| 699 | + |
|
| 700 | +@dataclass
|
|
| 701 | +class MUCRoomConfigFinished(ApplicationEvent):
|
|
| 702 | + name: str = field(init=False, default='muc-room-config-finished')
|
|
| 703 | + timestamp: float
|
|
| 704 | + |
|
| 705 | + |
|
| 706 | +@dataclass
|
|
| 707 | +class MUCRoomPresenceError(ApplicationEvent):
|
|
| 708 | + name: str = field(init=False, default='muc-room-presence-error')
|
|
| 709 | + timestamp: float
|
|
| 710 | + error: Any
|
|
| 711 | + |
|
| 712 | + |
|
| 713 | +@dataclass
|
|
| 714 | +class MUCRoomKicked(ApplicationEvent):
|
|
| 715 | + name: str = field(init=False, default='muc-room-kicked')
|
|
| 716 | + timestamp: float
|
|
| 717 | + status_codes: Optional[set[StatusCode]]
|
|
| 718 | + reason: Optional[str]
|
|
| 719 | + actor: Optional[str]
|
|
| 720 | + |
|
| 721 | + |
|
| 722 | +@dataclass
|
|
| 723 | +class MUCRoomDestroyed(ApplicationEvent):
|
|
| 724 | + name: str = field(init=False, default='muc-room-destroyed')
|
|
| 725 | + timestamp: float
|
|
| 726 | + reason: Optional[str]
|
|
| 727 | + alternate: Optional[JID]
|
|
| 728 | + |
|
| 729 | + |
|
| 730 | +@dataclass
|
|
| 731 | +class MUCUserJoined(ApplicationEvent):
|
|
| 732 | + name: str = field(init=False, default='muc-user-joined')
|
|
| 733 | + timestamp: float
|
|
| 734 | + is_self: bool
|
|
| 735 | + nick: str
|
|
| 736 | + status_codes: Optional[set[StatusCode]]
|
|
| 737 | + |
|
| 738 | + |
|
| 739 | +@dataclass
|
|
| 740 | +class MUCUserLeft(ApplicationEvent):
|
|
| 741 | + name: str = field(init=False, default='muc-user-left')
|
|
| 742 | + timestamp: float
|
|
| 743 | + is_self: bool
|
|
| 744 | + nick: str
|
|
| 745 | + status_codes: Optional[set[StatusCode]]
|
|
| 746 | + reason: Optional[str]
|
|
| 747 | + actor: Optional[str]
|
|
| 748 | + |
|
| 749 | + |
|
| 750 | +@dataclass
|
|
| 751 | +class MUCUserRoleChanged(ApplicationEvent):
|
|
| 752 | + name: str = field(init=False, default='muc-user-role-changed')
|
|
| 753 | + timestamp: float
|
|
| 754 | + is_self: bool
|
|
| 755 | + nick: str
|
|
| 756 | + role: Role
|
|
| 757 | + reason: Optional[str]
|
|
| 758 | + actor: Optional[str]
|
|
| 759 | + |
|
| 760 | + |
|
| 761 | +@dataclass
|
|
| 762 | +class MUCUserAffiliationChanged(ApplicationEvent):
|
|
| 763 | + name: str = field(init=False, default='muc-user-affiliation-changed')
|
|
| 764 | + timestamp: float
|
|
| 765 | + is_self: bool
|
|
| 766 | + nick: str
|
|
| 767 | + affiliation: Affiliation
|
|
| 768 | + reason: Optional[str]
|
|
| 769 | + actor: Optional[str]
|
|
| 770 | + |
|
| 771 | + |
|
| 772 | +@dataclass
|
|
| 773 | +class MUCUserStatusShowChanged(ApplicationEvent):
|
|
| 774 | + name: str = field(init=False, default='muc-user-status-show-changed')
|
|
| 775 | + timestamp: float
|
|
| 776 | + is_self: bool
|
|
| 777 | + nick: str
|
|
| 778 | + status: str
|
|
| 779 | + show_value: str |
| ... | ... | @@ -15,11 +15,14 @@ |
| 15 | 15 | from __future__ import annotations
|
| 16 | 16 | |
| 17 | 17 | from typing import Any
|
| 18 | +from typing import cast
|
|
| 18 | 19 | from typing import Iterator
|
| 19 | 20 | from typing import Optional
|
| 20 | 21 | from typing import Union
|
| 21 | 22 | from typing import overload
|
| 22 | 23 | |
| 24 | +import time
|
|
| 25 | + |
|
| 23 | 26 | import cairo
|
| 24 | 27 | from nbxmpp.const import Affiliation
|
| 25 | 28 | from nbxmpp.const import Chatstate
|
| ... | ... | @@ -29,9 +32,12 @@ |
| 29 | 32 | from nbxmpp.structs import DiscoInfo
|
| 30 | 33 | from nbxmpp.structs import LocationData
|
| 31 | 34 | from nbxmpp.structs import TuneData
|
| 35 | +from nbxmpp.structs import MessageProperties
|
|
| 32 | 36 | from nbxmpp.structs import MucSubject
|
| 37 | +from nbxmpp.structs import PresenceProperties
|
|
| 33 | 38 | |
| 34 | 39 | from gajim.common import app
|
| 40 | +from gajim.common import events
|
|
| 35 | 41 | from gajim.common import types
|
| 36 | 42 | from gajim.common.const import PresenceShowExt
|
| 37 | 43 | from gajim.common.const import SimpleClientState
|
| ... | ... | @@ -942,12 +948,32 @@ def update_presence(self, |
| 942 | 948 | |
| 943 | 949 | if not self._presence.available and presence.available:
|
| 944 | 950 | self._presence = presence
|
| 945 | - self.notify('user-joined', *args)
|
|
| 951 | + |
|
| 952 | + properties = cast(MessageProperties, args[0])
|
|
| 953 | + event = events.MUCUserJoined(
|
|
| 954 | + timestamp=time.time(),
|
|
| 955 | + is_self=properties.is_muc_self_presence,
|
|
| 956 | + nick=self.name,
|
|
| 957 | + status_codes=properties.muc_status_codes)
|
|
| 958 | + |
|
| 959 | + app.storage.events.store(self.room, event)
|
|
| 960 | + self.notify('user-joined', event)
|
|
| 946 | 961 | return
|
| 947 | 962 | |
| 948 | 963 | if not presence.available:
|
| 949 | 964 | self._presence = presence
|
| 950 | - self.notify('user-left', *args)
|
|
| 965 | + |
|
| 966 | + properties = cast(MessageProperties, args[0])
|
|
| 967 | + event = events.MUCUserLeft(
|
|
| 968 | + timestamp=time.time(),
|
|
| 969 | + is_self=properties.is_muc_self_presence,
|
|
| 970 | + nick=self.name,
|
|
| 971 | + status_codes=properties.muc_status_codes,
|
|
| 972 | + reason=properties.muc_user.reason,
|
|
| 973 | + actor=properties.muc_user.actor)
|
|
| 974 | + |
|
| 975 | + app.storage.events.store(self.room, event)
|
|
| 976 | + self.notify('user-left', event)
|
|
| 951 | 977 | return
|
| 952 | 978 | |
| 953 | 979 | signals: list[str] = []
|
| ... | ... | @@ -962,8 +988,37 @@ def update_presence(self, |
| 962 | 988 | signals.append('user-status-show-changed')
|
| 963 | 989 | |
| 964 | 990 | self._presence = presence
|
| 991 | + |
|
| 965 | 992 | for signal in signals:
|
| 966 | - self.notify(signal, *args)
|
|
| 993 | + properties = cast(PresenceProperties, args[0])
|
|
| 994 | + if signal == 'user-affiliation-changed':
|
|
| 995 | + event = events.MUCUserAffiliationChanged(
|
|
| 996 | + timestamp=time.time(),
|
|
| 997 | + is_self=properties.is_muc_self_presence,
|
|
| 998 | + nick=self.name,
|
|
| 999 | + affiliation=self.affiliation,
|
|
| 1000 | + reason=properties.muc_user.reason,
|
|
| 1001 | + actor=properties.muc_user.actor)
|
|
| 1002 | + |
|
| 1003 | + if signal == 'user-role-changed':
|
|
| 1004 | + event = events.MUCUserRoleChanged(
|
|
| 1005 | + timestamp=time.time(),
|
|
| 1006 | + is_self=properties.is_muc_self_presence,
|
|
| 1007 | + nick=self.name,
|
|
| 1008 | + role=self.role,
|
|
| 1009 | + reason=properties.muc_user.reason,
|
|
| 1010 | + actor=properties.muc_user.actor)
|
|
| 1011 | + |
|
| 1012 | + if signal == 'user-status-show-changed':
|
|
| 1013 | + event = events.MUCUserStatusShowChanged(
|
|
| 1014 | + timestamp=time.time(),
|
|
| 1015 | + is_self=properties.is_muc_self_presence,
|
|
| 1016 | + nick=self.name,
|
|
| 1017 | + status=properties.status,
|
|
| 1018 | + show_value=properties.show.value)
|
|
| 1019 | + |
|
| 1020 | + app.storage.events.store(self.room, event)
|
|
| 1021 | + self.notify(signal, event)
|
|
| 967 | 1022 | |
| 968 | 1023 | def update_avatar(self, *args: Any) -> None:
|
| 969 | 1024 | app.app.avatar_storage.invalidate_cache(self._jid)
|
| ... | ... | @@ -44,6 +44,7 @@ |
| 44 | 44 | from gi.repository import GLib
|
| 45 | 45 | |
| 46 | 46 | from gajim.common import app
|
| 47 | +from gajim.common import events
|
|
| 47 | 48 | from gajim.common import helpers
|
| 48 | 49 | from gajim.common import types
|
| 49 | 50 | from gajim.common.const import ClientState, KindConstant
|
| ... | ... | @@ -456,7 +457,11 @@ def _on_disco_result_after_config(self, task: Task) -> None: |
| 456 | 457 | self._log.info('Configuration finished: %s', jid)
|
| 457 | 458 | |
| 458 | 459 | room = self._get_contact(jid.bare)
|
| 459 | - room.notify('room-config-finished')
|
|
| 460 | + event = events.MUCRoomConfigFinished(timestamp=time.time())
|
|
| 461 | + |
|
| 462 | + assert isinstance(room, GroupchatContact)
|
|
| 463 | + app.storage.events.store(room, event)
|
|
| 464 | + room.notify('room-config-finished', event)
|
|
| 460 | 465 | |
| 461 | 466 | def update_presence(self) -> None:
|
| 462 | 467 | mucs = self._get_mucs_with_state([MUCJoinedState.JOINED,
|
| ... | ... | @@ -536,6 +541,11 @@ def _on_error_presence(self, |
| 536 | 541 | self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
|
| 537 | 542 | |
| 538 | 543 | else:
|
| 544 | + event = events.MUCRoomPresenceError(
|
|
| 545 | + timestamp=time.time(),
|
|
| 546 | + error=properties.error)
|
|
| 547 | + assert isinstance(room, GroupchatContact)
|
|
| 548 | + app.storage.events.store(room, event)
|
|
| 539 | 549 | room.notify('room-presence-error', properties)
|
| 540 | 550 | |
| 541 | 551 | def _on_muc_user_presence(self,
|
| ... | ... | @@ -560,7 +570,15 @@ def _on_muc_user_presence(self, |
| 560 | 570 | self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
|
| 561 | 571 | self._con.get_module('Bookmarks').remove(room_jid)
|
| 562 | 572 | room.set_not_joined()
|
| 563 | - room.notify('room-destroyed', properties)
|
|
| 573 | + |
|
| 574 | + event = events.MUCRoomDestroyed(
|
|
| 575 | + timestamp=time.time(),
|
|
| 576 | + reason=properties.muc_destroyed.reason,
|
|
| 577 | + alternate=properties.muc_destroyed.alternate)
|
|
| 578 | + assert isinstance(room, GroupchatContact)
|
|
| 579 | + app.storage.events.store(room, event)
|
|
| 580 | + |
|
| 581 | + room.notify('room-destroyed', event)
|
|
| 564 | 582 | return
|
| 565 | 583 | |
| 566 | 584 | if properties.is_nickname_changed:
|
| ... | ... | @@ -586,10 +604,16 @@ def _on_muc_user_presence(self, |
| 586 | 604 | presence = self._process_user_presence(properties)
|
| 587 | 605 | occupant.update_presence(presence, properties)
|
| 588 | 606 | |
| 589 | - room.notify('user-nickname-changed',
|
|
| 590 | - occupant,
|
|
| 591 | - new_occupant,
|
|
| 592 | - properties)
|
|
| 607 | + event = events.MUCNicknameChanged(
|
|
| 608 | + timestamp=time.time(),
|
|
| 609 | + is_self=properties.is_muc_self_presence,
|
|
| 610 | + new_name=new_occupant.name,
|
|
| 611 | + old_name=occupant.name)
|
|
| 612 | + |
|
| 613 | + assert isinstance(room, GroupchatContact)
|
|
| 614 | + app.storage.events.store(room, event)
|
|
| 615 | + |
|
| 616 | + room.notify('user-nickname-changed', event, occupant, new_occupant)
|
|
| 593 | 617 | return
|
| 594 | 618 | |
| 595 | 619 | is_joined = self._is_user_joined(properties.jid)
|
| ... | ... | @@ -616,7 +640,16 @@ def _on_muc_user_presence(self, |
| 616 | 640 | if properties.is_muc_self_presence and properties.is_kicked:
|
| 617 | 641 | self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
|
| 618 | 642 | room.set_not_joined()
|
| 619 | - room.notify('room-kicked', properties)
|
|
| 643 | + |
|
| 644 | + event = events.MUCRoomKicked(
|
|
| 645 | + timestamp=time.time(),
|
|
| 646 | + status_codes=properties.muc_status_codes,
|
|
| 647 | + reason=properties.muc_user.reason,
|
|
| 648 | + actor=properties.muc_user.actor)
|
|
| 649 | + assert isinstance(room, GroupchatContact)
|
|
| 650 | + app.storage.events.store(room, event)
|
|
| 651 | + room.notify('room-kicked', event)
|
|
| 652 | + |
|
| 620 | 653 | status_codes = properties.muc_status_codes or []
|
| 621 | 654 | if StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
|
| 622 | 655 | self._start_rejoin_timeout(room_jid)
|
| ... | ... | @@ -889,12 +922,21 @@ def _on_config_change(self, |
| 889 | 922 | if not properties.is_muc_config_change:
|
| 890 | 923 | return
|
| 891 | 924 | |
| 892 | - room_jid = str(properties.muc_jid)
|
|
| 893 | 925 | self._log.info('Received config change: %s %s',
|
| 894 | - room_jid, properties.muc_status_codes)
|
|
| 926 | + properties.muc_jid, properties.muc_status_codes)
|
|
| 895 | 927 | |
| 896 | - room = self._get_contact(room_jid)
|
|
| 897 | - room.notify('room-config-changed', properties)
|
|
| 928 | + assert properties.muc_status_codes is not None
|
|
| 929 | + event = events.MUCRoomConfigChanged(
|
|
| 930 | + timestamp=time.time(),
|
|
| 931 | + status_codes=properties.muc_status_codes)
|
|
| 932 | + |
|
| 933 | + assert properties.muc_jid is not None
|
|
| 934 | + room = self._get_contact(properties.muc_jid)
|
|
| 935 | + |
|
| 936 | + assert isinstance(room, GroupchatContact)
|
|
| 937 | + app.storage.events.store(room, event)
|
|
| 938 | + |
|
| 939 | + room.notify('room-config-changed', event)
|
|
| 898 | 940 | |
| 899 | 941 | raise nbxmpp.NodeProcessed
|
| 900 | 942 |
| ... | ... | @@ -30,11 +30,15 @@ |
| 30 | 30 | |
| 31 | 31 | from gi.repository import GLib
|
| 32 | 32 | |
| 33 | +import nbxmpp.const
|
|
| 33 | 34 | from nbxmpp.protocol import Iq
|
| 34 | 35 | from nbxmpp.protocol import JID
|
| 35 | 36 | from nbxmpp.structs import RosterItem
|
| 36 | 37 | from nbxmpp.structs import DiscoInfo
|
| 37 | 38 | from nbxmpp.structs import CommonError
|
| 39 | +from nbxmpp.const import Role
|
|
| 40 | +from nbxmpp.const import Affiliation
|
|
| 41 | +from nbxmpp.const import StatusCode
|
|
| 38 | 42 | from nbxmpp.modules.discovery import parse_disco_info
|
| 39 | 43 | |
| 40 | 44 | |
| ... | ... | @@ -98,6 +102,13 @@ def _adapt_disco_info(disco_info: DiscoInfo) -> str: |
| 98 | 102 | sqlite3.register_adapter(DiscoInfo, _adapt_disco_info)
|
| 99 | 103 | |
| 100 | 104 | |
| 105 | +def _convert_json(json_string: bytes) -> dict[str, Any]:
|
|
| 106 | + return json.loads(json_string, object_hook=json_decoder)
|
|
| 107 | + |
|
| 108 | + |
|
| 109 | +sqlite3.register_converter('JSON', _convert_json)
|
|
| 110 | + |
|
| 111 | + |
|
| 101 | 112 | class Encoder(json.JSONEncoder):
|
| 102 | 113 | def default(self, o: Any) -> Any:
|
| 103 | 114 | if isinstance(o, set):
|
| ... | ... | @@ -111,6 +122,10 @@ def default(self, o: Any) -> Any: |
| 111 | 122 | dct['__type'] = 'RosterItem'
|
| 112 | 123 | return dct
|
| 113 | 124 | |
| 125 | + if isinstance(o, (Affiliation, Role, StatusCode)):
|
|
| 126 | + return {'value': o.value,
|
|
| 127 | + '__type': o.__class__.__name__}
|
|
| 128 | + |
|
| 114 | 129 | return json.JSONEncoder.default(self, o)
|
| 115 | 130 | |
| 116 | 131 | |
| ... | ... | @@ -118,8 +133,10 @@ def json_decoder(dct: dict[str, Any]) -> Any: |
| 118 | 133 | type_ = dct.get('__type')
|
| 119 | 134 | if type_ is None:
|
| 120 | 135 | return dct
|
| 136 | + |
|
| 121 | 137 | if type_ == 'JID':
|
| 122 | 138 | return JID.from_string(dct['value'])
|
| 139 | + |
|
| 123 | 140 | if type_ == 'RosterItem':
|
| 124 | 141 | return RosterItem(jid=dct['jid'],
|
| 125 | 142 | name=dct['name'],
|
| ... | ... | @@ -127,6 +144,10 @@ def json_decoder(dct: dict[str, Any]) -> Any: |
| 127 | 144 | subscription=dct['subscription'],
|
| 128 | 145 | approved=dct['approved'],
|
| 129 | 146 | groups=set(dct['groups']))
|
| 147 | + |
|
| 148 | + if type_ in ('Affiliation', 'Role', 'StatusCode'):
|
|
| 149 | + return getattr(nbxmpp.const, type_)(dct['value'])
|
|
| 150 | + |
|
| 130 | 151 | return dct
|
| 131 | 152 | |
| 132 | 153 |
| 1 | +# This file is part of Gajim.
|
|
| 2 | +#
|
|
| 3 | +# Gajim is free software; you can redistribute it and/or modify
|
|
| 4 | +# it under the terms of the GNU General Public License as published
|
|
| 5 | +# by the Free Software Foundation; version 3 only.
|
|
| 6 | +#
|
|
| 7 | +# Gajim is distributed in the hope that it will be useful,
|
|
| 8 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 9 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 10 | +# GNU General Public License for more details.
|
|
| 11 | +#
|
|
| 12 | +# You should have received a copy of the GNU General Public License
|
|
| 13 | +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 14 | + |
|
| 15 | +from __future__ import annotations
|
|
| 16 | +import dataclasses
|
|
| 17 | + |
|
| 18 | +from typing import Any
|
|
| 19 | +from typing import NamedTuple
|
|
| 20 | + |
|
| 21 | +import json
|
|
| 22 | +import sqlite3
|
|
| 23 | +import logging
|
|
| 24 | +from collections import namedtuple
|
|
| 25 | + |
|
| 26 | +from nbxmpp.protocol import JID
|
|
| 27 | + |
|
| 28 | +from gajim.common import events
|
|
| 29 | +from gajim.common.storage.base import SqliteStorage
|
|
| 30 | +from gajim.common.storage.base import Encoder
|
|
| 31 | +from gajim.common.types import ChatContactT
|
|
| 32 | + |
|
| 33 | + |
|
| 34 | +EVENTS_SQL_STATEMENT = '''
|
|
| 35 | + CREATE TABLE events (
|
|
| 36 | + account TEXT,
|
|
| 37 | + jid TEXT,
|
|
| 38 | + event TEXT,
|
|
| 39 | + timestamp REAL,
|
|
| 40 | + data TEXT);
|
|
| 41 | + |
|
| 42 | + CREATE INDEX idx_account_jid ON events(account, jid);
|
|
| 43 | + |
|
| 44 | + PRAGMA user_version=1;
|
|
| 45 | + '''
|
|
| 46 | + |
|
| 47 | +EVENT_CLASSES: dict[str, Any] = {
|
|
| 48 | + 'muc-nickname-changed': events.MUCNicknameChanged,
|
|
| 49 | + 'muc-room-config-changed': events.MUCRoomConfigChanged,
|
|
| 50 | + 'muc-room-config-finished': events.MUCRoomConfigFinished,
|
|
| 51 | + 'muc-room-presence-error': events.MUCRoomPresenceError,
|
|
| 52 | + 'muc-room-kicked': events.MUCRoomKicked,
|
|
| 53 | + 'muc-room-destroyed': events.MUCRoomDestroyed,
|
|
| 54 | + 'muc-user-joined': events.MUCUserJoined,
|
|
| 55 | + 'muc-user-left': events.MUCUserLeft,
|
|
| 56 | + 'muc-user-role-changed': events.MUCUserRoleChanged,
|
|
| 57 | + 'muc-user-affiliation-changed': events.MUCUserAffiliationChanged,
|
|
| 58 | + 'muc-user-status-show-changed': events.MUCUserStatusShowChanged,
|
|
| 59 | +}
|
|
| 60 | + |
|
| 61 | +log = logging.getLogger('gajim.c.storage.events')
|
|
| 62 | + |
|
| 63 | + |
|
| 64 | +class EventRow(NamedTuple):
|
|
| 65 | + account: str
|
|
| 66 | + jid: JID
|
|
| 67 | + event: str
|
|
| 68 | + timestamp: float
|
|
| 69 | + data: dict[str, Any]
|
|
| 70 | + |
|
| 71 | + |
|
| 72 | +class EventStorage(SqliteStorage):
|
|
| 73 | + def __init__(self):
|
|
| 74 | + SqliteStorage.__init__(self,
|
|
| 75 | + log,
|
|
| 76 | + None,
|
|
| 77 | + EVENTS_SQL_STATEMENT)
|
|
| 78 | + |
|
| 79 | + def init(self, **kwargs: Any) -> None:
|
|
| 80 | + SqliteStorage.init(self,
|
|
| 81 | + detect_types=sqlite3.PARSE_COLNAMES)
|
|
| 82 | + self._con.row_factory = self._namedtuple_factory
|
|
| 83 | + |
|
| 84 | + @staticmethod
|
|
| 85 | + def _namedtuple_factory(cursor: sqlite3.Cursor,
|
|
| 86 | + row: tuple[Any, ...]) -> NamedTuple:
|
|
| 87 | + |
|
| 88 | + assert cursor.description is not None
|
|
| 89 | + fields = [col[0] for col in cursor.description]
|
|
| 90 | + Row = namedtuple('Row', fields) # type: ignore
|
|
| 91 | + return Row(*row)
|
|
| 92 | + |
|
| 93 | + def _migrate(self) -> None:
|
|
| 94 | + pass
|
|
| 95 | + |
|
| 96 | + def store(self,
|
|
| 97 | + contact: ChatContactT,
|
|
| 98 | + event: Any
|
|
| 99 | + ) -> None:
|
|
| 100 | + |
|
| 101 | + event_dict = dataclasses.asdict(event)
|
|
| 102 | + name = event_dict.pop('name')
|
|
| 103 | + timestamp = event_dict.pop('timestamp')
|
|
| 104 | + |
|
| 105 | + insert_sql = '''
|
|
| 106 | + INSERT INTO events(account, jid, event, timestamp, data)
|
|
| 107 | + VALUES(?, ?, ?, ?, ?)'''
|
|
| 108 | + |
|
| 109 | + self._con.execute(insert_sql, (contact.account,
|
|
| 110 | + contact.jid,
|
|
| 111 | + name,
|
|
| 112 | + timestamp,
|
|
| 113 | + json.dumps(event_dict, cls=Encoder)))
|
|
| 114 | + |
|
| 115 | + def load(self,
|
|
| 116 | + contact: ChatContactT,
|
|
| 117 | + before: bool,
|
|
| 118 | + timestamp: float
|
|
| 119 | + ) -> list[EventRow]:
|
|
| 120 | + |
|
| 121 | + time_operator = '<' if before else '>'
|
|
| 122 | + |
|
| 123 | + insert_sql = '''
|
|
| 124 | + SELECT account, jid, event, timestamp, data as "data [JSON]"
|
|
| 125 | + FROM events WHERE account=? AND jid=? AND timestamp %s ?
|
|
| 126 | + ''' % time_operator
|
|
| 127 | + |
|
| 128 | + for row in self._con.execute(insert_sql, (contact.account,
|
|
| 129 | + contact.jid,
|
|
| 130 | + timestamp)).fetchall():
|
|
| 131 | + event_class = EVENT_CLASSES[row.event]
|
|
| 132 | + yield event_class(**row.data, timestamp=row.timestamp) |
| ... | ... | @@ -21,8 +21,6 @@ |
| 21 | 21 | |
| 22 | 22 | import cairo
|
| 23 | 23 | |
| 24 | -from nbxmpp.structs import PresenceProperties
|
|
| 25 | - |
|
| 26 | 24 | from gajim.common import app
|
| 27 | 25 | from gajim.common import ged
|
| 28 | 26 | from gajim.common import types
|
| ... | ... | @@ -184,13 +182,7 @@ def _on_muc_state_changed(self, |
| 184 | 182 | if contact.is_joined:
|
| 185 | 183 | self._update_content()
|
| 186 | 184 | |
| 187 | - def _on_user_role_changed(self,
|
|
| 188 | - _contact: GroupchatContact,
|
|
| 189 | - _signal_name: str,
|
|
| 190 | - user_contact: GroupchatParticipant,
|
|
| 191 | - properties: PresenceProperties
|
|
| 192 | - ) -> None:
|
|
| 193 | - |
|
| 185 | + def _on_user_role_changed(self, *args: Any) -> None:
|
|
| 194 | 186 | self._update_content()
|
| 195 | 187 | |
| 196 | 188 | def _on_user_state_changed(self, *args: Any) -> None:
|
| ... | ... | @@ -243,16 +243,17 @@ def _move_chat_to_workspace(self, |
| 243 | 243 | if not workspace_id:
|
| 244 | 244 | workspace_id = app.window.add_workspace(switch=False)
|
| 245 | 245 | |
| 246 | - current_chatlist = cast(ChatList, self.get_visible_child())
|
|
| 247 | - type_ = current_chatlist.get_chat_type(params.account, params.jid)
|
|
| 246 | + source_chatlist = self.get_chatlist(params.source_workspace_id)
|
|
| 247 | + type_ = source_chatlist.get_chat_type(params.account, params.jid)
|
|
| 248 | 248 | if type_ is None:
|
| 249 | 249 | return
|
| 250 | - current_chatlist.remove_chat(params.account, params.jid)
|
|
| 250 | + |
|
| 251 | + source_chatlist.remove_chat(params.account, params.jid)
|
|
| 251 | 252 | |
| 252 | 253 | new_chatlist = self.get_chatlist(workspace_id)
|
| 253 | 254 | new_chatlist.add_chat(params.account, params.jid, type_, False, -1)
|
| 254 | 255 | |
| 255 | - self.store_open_chats(current_chatlist.workspace_id)
|
|
| 256 | + self.store_open_chats(source_chatlist.workspace_id)
|
|
| 256 | 257 | self.store_open_chats(workspace_id)
|
| 257 | 258 | |
| 258 | 259 | @structs.actionmethod
|
| ... | ... | @@ -28,7 +28,6 @@ |
| 28 | 28 | from nbxmpp.errors import StanzaError
|
| 29 | 29 | from nbxmpp.protocol import JID
|
| 30 | 30 | from nbxmpp.structs import MessageProperties
|
| 31 | -from nbxmpp.structs import PresenceProperties
|
|
| 32 | 31 | |
| 33 | 32 | from gajim.common import app
|
| 34 | 33 | from gajim.common import events
|
| ... | ... | @@ -297,23 +296,25 @@ def _on_user_joined(self, |
| 297 | 296 | contact: GroupchatContact,
|
| 298 | 297 | _signal_name: str,
|
| 299 | 298 | _user_contact: GroupchatParticipant,
|
| 300 | - _properties: PresenceProperties
|
|
| 299 | + _event: events.MUCUserJoined
|
|
| 301 | 300 | ) -> None:
|
| 301 | + |
|
| 302 | 302 | self._update_group_chat_actions(contact)
|
| 303 | 303 | |
| 304 | 304 | def _on_user_role_changed(self,
|
| 305 | 305 | contact: GroupchatContact,
|
| 306 | 306 | _signal_name: str,
|
| 307 | 307 | _user_contact: GroupchatParticipant,
|
| 308 | - _properties: PresenceProperties
|
|
| 308 | + _event: events.MUCUserRoleChanged
|
|
| 309 | 309 | ) -> None:
|
| 310 | + |
|
| 310 | 311 | self._update_group_chat_actions(contact)
|
| 311 | 312 | |
| 312 | 313 | def _on_user_affiliation_changed(self,
|
| 313 | 314 | contact: GroupchatContact,
|
| 314 | 315 | _signal_name: str,
|
| 315 | 316 | _user_contact: GroupchatParticipant,
|
| 316 | - _properties: PresenceProperties
|
|
| 317 | + _event: events.MUCUserAffiliationChanged
|
|
| 317 | 318 | ) -> None:
|
| 318 | 319 | self._update_group_chat_actions(contact)
|
| 319 | 320 |
| ... | ... | @@ -19,9 +19,6 @@ |
| 19 | 19 | from typing import Union
|
| 20 | 20 | from typing import cast
|
| 21 | 21 | |
| 22 | -from collections import defaultdict
|
|
| 23 | -from collections import deque
|
|
| 24 | -from functools import partial
|
|
| 25 | 22 | import os
|
| 26 | 23 | import logging
|
| 27 | 24 | import time
|
| ... | ... | @@ -105,12 +102,6 @@ def __init__(self) -> None: |
| 105 | 102 | self._muc_subjects: dict[
|
| 106 | 103 | types.ChatContactT, tuple[float, MucSubject]] = {}
|
| 107 | 104 | |
| 108 | - # Store 100 info messages per contact on a FIFO basis
|
|
| 109 | - self._info_messages: dict[
|
|
| 110 | - types.ChatContactT,
|
|
| 111 | - deque[tuple[float, str]]] = defaultdict(
|
|
| 112 | - partial(deque, maxlen=100)) # pyright: ignore
|
|
| 113 | - |
|
| 114 | 105 | self.widget = cast(Gtk.Box, self._ui.get_object('control_box'))
|
| 115 | 106 | self.widget.show_all()
|
| 116 | 107 | |
| ... | ... | @@ -212,9 +203,6 @@ def switch_contact(self, contact: Union[BareContact, |
| 212 | 203 | for transfer in transfers:
|
| 213 | 204 | self.add_file_transfer(transfer)
|
| 214 | 205 | |
| 215 | - for timestamp, message in self._info_messages[contact]:
|
|
| 216 | - self.add_info_message(message, timestamp)
|
|
| 217 | - |
|
| 218 | 206 | if isinstance(contact, GroupchatContact):
|
| 219 | 207 | if (app.settings.get('show_subject_on_join') or
|
| 220 | 208 | not contact.is_joining):
|
| ... | ... | @@ -515,10 +503,6 @@ def add_info_message(self, |
| 515 | 503 | timestamp: Optional[float] = None
|
| 516 | 504 | ) -> None:
|
| 517 | 505 | |
| 518 | - if timestamp is None:
|
|
| 519 | - assert self._contact is not None
|
|
| 520 | - self._info_messages[self._contact].append((time.time(), text))
|
|
| 521 | - |
|
| 522 | 506 | self.conversation_view.add_info_message(text, timestamp)
|
| 523 | 507 | |
| 524 | 508 | def add_file_transfer(self, transfer: HTTPFileTransfer) -> None:
|
| ... | ... | @@ -639,6 +623,42 @@ def _fetch_n_lines_history(self, |
| 639 | 623 | # if self.conversation_view.reduce_message_count(before):
|
| 640 | 624 | # self._scrolled_view.set_history_complete(before, False)
|
| 641 | 625 | |
| 626 | + for event in app.storage.events.load(self._contact, before, timestamp):
|
|
| 627 | + if isinstance(event, events.MUCUserJoined):
|
|
| 628 | + self._process_muc_user_joined(event)
|
|
| 629 | + |
|
| 630 | + elif isinstance(event, events.MUCUserLeft):
|
|
| 631 | + self._process_muc_user_left(event)
|
|
| 632 | + |
|
| 633 | + elif isinstance(event, events.MUCNicknameChanged):
|
|
| 634 | + self._process_muc_nickname_changed(event)
|
|
| 635 | + |
|
| 636 | + elif isinstance(event, events.MUCRoomKicked):
|
|
| 637 | + self._process_muc_room_kicked(event)
|
|
| 638 | + |
|
| 639 | + elif isinstance(event, events.MUCUserAffiliationChanged):
|
|
| 640 | + self._process_muc_user_affiliation_changed(event)
|
|
| 641 | + |
|
| 642 | + elif isinstance(event, events.MUCUserRoleChanged):
|
|
| 643 | + self._process_muc_user_role_changed(event)
|
|
| 644 | + |
|
| 645 | + elif isinstance(event, events.MUCUserStatusShowChanged):
|
|
| 646 | + self._process_muc_user_status_show_changed(event)
|
|
| 647 | + |
|
| 648 | + elif isinstance(event, events.MUCRoomConfigChanged):
|
|
| 649 | + self._process_muc_room_config_changed(event)
|
|
| 650 | + |
|
| 651 | + elif isinstance(event, events.MUCRoomConfigFinished):
|
|
| 652 | + self._process_muc_room_config_finished(event)
|
|
| 653 | + |
|
| 654 | + elif isinstance(event, events.MUCRoomPresenceError):
|
|
| 655 | + self._process_muc_room_presence_error(event)
|
|
| 656 | + |
|
| 657 | + elif isinstance(event, events.MUCRoomDestroyed):
|
|
| 658 | + self._process_muc_room_destroyed(event)
|
|
| 659 | + else:
|
|
| 660 | + raise ValueError('Unknown event: %s' % event)
|
|
| 661 | + |
|
| 642 | 662 | self._scrolled_view.block_signals(False)
|
| 643 | 663 | |
| 644 | 664 | def add_messages(self, messages: list[ConversationRow]):
|
| ... | ... | @@ -723,143 +743,213 @@ def add_message(self, |
| 723 | 743 | additional_data=additional_data)
|
| 724 | 744 | |
| 725 | 745 | def _on_user_nickname_changed(self,
|
| 726 | - contact: types.GroupchatContact,
|
|
| 746 | + _contact: types.GroupchatContact,
|
|
| 727 | 747 | _signal_name: str,
|
| 728 | - old_contact: types.GroupchatParticipant,
|
|
| 729 | - new_contact: types.GroupchatParticipant,
|
|
| 730 | - properties: PresenceProperties
|
|
| 748 | + event: events.MUCNicknameChanged,
|
|
| 749 | + _old_contact: types.GroupchatParticipant,
|
|
| 750 | + _new_contact: types.GroupchatParticipant
|
|
| 731 | 751 | ) -> None:
|
| 732 | 752 | |
| 733 | - if properties.is_muc_self_presence:
|
|
| 734 | - message = _('You are now known as %s') % new_contact.name
|
|
| 753 | + self._process_muc_nickname_changed(event)
|
|
| 754 | + |
|
| 755 | + def _process_muc_nickname_changed(self,
|
|
| 756 | + event: events.MUCNicknameChanged
|
|
| 757 | + ) -> None:
|
|
| 758 | + |
|
| 759 | + if event.is_self:
|
|
| 760 | + message = _('You are now known as %s') % event.new_name
|
|
| 735 | 761 | else:
|
| 736 | 762 | message = _('{nick} is now known '
|
| 737 | - 'as {new_nick}').format(nick=old_contact.name,
|
|
| 738 | - new_nick=new_contact.name)
|
|
| 763 | + 'as {new_nick}').format(nick=event.old_name,
|
|
| 764 | + new_nick=event.new_name)
|
|
| 765 | + self.add_info_message(message, event.timestamp)
|
|
| 739 | 766 | |
| 740 | - self.add_info_message(message)
|
|
| 767 | + def _on_room_kicked(self,
|
|
| 768 | + _contact: GroupchatContact,
|
|
| 769 | + _signal_name: str,
|
|
| 770 | + event: events.MUCRoomKicked
|
|
| 771 | + ) -> None:
|
|
| 741 | 772 | |
| 742 | - def _on_muc_user_status_show_changed(self,
|
|
| 743 | - contact: GroupchatContact,
|
|
| 744 | - _signal_name: str,
|
|
| 745 | - user_contact: GroupchatParticipant,
|
|
| 746 | - properties: PresenceProperties
|
|
| 747 | - ) -> None:
|
|
| 773 | + self._process_muc_room_kicked(event)
|
|
| 748 | 774 | |
| 749 | - if not contact.settings.get('print_status'):
|
|
| 775 | + def _process_muc_room_kicked(self, event: events.MUCRoomKicked) -> None:
|
|
| 776 | + status_codes = event.status_codes or []
|
|
| 777 | + |
|
| 778 | + reason = event.reason
|
|
| 779 | + reason = '' if reason is None else f': {reason}'
|
|
| 780 | + |
|
| 781 | + actor = event.actor
|
|
| 782 | + # Group Chat: You have been kicked by Alice
|
|
| 783 | + actor = '' if actor is None else _(' by {actor}').format(
|
|
| 784 | + actor=actor)
|
|
| 785 | + |
|
| 786 | + # Group Chat: We have been removed from the room by Alice: reason
|
|
| 787 | + message = _('You have been removed from the '
|
|
| 788 | + 'group chat{actor}{reason}')
|
|
| 789 | + |
|
| 790 | + if StatusCode.REMOVED_ERROR in status_codes:
|
|
| 791 | + # Handle 333 before 307, some MUCs add both
|
|
| 792 | + # Group Chat: Server kicked us because of an server error
|
|
| 793 | + message = _('You have left due '
|
|
| 794 | + 'to an error{reason}').format(reason=reason)
|
|
| 795 | + |
|
| 796 | + elif StatusCode.REMOVED_KICKED in status_codes:
|
|
| 797 | + # Group Chat: We have been kicked by Alice: reason
|
|
| 798 | + message = _('You have been '
|
|
| 799 | + 'kicked{actor}{reason}').format(actor=actor,
|
|
| 800 | + reason=reason)
|
|
| 801 | + |
|
| 802 | + elif StatusCode.REMOVED_BANNED in status_codes:
|
|
| 803 | + # Group Chat: We have been banned by Alice: reason
|
|
| 804 | + message = _('You have been '
|
|
| 805 | + 'banned{actor}{reason}').format(actor=actor,
|
|
| 806 | + reason=reason)
|
|
| 807 | + |
|
| 808 | + elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
|
|
| 809 | + # Group Chat: We were removed because of an affiliation change
|
|
| 810 | + reason = _(': Affiliation changed')
|
|
| 811 | + message = message.format(actor=actor, reason=reason)
|
|
| 812 | + |
|
| 813 | + elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
|
|
| 814 | + # Group Chat: Room configuration changed
|
|
| 815 | + reason = _(': Group chat configuration changed to members-only')
|
|
| 816 | + message = message.format(actor=actor, reason=reason)
|
|
| 817 | + |
|
| 818 | + elif StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
|
|
| 819 | + # Group Chat: Kicked because of server shutdown
|
|
| 820 | + reason = ': System shutdown'
|
|
| 821 | + message = message.format(actor=actor, reason=reason)
|
|
| 822 | + |
|
| 823 | + else:
|
|
| 824 | + # No formatted message available
|
|
| 750 | 825 | return
|
| 751 | 826 | |
| 752 | - self.conversation_view.add_user_status(user_contact.name,
|
|
| 753 | - user_contact.show.value,
|
|
| 754 | - user_contact.status)
|
|
| 827 | + self.add_info_message(message, event.timestamp)
|
|
| 755 | 828 | |
| 756 | - def _on_user_status_show_changed(self,
|
|
| 757 | - _user_contact: GroupchatParticipant,
|
|
| 829 | + def _on_user_affiliation_changed(self,
|
|
| 830 | + _contact: GroupchatContact,
|
|
| 758 | 831 | _signal_name: str,
|
| 759 | - properties: PresenceProperties
|
|
| 832 | + user_contact: GroupchatParticipant,
|
|
| 833 | + event: events.MUCUserAffiliationChanged
|
|
| 760 | 834 | ) -> None:
|
| 761 | 835 | |
| 762 | - nick = properties.muc_nickname
|
|
| 763 | - status = properties.status
|
|
| 764 | - status = '' if not status else f' - {status}'
|
|
| 765 | - assert properties.show is not None
|
|
| 766 | - show = helpers.get_uf_show(properties.show.value)
|
|
| 836 | + self._process_muc_user_affiliation_changed(event)
|
|
| 767 | 837 | |
| 768 | - assert isinstance(self.contact, GroupchatParticipant)
|
|
| 769 | - if not self.contact.room.settings.get('print_status'):
|
|
| 770 | - return
|
|
| 838 | + def _process_muc_user_affiliation_changed(
|
|
| 839 | + self,
|
|
| 840 | + event: events.MUCUserAffiliationChanged) -> None:
|
|
| 771 | 841 | |
| 772 | - if properties.is_muc_self_presence:
|
|
| 773 | - message = _('You are now {show}{status}').format(show=show,
|
|
| 774 | - status=status)
|
|
| 842 | + affiliation = helpers.get_uf_affiliation(event.affiliation)
|
|
| 843 | + |
|
| 844 | + reason = event.reason
|
|
| 845 | + reason = '' if reason is None else f': {reason}'
|
|
| 846 | + |
|
| 847 | + actor = event.actor
|
|
| 848 | + # Group Chat: You have been kicked by Alice
|
|
| 849 | + actor = '' if actor is None else _(' by {actor}').format(
|
|
| 850 | + actor=actor)
|
|
| 775 | 851 | |
| 852 | + if event.is_self:
|
|
| 853 | + message = _('** Your Affiliation has been set to '
|
|
| 854 | + '{affiliation}{actor}{reason}').format(
|
|
| 855 | + affiliation=affiliation,
|
|
| 856 | + actor=actor,
|
|
| 857 | + reason=reason)
|
|
| 776 | 858 | else:
|
| 777 | - message = _('{nick} is now {show}{status}').format(nick=nick,
|
|
| 778 | - show=show,
|
|
| 779 | - status=status)
|
|
| 780 | - self.add_info_message(message)
|
|
| 859 | + message = _('** Affiliation of {nick} has been set to '
|
|
| 860 | + '{affiliation}{actor}{reason}').format(
|
|
| 861 | + nick=event.nick,
|
|
| 862 | + affiliation=affiliation,
|
|
| 863 | + actor=actor,
|
|
| 864 | + reason=reason)
|
|
| 781 | 865 | |
| 782 | - def _on_room_voice_request(self,
|
|
| 783 | - _contact: GroupchatContact,
|
|
| 784 | - _signal_name: str,
|
|
| 785 | - properties: MessageProperties
|
|
| 786 | - ) -> None:
|
|
| 787 | - voice_request = properties.voice_request
|
|
| 788 | - assert voice_request is not None
|
|
| 866 | + self.add_info_message(message, event.timestamp)
|
|
| 789 | 867 | |
| 790 | - def on_approve() -> None:
|
|
| 791 | - self.client.get_module('MUC').approve_voice_request(
|
|
| 792 | - self.contact.jid, voice_request)
|
|
| 868 | + def _on_user_role_changed(self,
|
|
| 869 | + _contact: GroupchatContact,
|
|
| 870 | + _signal_name: str,
|
|
| 871 | + user_contact: GroupchatParticipant,
|
|
| 872 | + event: events.MUCUserRoleChanged
|
|
| 873 | + ) -> None:
|
|
| 793 | 874 | |
| 794 | - ConfirmationDialog(
|
|
| 795 | - _('Voice Request'),
|
|
| 796 | - _('Voice Request'),
|
|
| 797 | - _('<b>%(nick)s</b> from <b>%(room_name)s</b> requests voice') % {
|
|
| 798 | - 'nick': voice_request.nick, 'room_name': self.contact.name},
|
|
| 799 | - [DialogButton.make('Cancel'),
|
|
| 800 | - DialogButton.make('Accept',
|
|
| 801 | - text=_('_Approve'),
|
|
| 802 | - callback=on_approve)],
|
|
| 803 | - modal=False).show()
|
|
| 875 | + self._process_muc_user_role_changed(event)
|
|
| 804 | 876 | |
| 805 | - def add_muc_message(self,
|
|
| 806 | - text: str,
|
|
| 807 | - tim: float,
|
|
| 808 | - contact: str = '',
|
|
| 809 | - displaymarking: Optional[Displaymarking] = None,
|
|
| 810 | - message_id: Optional[str] = None,
|
|
| 811 | - stanza_id: Optional[str] = None,
|
|
| 812 | - additional_data: Optional[AdditionalDataDict] = None,
|
|
| 813 | - ) -> None:
|
|
| 877 | + def _process_muc_user_role_changed(self,
|
|
| 878 | + event: events.MUCUserRoleChanged
|
|
| 879 | + ) -> None:
|
|
| 814 | 880 | |
| 815 | - assert isinstance(self._contact, GroupchatContact)
|
|
| 881 | + role = helpers.get_uf_role(event.role)
|
|
| 882 | + nick = event.nick
|
|
| 816 | 883 | |
| 817 | - if contact == self._contact.nickname:
|
|
| 818 | - kind = 'outgoing'
|
|
| 884 | + reason = event.reason
|
|
| 885 | + reason = '' if reason is None else f': {reason}'
|
|
| 886 | + |
|
| 887 | + actor = event.actor
|
|
| 888 | + # Group Chat: You have been kicked by Alice
|
|
| 889 | + actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 890 | + |
|
| 891 | + if event.is_self:
|
|
| 892 | + message = _('** Your Role has been set to '
|
|
| 893 | + '{role}{actor}{reason}').format(role=role,
|
|
| 894 | + actor=actor,
|
|
| 895 | + reason=reason)
|
|
| 819 | 896 | else:
|
| 820 | - kind = 'incoming'
|
|
| 821 | - # muc-specific chatstate
|
|
| 897 | + message = _('** Role of {nick} has been set to '
|
|
| 898 | + '{role}{actor}{reason}').format(nick=nick,
|
|
| 899 | + role=role,
|
|
| 900 | + actor=actor,
|
|
| 901 | + reason=reason)
|
|
| 902 | + self.add_info_message(message, event.timestamp)
|
|
| 822 | 903 | |
| 823 | - self._add_message(text,
|
|
| 824 | - kind,
|
|
| 825 | - contact,
|
|
| 826 | - tim,
|
|
| 827 | - displaymarking=displaymarking,
|
|
| 828 | - message_id=message_id,
|
|
| 829 | - stanza_id=stanza_id,
|
|
| 830 | - additional_data=additional_data)
|
|
| 904 | + def _on_user_status_show_changed(self,
|
|
| 905 | + _user_contact: GroupchatParticipant,
|
|
| 906 | + _signal_name: str,
|
|
| 907 | + event: events.MUCUserStatusShowChanged
|
|
| 908 | + ) -> None:
|
|
| 831 | 909 | |
| 832 | - def _on_room_subject(self,
|
|
| 833 | - contact: GroupchatContact,
|
|
| 834 | - _signal_name: str,
|
|
| 835 | - subject: Optional[MucSubject]
|
|
| 836 | - ) -> None:
|
|
| 910 | + self._process_muc_user_status_show_changed(event)
|
|
| 837 | 911 | |
| 838 | - if subject is None:
|
|
| 839 | - return
|
|
| 912 | + def _process_muc_user_status_show_changed(
|
|
| 913 | + self,
|
|
| 914 | + event: events.MUCUserStatusShowChanged) -> None:
|
|
| 840 | 915 | |
| 841 | - _timestamp, old_subject = self._muc_subjects.get(contact, (None, None))
|
|
| 842 | - if old_subject is not None and old_subject.text == subject.text:
|
|
| 843 | - # Probably a rejoin, we already showed that subject
|
|
| 916 | + nick = event.nick
|
|
| 917 | + status = event.status
|
|
| 918 | + status = '' if not status else f' - {status}'
|
|
| 919 | + show = helpers.get_uf_show(event.show_value)
|
|
| 920 | + |
|
| 921 | + assert isinstance(self.contact, GroupchatParticipant)
|
|
| 922 | + if not self.contact.room.settings.get('print_status'):
|
|
| 844 | 923 | return
|
| 845 | 924 | |
| 846 | - self._muc_subjects[contact] = (time.time(), subject)
|
|
| 925 | + if event.is_self:
|
|
| 926 | + message = _('You are now {show}{status}').format(show=show,
|
|
| 927 | + status=status)
|
|
| 847 | 928 | |
| 848 | - if (app.settings.get('show_subject_on_join') or
|
|
| 849 | - not contact.is_joining):
|
|
| 850 | - self.conversation_view.add_muc_subject(subject)
|
|
| 929 | + else:
|
|
| 930 | + message = _('{nick} is now {show}{status}').format(
|
|
| 931 | + nick=nick,
|
|
| 932 | + show=show,
|
|
| 933 | + status=status)
|
|
| 934 | + |
|
| 935 | + self.add_info_message(message, event.timestamp)
|
|
| 851 | 936 | |
| 852 | 937 | def _on_room_config_changed(self,
|
| 853 | 938 | _contact: GroupchatContact,
|
| 854 | 939 | _signal_name: str,
|
| 855 | - properties: MessageProperties
|
|
| 940 | + event: events.MUCRoomConfigChanged
|
|
| 856 | 941 | ) -> None:
|
| 857 | - # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
|
|
| 858 | 942 | |
| 859 | - status_codes = properties.muc_status_codes
|
|
| 860 | - assert status_codes is not None
|
|
| 943 | + self._process_muc_room_config_changed(event)
|
|
| 944 | + |
|
| 945 | + def _process_muc_room_config_changed(self,
|
|
| 946 | + event: events.MUCRoomConfigChanged
|
|
| 947 | + ) -> None:
|
|
| 861 | 948 | |
| 949 | + # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
|
|
| 950 | + status_codes = event.status_codes
|
|
| 862 | 951 | changes: list[str] = []
|
| 952 | + |
|
| 863 | 953 | if StatusCode.SHOWING_UNAVAILABLE in status_codes:
|
| 864 | 954 | changes.append(_('Group chat now shows unavailable members'))
|
| 865 | 955 | |
| ... | ... | @@ -873,7 +963,8 @@ def _on_room_config_changed(self, |
| 873 | 963 | self.client.get_module('Discovery').disco_muc(self.contact.jid)
|
| 874 | 964 | |
| 875 | 965 | if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
|
| 876 | - # Can be a presence (see chg_contact_status in groupchat_control.py)
|
|
| 966 | + # Can be a presence
|
|
| 967 | + # (see chg_contact_status in groupchat_control.py)
|
|
| 877 | 968 | changes.append(_('Conversations are stored on the server'))
|
| 878 | 969 | |
| 879 | 970 | if StatusCode.CONFIG_NO_ROOM_LOGGING in status_codes:
|
| ... | ... | @@ -888,194 +979,130 @@ def _on_room_config_changed(self, |
| 888 | 979 | if StatusCode.CONFIG_FULL_ANONYMOUS in status_codes:
|
| 889 | 980 | changes.append(_('Group chat is now fully anonymous'))
|
| 890 | 981 | |
| 891 | - for change in changes:
|
|
| 892 | - self.add_info_message(change)
|
|
| 893 | - |
|
| 894 | - def rejoin(self) -> None:
|
|
| 895 | - self.client.get_module('MUC').join(self.contact.jid)
|
|
| 896 | - |
|
| 897 | - def _on_user_joined(self,
|
|
| 898 | - contact: GroupchatContact,
|
|
| 899 | - _signal_name: str,
|
|
| 900 | - user_contact: GroupchatParticipant,
|
|
| 901 | - properties: PresenceProperties
|
|
| 902 | - ) -> None:
|
|
| 903 | - |
|
| 904 | - nick = user_contact.name
|
|
| 905 | - if not properties.is_muc_self_presence:
|
|
| 906 | - if contact.is_joined:
|
|
| 907 | - self.conversation_view.add_muc_user_joined(nick)
|
|
| 908 | - return
|
|
| 909 | - |
|
| 910 | - status_codes = properties.muc_status_codes or []
|
|
| 911 | - |
|
| 912 | - if not contact.is_joined:
|
|
| 913 | - # We just joined the room
|
|
| 914 | - self.add_info_message(_('You (%s) joined the group chat') % nick)
|
|
| 915 | - |
|
| 916 | - if StatusCode.NON_ANONYMOUS in status_codes:
|
|
| 917 | - self.add_info_message(
|
|
| 918 | - _('Any participant is allowed to see your full XMPP Address'))
|
|
| 919 | - |
|
| 920 | - if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
|
|
| 921 | - self.add_info_message(_('Conversations are stored on the server'))
|
|
| 922 | - |
|
| 923 | - if StatusCode.NICKNAME_MODIFIED in status_codes:
|
|
| 924 | - self.add_info_message(
|
|
| 925 | - _('The server has assigned or modified your nickname in this '
|
|
| 926 | - 'group chat'))
|
|
| 982 | + for message in changes:
|
|
| 983 | + self.add_info_message(message, event.timestamp)
|
|
| 927 | 984 | |
| 928 | 985 | def _on_room_config_finished(self,
|
| 929 | 986 | _contact: GroupchatContact,
|
| 930 | - _signal_name: str
|
|
| 987 | + _signal_name: str,
|
|
| 988 | + event: events.MUCRoomConfigFinished
|
|
| 931 | 989 | ) -> None:
|
| 990 | + self._process_muc_room_config_finished(event)
|
|
| 991 | + |
|
| 992 | + def _process_muc_room_config_finished(self,
|
|
| 993 | + event: events.MUCRoomConfigFinished
|
|
| 994 | + ) -> None:
|
|
| 995 | + |
|
| 932 | 996 | self.add_info_message(_('A new group chat has been created'))
|
| 933 | 997 | |
| 934 | - def _on_user_affiliation_changed(self,
|
|
| 935 | - _contact: GroupchatContact,
|
|
| 936 | - _signal_name: str,
|
|
| 937 | - user_contact: GroupchatParticipant,
|
|
| 938 | - properties: PresenceProperties
|
|
| 939 | - ) -> None:
|
|
| 940 | - affiliation = helpers.get_uf_affiliation(user_contact.affiliation)
|
|
| 941 | - nick = user_contact.name
|
|
| 998 | + def _on_room_presence_error(self,
|
|
| 999 | + _contact: GroupchatContact,
|
|
| 1000 | + _signal_name: str,
|
|
| 1001 | + event: events.MUCRoomPresenceError
|
|
| 1002 | + ) -> None:
|
|
| 942 | 1003 | |
| 943 | - assert properties.muc_user is not None
|
|
| 944 | - reason = properties.muc_user.reason
|
|
| 945 | - reason = '' if reason is None else f': {reason}'
|
|
| 1004 | + self._process_muc_room_presence_error(event)
|
|
| 946 | 1005 | |
| 947 | - actor = properties.muc_user.actor
|
|
| 948 | - # Group Chat: You have been kicked by Alice
|
|
| 949 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1006 | + def _process_muc_room_presence_error(self,
|
|
| 1007 | + event: events.MUCRoomPresenceError
|
|
| 1008 | + ) -> None:
|
|
| 950 | 1009 | |
| 951 | - if properties.is_muc_self_presence:
|
|
| 952 | - message = _('** Your Affiliation has been set to '
|
|
| 953 | - '{affiliation}{actor}{reason}').format(
|
|
| 954 | - affiliation=affiliation,
|
|
| 955 | - actor=actor,
|
|
| 956 | - reason=reason)
|
|
| 957 | - else:
|
|
| 958 | - message = _('** Affiliation of {nick} has been set to '
|
|
| 959 | - '{affiliation}{actor}{reason}').format(
|
|
| 960 | - nick=nick,
|
|
| 961 | - affiliation=affiliation,
|
|
| 962 | - actor=actor,
|
|
| 963 | - reason=reason)
|
|
| 1010 | + error_message = to_user_string(event.error)
|
|
| 1011 | + self.add_info_message(_('Error: %s') % error_message)
|
|
| 964 | 1012 | |
| 965 | - self.add_info_message(message)
|
|
| 1013 | + def _on_room_destroyed(self,
|
|
| 1014 | + _contact: GroupchatContact,
|
|
| 1015 | + _signal_name: str,
|
|
| 1016 | + event: events.MUCRoomDestroyed
|
|
| 1017 | + ) -> None:
|
|
| 966 | 1018 | |
| 967 | - def _on_user_role_changed(self,
|
|
| 968 | - _contact: GroupchatContact,
|
|
| 969 | - _signal_name: str,
|
|
| 970 | - user_contact: GroupchatParticipant,
|
|
| 971 | - properties: PresenceProperties
|
|
| 972 | - ) -> None:
|
|
| 973 | - role = helpers.get_uf_role(user_contact.role)
|
|
| 974 | - nick = user_contact.name
|
|
| 1019 | + self._process_muc_room_destroyed(event)
|
|
| 1020 | + |
|
| 1021 | + def _process_muc_room_destroyed(self,
|
|
| 1022 | + event: events.MUCRoomDestroyed
|
|
| 1023 | + ) -> None:
|
|
| 975 | 1024 | |
| 976 | - assert properties.muc_user is not None
|
|
| 977 | - reason = properties.muc_user.reason
|
|
| 1025 | + reason = event.reason
|
|
| 978 | 1026 | reason = '' if reason is None else f': {reason}'
|
| 979 | 1027 | |
| 980 | - actor = properties.muc_user.actor
|
|
| 981 | - # Group Chat: You have been kicked by Alice
|
|
| 982 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1028 | + message = _('Group chat has been destroyed')
|
|
| 983 | 1029 | |
| 984 | - if properties.is_muc_self_presence:
|
|
| 985 | - message = _('** Your Role has been set to '
|
|
| 986 | - '{role}{actor}{reason}').format(role=role,
|
|
| 987 | - actor=actor,
|
|
| 988 | - reason=reason)
|
|
| 989 | - else:
|
|
| 990 | - message = _('** Role of {nick} has been set to '
|
|
| 991 | - '{role}{actor}{reason}').format(nick=nick,
|
|
| 992 | - role=role,
|
|
| 993 | - actor=actor,
|
|
| 994 | - reason=reason)
|
|
| 1030 | + if event.alternate is not None:
|
|
| 1031 | + message += '\n' + _('You can join this group chat instead: '
|
|
| 1032 | + 'xmpp:%s?join') % str(event.alternate)
|
|
| 995 | 1033 | |
| 996 | - self.add_info_message(message)
|
|
| 1034 | + self.add_info_message(message, event.timestamp)
|
|
| 997 | 1035 | |
| 998 | - def _on_room_kicked(self,
|
|
| 1036 | + def _on_user_joined(self,
|
|
| 999 | 1037 | _contact: GroupchatContact,
|
| 1000 | 1038 | _signal_name: str,
|
| 1001 | - properties: MessageProperties
|
|
| 1039 | + _user_contact: GroupchatParticipant,
|
|
| 1040 | + event: events.MUCUserJoined
|
|
| 1002 | 1041 | ) -> None:
|
| 1003 | - status_codes = properties.muc_status_codes or []
|
|
| 1004 | 1042 | |
| 1005 | - assert properties.muc_user is not None
|
|
| 1006 | - reason = properties.muc_user.reason
|
|
| 1007 | - reason = '' if reason is None else f': {reason}'
|
|
| 1043 | + self._process_muc_user_joined(event)
|
|
| 1008 | 1044 | |
| 1009 | - actor = properties.muc_user.actor
|
|
| 1010 | - # Group Chat: You have been kicked by Alice
|
|
| 1011 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1045 | + def _process_muc_user_joined(self, event: events.MUCUserJoined) -> None:
|
|
| 1046 | + assert isinstance(self.contact, GroupchatContact)
|
|
| 1012 | 1047 | |
| 1013 | - # Group Chat: We have been removed from the room by Alice: reason
|
|
| 1014 | - message = _('You have been removed from the group chat{actor}{reason}')
|
|
| 1048 | + if not event.is_self:
|
|
| 1049 | + if self.contact.is_joined:
|
|
| 1050 | + self.conversation_view.add_muc_user_joined(event.nick)
|
|
| 1051 | + return
|
|
| 1015 | 1052 | |
| 1016 | - if StatusCode.REMOVED_ERROR in status_codes:
|
|
| 1017 | - # Handle 333 before 307, some MUCs add both
|
|
| 1018 | - # Group Chat: Server kicked us because of an server error
|
|
| 1019 | - message = _('You have left due '
|
|
| 1020 | - 'to an error{reason}').format(reason=reason)
|
|
| 1021 | - self.add_info_message(message)
|
|
| 1053 | + status_codes = event.status_codes or []
|
|
| 1022 | 1054 | |
| 1023 | - elif StatusCode.REMOVED_KICKED in status_codes:
|
|
| 1024 | - # Group Chat: We have been kicked by Alice: reason
|
|
| 1025 | - message = _('You have been '
|
|
| 1026 | - 'kicked{actor}{reason}').format(actor=actor,
|
|
| 1027 | - reason=reason)
|
|
| 1028 | - self.add_info_message(message)
|
|
| 1055 | + message = None
|
|
| 1056 | + if not self.contact.is_joined:
|
|
| 1057 | + # We just joined the room
|
|
| 1058 | + message = _('You (%s) joined the group chat') % event.nick
|
|
| 1029 | 1059 | |
| 1030 | - elif StatusCode.REMOVED_BANNED in status_codes:
|
|
| 1031 | - # Group Chat: We have been banned by Alice: reason
|
|
| 1032 | - message = _('You have been '
|
|
| 1033 | - 'banned{actor}{reason}').format(actor=actor,
|
|
| 1034 | - reason=reason)
|
|
| 1035 | - self.add_info_message(message)
|
|
| 1060 | + if StatusCode.NON_ANONYMOUS in status_codes:
|
|
| 1061 | + message = _('Any participant is allowed to see your full '
|
|
| 1062 | + 'XMPP Address')
|
|
| 1036 | 1063 | |
| 1037 | - elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
|
|
| 1038 | - # Group Chat: We were removed because of an affiliation change
|
|
| 1039 | - reason = _(': Affiliation changed')
|
|
| 1040 | - message = message.format(actor=actor, reason=reason)
|
|
| 1041 | - self.add_info_message(message)
|
|
| 1064 | + if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
|
|
| 1065 | + message = _('Conversations are stored on the server')
|
|
| 1042 | 1066 | |
| 1043 | - elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
|
|
| 1044 | - # Group Chat: Room configuration changed
|
|
| 1045 | - reason = _(': Group chat configuration changed to members-only')
|
|
| 1046 | - message = message.format(actor=actor, reason=reason)
|
|
| 1047 | - self.add_info_message(message)
|
|
| 1067 | + if StatusCode.NICKNAME_MODIFIED in status_codes:
|
|
| 1068 | + message = _('The server has assigned or modified your '
|
|
| 1069 | + 'nickname in this group chat')
|
|
| 1048 | 1070 | |
| 1049 | - elif StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
|
|
| 1050 | - # Group Chat: Kicked because of server shutdown
|
|
| 1051 | - reason = ': System shutdown'
|
|
| 1052 | - message = message.format(actor=actor, reason=reason)
|
|
| 1053 | - self.add_info_message(message)
|
|
| 1071 | + if message is not None:
|
|
| 1072 | + self.add_info_message(message, event.timestamp)
|
|
| 1054 | 1073 | |
| 1055 | 1074 | def _on_user_left(self,
|
| 1056 | 1075 | _contact: GroupchatContact,
|
| 1057 | 1076 | _signal_name: str,
|
| 1058 | - user_contact: GroupchatParticipant,
|
|
| 1059 | - properties: MessageProperties
|
|
| 1077 | + _user_contact: GroupchatParticipant,
|
|
| 1078 | + event: events.MUCUserLeft
|
|
| 1060 | 1079 | ) -> None:
|
| 1061 | - status_codes = properties.muc_status_codes or []
|
|
| 1062 | - nick = user_contact.name
|
|
| 1063 | 1080 | |
| 1064 | - assert properties.muc_user is not None
|
|
| 1065 | - reason = properties.muc_user.reason
|
|
| 1081 | + self._process_muc_user_left(event)
|
|
| 1082 | + |
|
| 1083 | + def _process_muc_user_left(self, event: events.MUCUserLeft) -> None:
|
|
| 1084 | + if event.is_self:
|
|
| 1085 | + return
|
|
| 1086 | + |
|
| 1087 | + status_codes = event.status_codes or []
|
|
| 1088 | + nick = event.nick
|
|
| 1089 | + |
|
| 1090 | + reason = event.reason
|
|
| 1066 | 1091 | reason = '' if reason is None else f': {reason}'
|
| 1067 | 1092 | |
| 1068 | - actor = properties.muc_user.actor
|
|
| 1093 | + actor = event.actor
|
|
| 1069 | 1094 | # Group Chat: You have been kicked by Alice
|
| 1070 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1095 | + actor = '' if actor is None else _(' by {actor}').format(
|
|
| 1096 | + actor=actor)
|
|
| 1071 | 1097 | |
| 1072 | 1098 | # Group Chat: We have been removed from the room
|
| 1073 | - message = _('{nick} has been removed from the group chat{by}{reason}')
|
|
| 1099 | + message = _('{nick} has been removed from the group '
|
|
| 1100 | + 'chat{by}{reason}')
|
|
| 1074 | 1101 | |
| 1075 | 1102 | if StatusCode.REMOVED_ERROR in status_codes:
|
| 1076 | 1103 | # Handle 333 before 307, some MUCs add both
|
| 1077 | 1104 | self.conversation_view.add_muc_user_left(
|
| 1078 | - nick, properties.muc_user.reason, error=True)
|
|
| 1105 | + nick, event.reason, error=True)
|
|
| 1079 | 1106 | |
| 1080 | 1107 | elif StatusCode.REMOVED_KICKED in status_codes:
|
| 1081 | 1108 | # Group Chat: User was kicked by Alice: reason
|
| ... | ... | @@ -1083,7 +1110,6 @@ def _on_user_left(self, |
| 1083 | 1110 | 'kicked{actor}{reason}').format(nick=nick,
|
| 1084 | 1111 | actor=actor,
|
| 1085 | 1112 | reason=reason)
|
| 1086 | - self.add_info_message(message)
|
|
| 1087 | 1113 | |
| 1088 | 1114 | elif StatusCode.REMOVED_BANNED in status_codes:
|
| 1089 | 1115 | # Group Chat: User was banned by Alice: reason
|
| ... | ... | @@ -1091,49 +1117,104 @@ def _on_user_left(self, |
| 1091 | 1117 | 'banned{actor}{reason}').format(nick=nick,
|
| 1092 | 1118 | actor=actor,
|
| 1093 | 1119 | reason=reason)
|
| 1094 | - self.add_info_message(message)
|
|
| 1095 | 1120 | |
| 1096 | 1121 | elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
|
| 1097 | 1122 | reason = _(': Affiliation changed')
|
| 1098 | 1123 | message = message.format(nick=nick, by=actor, reason=reason)
|
| 1099 | - self.add_info_message(message)
|
|
| 1100 | 1124 | |
| 1101 | 1125 | elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
|
| 1102 | 1126 | reason = _(': Group chat configuration changed to members-only')
|
| 1103 | 1127 | message = message.format(nick=nick, by=actor, reason=reason)
|
| 1104 | - self.add_info_message(message)
|
|
| 1105 | 1128 | |
| 1106 | 1129 | else:
|
| 1107 | - self.conversation_view.add_muc_user_left(
|
|
| 1108 | - nick, properties.muc_user.reason)
|
|
| 1130 | + self.conversation_view.add_muc_user_left(nick, event.reason)
|
|
| 1131 | + return
|
|
| 1109 | 1132 | |
| 1110 | - def _on_room_presence_error(self,
|
|
| 1111 | - _contact: GroupchatContact,
|
|
| 1112 | - _signal_name: str,
|
|
| 1113 | - properties: PresenceProperties
|
|
| 1114 | - ) -> None:
|
|
| 1133 | + self.add_info_message(message, event.timestamp)
|
|
| 1115 | 1134 | |
| 1116 | - assert properties.error is not None
|
|
| 1117 | - error_message = to_user_string(properties.error)
|
|
| 1118 | - self.add_info_message(_('Error: %s') % error_message)
|
|
| 1135 | + def _on_muc_user_status_show_changed(self,
|
|
| 1136 | + contact: GroupchatContact,
|
|
| 1137 | + _signal_name: str,
|
|
| 1138 | + user_contact: GroupchatParticipant,
|
|
| 1139 | + properties: PresenceProperties
|
|
| 1140 | + ) -> None:
|
|
| 1119 | 1141 | |
| 1120 | - def _on_room_destroyed(self,
|
|
| 1121 | - _contact: GroupchatContact,
|
|
| 1122 | - _signal_name: str,
|
|
| 1123 | - properties: PresenceProperties
|
|
| 1124 | - ) -> None:
|
|
| 1142 | + if not contact.settings.get('print_status'):
|
|
| 1143 | + return
|
|
| 1125 | 1144 | |
| 1126 | - destroyed = properties.muc_destroyed
|
|
| 1127 | - assert destroyed is not None
|
|
| 1145 | + self.conversation_view.add_user_status(user_contact.name,
|
|
| 1146 | + user_contact.show.value,
|
|
| 1147 | + user_contact.status)
|
|
| 1128 | 1148 | |
| 1129 | - reason = destroyed.reason
|
|
| 1130 | - reason = '' if reason is None else f': {reason}'
|
|
| 1149 | + def _on_room_voice_request(self,
|
|
| 1150 | + _contact: GroupchatContact,
|
|
| 1151 | + _signal_name: str,
|
|
| 1152 | + properties: MessageProperties
|
|
| 1153 | + ) -> None:
|
|
| 1154 | + voice_request = properties.voice_request
|
|
| 1155 | + assert voice_request is not None
|
|
| 1131 | 1156 | |
| 1132 | - message = _('Group chat has been destroyed')
|
|
| 1133 | - self.add_info_message(message)
|
|
| 1157 | + def on_approve() -> None:
|
|
| 1158 | + self.client.get_module('MUC').approve_voice_request(
|
|
| 1159 | + self.contact.jid, voice_request)
|
|
| 1160 | + |
|
| 1161 | + ConfirmationDialog(
|
|
| 1162 | + _('Voice Request'),
|
|
| 1163 | + _('Voice Request'),
|
|
| 1164 | + _('<b>%(nick)s</b> from <b>%(room_name)s</b> requests voice') % {
|
|
| 1165 | + 'nick': voice_request.nick, 'room_name': self.contact.name},
|
|
| 1166 | + [DialogButton.make('Cancel'),
|
|
| 1167 | + DialogButton.make('Accept',
|
|
| 1168 | + text=_('_Approve'),
|
|
| 1169 | + callback=on_approve)],
|
|
| 1170 | + modal=False).show()
|
|
| 1134 | 1171 | |
| 1135 | - alternate = destroyed.alternate
|
|
| 1136 | - if alternate is not None:
|
|
| 1137 | - join_message = _('You can join this group chat '
|
|
| 1138 | - 'instead: xmpp:%s?join') % str(alternate)
|
|
| 1139 | - self.add_info_message(join_message) |
|
| 1172 | + def add_muc_message(self,
|
|
| 1173 | + text: str,
|
|
| 1174 | + tim: float,
|
|
| 1175 | + contact: str = '',
|
|
| 1176 | + displaymarking: Optional[Displaymarking] = None,
|
|
| 1177 | + message_id: Optional[str] = None,
|
|
| 1178 | + stanza_id: Optional[str] = None,
|
|
| 1179 | + additional_data: Optional[AdditionalDataDict] = None,
|
|
| 1180 | + ) -> None:
|
|
| 1181 | + |
|
| 1182 | + assert isinstance(self._contact, GroupchatContact)
|
|
| 1183 | + |
|
| 1184 | + if contact == self._contact.nickname:
|
|
| 1185 | + kind = 'outgoing'
|
|
| 1186 | + else:
|
|
| 1187 | + kind = 'incoming'
|
|
| 1188 | + # muc-specific chatstate
|
|
| 1189 | + |
|
| 1190 | + self._add_message(text,
|
|
| 1191 | + kind,
|
|
| 1192 | + contact,
|
|
| 1193 | + tim,
|
|
| 1194 | + displaymarking=displaymarking,
|
|
| 1195 | + message_id=message_id,
|
|
| 1196 | + stanza_id=stanza_id,
|
|
| 1197 | + additional_data=additional_data)
|
|
| 1198 | + |
|
| 1199 | + def _on_room_subject(self,
|
|
| 1200 | + contact: GroupchatContact,
|
|
| 1201 | + _signal_name: str,
|
|
| 1202 | + subject: Optional[MucSubject]
|
|
| 1203 | + ) -> None:
|
|
| 1204 | + |
|
| 1205 | + if subject is None:
|
|
| 1206 | + return
|
|
| 1207 | + |
|
| 1208 | + _timestamp, old_subject = self._muc_subjects.get(contact, (None, None))
|
|
| 1209 | + if old_subject is not None and old_subject.text == subject.text:
|
|
| 1210 | + # Probably a rejoin, we already showed that subject
|
|
| 1211 | + return
|
|
| 1212 | + |
|
| 1213 | + self._muc_subjects[contact] = (time.time(), subject)
|
|
| 1214 | + |
|
| 1215 | + if (app.settings.get('show_subject_on_join') or
|
|
| 1216 | + not contact.is_joining):
|
|
| 1217 | + self.conversation_view.add_muc_subject(subject)
|
|
| 1218 | + |
|
| 1219 | + def rejoin(self) -> None:
|
|
| 1220 | + self.client.get_module('MUC').join(self.contact.jid) |
| ... | ... | @@ -176,7 +176,7 @@ def add_muc_subject(self, |
| 176 | 176 | |
| 177 | 177 | def add_muc_user_left(self,
|
| 178 | 178 | nick: str,
|
| 179 | - reason: str,
|
|
| 179 | + reason: Optional[str],
|
|
| 180 | 180 | error: bool = False) -> None:
|
| 181 | 181 | assert isinstance(self._contact, GroupchatContact)
|
| 182 | 182 | if not self._contact.settings.get('print_join_left'):
|
| ... | ... | @@ -18,7 +18,6 @@ |
| 18 | 18 | |
| 19 | 19 | import logging
|
| 20 | 20 | |
| 21 | -from nbxmpp.structs import PresenceProperties
|
|
| 22 | 21 | |
| 23 | 22 | from gi.repository import Gtk
|
| 24 | 23 | from gi.repository import Gdk
|
| ... | ... | @@ -27,6 +26,7 @@ |
| 27 | 26 | from gajim.common import ged
|
| 28 | 27 | from gajim.common import types
|
| 29 | 28 | from gajim.common.events import GcMessageReceived
|
| 29 | +from gajim.common.events import MUCNicknameChanged
|
|
| 30 | 30 | from gajim.common.ged import EventHelper
|
| 31 | 31 | from gajim.common.helpers import jid_is_blocked
|
| 32 | 32 | from gajim.common.helpers import message_needs_highlight
|
| ... | ... | @@ -76,12 +76,12 @@ def switch_contact(self, contact: types.GroupchatContactT) -> None: |
| 76 | 76 | def _on_user_nickname_changed(self,
|
| 77 | 77 | _contact: types.GroupchatContact,
|
| 78 | 78 | _signal_name: str,
|
| 79 | + event: MUCNicknameChanged,
|
|
| 79 | 80 | old_contact: types.GroupchatParticipant,
|
| 80 | - new_contact: types.GroupchatParticipant,
|
|
| 81 | - properties: PresenceProperties
|
|
| 81 | + new_contact: types.GroupchatParticipant
|
|
| 82 | 82 | ) -> None:
|
| 83 | 83 | |
| 84 | - if properties.is_muc_self_presence:
|
|
| 84 | + if event.is_self:
|
|
| 85 | 85 | return
|
| 86 | 86 | |
| 87 | 87 | old_name = old_contact.name
|
| ... | ... | @@ -25,7 +25,6 @@ |
| 25 | 25 | from gi.repository import Gtk
|
| 26 | 26 | from gi.repository import GLib
|
| 27 | 27 | from nbxmpp.const import Affiliation
|
| 28 | -from nbxmpp.structs import PresenceProperties
|
|
| 29 | 28 | |
| 30 | 29 | from gajim.common import app
|
| 31 | 30 | from gajim.common import ged
|
| ... | ... | @@ -36,6 +35,7 @@ |
| 36 | 35 | from gajim.common.const import AvatarSize
|
| 37 | 36 | from gajim.common.const import StyleAttr
|
| 38 | 37 | from gajim.common.events import ApplicationEvent
|
| 38 | +from gajim.common.events import MUCNicknameChanged
|
|
| 39 | 39 | from gajim.common.modules.contacts import GroupchatContact
|
| 40 | 40 | |
| 41 | 41 | from .menus import get_groupchat_roster_menu
|
| ... | ... | @@ -281,11 +281,11 @@ def _update_contact(self, |
| 281 | 281 | self._add_contact(user_contact)
|
| 282 | 282 | |
| 283 | 283 | def _on_user_nickname_changed(self,
|
| 284 | - contact: types.GroupchatContact,
|
|
| 284 | + _contact: types.GroupchatContact,
|
|
| 285 | 285 | _signal_name: str,
|
| 286 | + _event: MUCNicknameChanged,
|
|
| 286 | 287 | old_contact: types.GroupchatParticipant,
|
| 287 | - new_contact: types.GroupchatParticipant,
|
|
| 288 | - properties: PresenceProperties
|
|
| 288 | + new_contact: types.GroupchatParticipant
|
|
| 289 | 289 | ) -> None:
|
| 290 | 290 | |
| 291 | 291 | self._remove_contact(old_contact)
|
| ... | ... | @@ -62,6 +62,7 @@ |
| 62 | 62 | from .structs import AccountJidParam
|
| 63 | 63 | from .structs import AddChatActionParams
|
| 64 | 64 | from .structs import actionmethod
|
| 65 | +from .structs import ChatListEntryParam
|
|
| 65 | 66 | |
| 66 | 67 | if TYPE_CHECKING:
|
| 67 | 68 | from .control import ChatControl
|
| ... | ... | @@ -626,18 +627,52 @@ def _remove_workspace(self, |
| 626 | 627 | self.remove_workspace(workspace_id)
|
| 627 | 628 | |
| 628 | 629 | def remove_workspace(self, workspace_id: str) -> None:
|
| 630 | + if len(app.settings.get_workspaces()) == 1:
|
|
| 631 | + log.warning('Tried to remove the only workspace')
|
|
| 632 | + return
|
|
| 633 | + |
|
| 629 | 634 | was_active = self.get_active_workspace() == workspace_id
|
| 635 | + chat_list = self.get_chat_list(workspace_id)
|
|
| 636 | + open_chats = chat_list.get_open_chats()
|
|
| 637 | + |
|
| 638 | + def _continue_removing_workspace():
|
|
| 639 | + new_workspace_id = self._workspace_side_bar.get_other_workspace(
|
|
| 640 | + workspace_id)
|
|
| 641 | + if new_workspace_id is None:
|
|
| 642 | + log.warning('No other workspaces found')
|
|
| 643 | + return
|
|
| 630 | 644 | |
| 631 | - success = self._workspace_side_bar.remove_workspace(workspace_id)
|
|
| 632 | - if not success:
|
|
| 645 | + for open_chat in open_chats:
|
|
| 646 | + params = ChatListEntryParam(
|
|
| 647 | + workspace_id=new_workspace_id,
|
|
| 648 | + source_workspace_id=workspace_id,
|
|
| 649 | + account=open_chat['account'],
|
|
| 650 | + jid=open_chat['jid'])
|
|
| 651 | + self.activate_action('move-chat-to-workspace',
|
|
| 652 | + params.to_variant())
|
|
| 653 | + |
|
| 654 | + if was_active:
|
|
| 655 | + self.activate_workspace(new_workspace_id)
|
|
| 656 | + |
|
| 657 | + self._workspace_side_bar.remove_workspace(workspace_id)
|
|
| 658 | + self._chat_page.remove_chat_list(workspace_id)
|
|
| 659 | + app.settings.remove_workspace(workspace_id)
|
|
| 660 | + |
|
| 661 | + if open_chats:
|
|
| 662 | + ConfirmationDialog(
|
|
| 663 | + _('Remove Workspace'),
|
|
| 664 | + _('Remove Workspace'),
|
|
| 665 | + _('This workspace contains chats. All chats will be moved to '
|
|
| 666 | + 'the next workspace. Remove anyway?'),
|
|
| 667 | + [DialogButton.make('Cancel',
|
|
| 668 | + text=_('_No')),
|
|
| 669 | + DialogButton.make('Remove',
|
|
| 670 | + callback=_continue_removing_workspace)]
|
|
| 671 | + ).show()
|
|
| 633 | 672 | return
|
| 634 | 673 | |
| 635 | - if was_active:
|
|
| 636 | - new_active_id = self._workspace_side_bar.get_first_workspace()
|
|
| 637 | - self.activate_workspace(new_active_id)
|
|
| 638 | - |
|
| 639 | - self._chat_page.remove_chat_list(workspace_id)
|
|
| 640 | - app.settings.remove_workspace(workspace_id)
|
|
| 674 | + # No chats in chat list, it is save to remove this workspace
|
|
| 675 | + _continue_removing_workspace()
|
|
| 641 | 676 | |
| 642 | 677 | def _activate_workspace(self,
|
| 643 | 678 | _action: Gio.SimpleAction,
|
| ... | ... | @@ -410,6 +410,7 @@ def get_chat_list_row_menu(workspace_id: str, |
| 410 | 410 | menu = GajimMenu()
|
| 411 | 411 | |
| 412 | 412 | params = ChatListEntryParam(workspace_id=workspace_id,
|
| 413 | + source_workspace_id=workspace_id,
|
|
| 413 | 414 | account=account,
|
| 414 | 415 | jid=jid)
|
| 415 | 416 | |
| ... | ... | @@ -423,6 +424,7 @@ def get_chat_list_row_menu(workspace_id: str, |
| 423 | 424 | submenu.add_item(name, 'win.move-chat-to-workspace', params)
|
| 424 | 425 | |
| 425 | 426 | params = ChatListEntryParam(workspace_id='',
|
| 427 | + source_workspace_id=workspace_id,
|
|
| 426 | 428 | account=account,
|
| 427 | 429 | jid=jid)
|
| 428 | 430 | |
| ... | ... | @@ -450,6 +452,7 @@ def get_workspace_params(current_workspace_id: str, |
| 450 | 452 | continue
|
| 451 | 453 | name = app.settings.get_workspace_setting(workspace_id, 'name')
|
| 452 | 454 | params = ChatListEntryParam(workspace_id=workspace_id,
|
| 455 | + source_workspace_id=current_workspace_id,
|
|
| 453 | 456 | account=account,
|
| 454 | 457 | jid=jid)
|
| 455 | 458 | yield name, params
|
| ... | ... | @@ -575,9 +578,13 @@ def get_format_menu() -> GajimMenu: |
| 575 | 578 | |
| 576 | 579 | |
| 577 | 580 | def get_workspace_menu(workspace_id: str) -> GajimMenu:
|
| 581 | + remove_action = 'win.dummy'
|
|
| 582 | + if len(app.settings.get_workspaces()) > 1:
|
|
| 583 | + remove_action = 'win.remove-workspace'
|
|
| 584 | + |
|
| 578 | 585 | menuitems: MenuItemListT = [
|
| 579 | 586 | (_('Edit…'), 'win.edit-workspace', f'"{workspace_id}"'),
|
| 580 | - (_('Remove'), 'win.remove-workspace', f'"{workspace_id}"'),
|
|
| 587 | + (_('Remove'), remove_action, f'"{workspace_id}"'),
|
|
| 581 | 588 | ]
|
| 582 | 589 | |
| 583 | 590 | return GajimMenu.from_list(menuitems)
|
| ... | ... | @@ -55,6 +55,7 @@ class AddChatActionParams(VariantMixin): |
| 55 | 55 | @dataclass
|
| 56 | 56 | class ChatListEntryParam(VariantMixin):
|
| 57 | 57 | workspace_id: str
|
| 58 | + source_workspace_id: str
|
|
| 58 | 59 | account: str
|
| 59 | 60 | jid: JID
|
| 60 | 61 |
| ... | ... | @@ -24,8 +24,6 @@ |
| 24 | 24 | |
| 25 | 25 | from .avatar import make_workspace_avatar
|
| 26 | 26 | from .avatar_selector import AvatarSelector
|
| 27 | -from .dialogs import ConfirmationDialog
|
|
| 28 | -from .dialogs import DialogButton
|
|
| 29 | 27 | from .builder import get_builder
|
| 30 | 28 | from .util import rgba_to_float
|
| 31 | 29 | from .util import make_rgba
|
| ... | ... | @@ -93,25 +91,9 @@ def _on_key_press(self, _widget: Gtk.Widget, event: Gdk.EventKey) -> None: |
| 93 | 91 | self.destroy()
|
| 94 | 92 | |
| 95 | 93 | def _on_remove_workspace(self, _button: Gtk.Button) -> None:
|
| 96 | - def _on_remove():
|
|
| 97 | - assert self._workspace_id is not None
|
|
| 98 | - app.window.remove_workspace(self._workspace_id)
|
|
| 99 | - self.destroy()
|
|
| 100 | 94 | assert self._workspace_id is not None
|
| 101 | - chat_list = app.window.get_chat_list(self._workspace_id)
|
|
| 102 | - open_chats = chat_list.get_open_chats()
|
|
| 103 | - if len(open_chats) > 0:
|
|
| 104 | - ConfirmationDialog(
|
|
| 105 | - _('Remove Workspace'),
|
|
| 106 | - _('Remove Workspace'),
|
|
| 107 | - _('This workspace contains chats. Remove anyway?'),
|
|
| 108 | - [DialogButton.make('Cancel',
|
|
| 109 | - text=_('_No')),
|
|
| 110 | - DialogButton.make('Remove',
|
|
| 111 | - callback=_on_remove)]).show()
|
|
| 112 | - return
|
|
| 113 | - # No chats in chat list, it is save to remove workspace
|
|
| 114 | - _on_remove()
|
|
| 95 | + app.window.remove_workspace(self._workspace_id)
|
|
| 96 | + self.destroy()
|
|
| 115 | 97 | |
| 116 | 98 | def _on_cancel(self, _button: Gtk.Button) -> None:
|
| 117 | 99 | self.destroy()
|
| ... | ... | @@ -158,6 +158,7 @@ def _process_chat_list_drop(self, |
| 158 | 158 | workspace_id = ''
|
| 159 | 159 | |
| 160 | 160 | params = ChatListEntryParam(workspace_id=workspace_id,
|
| 161 | + source_workspace_id='',
|
|
| 161 | 162 | account=account,
|
| 162 | 163 | jid=jid)
|
| 163 | 164 | app.window.activate_action('move-chat-to-workspace',
|
| ... | ... | @@ -221,12 +222,18 @@ def store_workspace_order(self) -> None: |
| 221 | 222 | order.remove('add')
|
| 222 | 223 | app.settings.set_app_setting('workspace_order', order)
|
| 223 | 224 | |
| 224 | - def remove_workspace(self, workspace_id: str) -> bool:
|
|
| 225 | - if len(self._workspaces) == 1:
|
|
| 226 | - return False
|
|
| 225 | + def remove_workspace(self, workspace_id: str) -> None:
|
|
| 227 | 226 | row = self._workspaces.pop(workspace_id)
|
| 228 | 227 | self.remove(row)
|
| 229 | - return True
|
|
| 228 | + |
|
| 229 | + def get_other_workspace(self,
|
|
| 230 | + exclude_workspace_id: str
|
|
| 231 | + ) -> Optional[str]:
|
|
| 232 | + |
|
| 233 | + for workspace in self._workspaces.values():
|
|
| 234 | + if workspace.workspace_id != exclude_workspace_id:
|
|
| 235 | + return workspace.workspace_id
|
|
| 236 | + return None
|
|
| 230 | 237 | |
| 231 | 238 | def activate_workspace(self, workspace_id: str) -> None:
|
| 232 | 239 | row = cast(CommonWorkspace, self.get_selected_row())
|