Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
91cb768e
by Philipp Hörist at 2022-09-04T16:31:21+00:00
14 changed files:
- gajim/common/app.py
- gajim/common/application.py
- gajim/common/events.py
- gajim/common/modules/muc.py
- gajim/common/storage/base.py
- + gajim/common/storage/events.py
- gajim/gtk/chat_banner.py
- gajim/gtk/chat_stack.py
- gajim/gtk/control.py
- gajim/gtk/conversation/rows/muc_join_left.py
- gajim/gtk/conversation/view.py
- gajim/gtk/groupchat_nick_completion.py
- gajim/gtk/groupchat_roster.py
- pyrightconfig.json
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 |
| ... | ... | @@ -45,6 +45,7 @@ |
| 45 | 45 | from gi.repository import GLib
|
| 46 | 46 | |
| 47 | 47 | from gajim.common import app
|
| 48 | +from gajim.common import events
|
|
| 48 | 49 | from gajim.common import helpers
|
| 49 | 50 | from gajim.common import types
|
| 50 | 51 | from gajim.common.const import ClientState, KindConstant
|
| ... | ... | @@ -460,7 +461,11 @@ def _on_disco_result_after_config(self, task: Task) -> None: |
| 460 | 461 | self._log.info('Configuration finished: %s', jid)
|
| 461 | 462 | |
| 462 | 463 | room = self._get_contact(jid.bare)
|
| 463 | - room.notify('room-config-finished')
|
|
| 464 | + event = events.MUCRoomConfigFinished(timestamp=time.time())
|
|
| 465 | + |
|
| 466 | + assert isinstance(room, GroupchatContact)
|
|
| 467 | + app.storage.events.store(room, event)
|
|
| 468 | + room.notify('room-config-finished', event)
|
|
| 464 | 469 | |
| 465 | 470 | def update_presence(self) -> None:
|
| 466 | 471 | mucs = self._get_mucs_with_state([MUCJoinedState.JOINED,
|
| ... | ... | @@ -540,6 +545,11 @@ def _on_error_presence(self, |
| 540 | 545 | self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
|
| 541 | 546 | |
| 542 | 547 | else:
|
| 548 | + event = events.MUCRoomPresenceError(
|
|
| 549 | + timestamp=time.time(),
|
|
| 550 | + error=properties.error)
|
|
| 551 | + assert isinstance(room, GroupchatContact)
|
|
| 552 | + app.storage.events.store(room, event)
|
|
| 543 | 553 | room.notify('room-presence-error', properties)
|
| 544 | 554 | |
| 545 | 555 | def _on_muc_user_presence(self,
|
| ... | ... | @@ -564,7 +574,15 @@ def _on_muc_user_presence(self, |
| 564 | 574 | self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
|
| 565 | 575 | self._con.get_module('Bookmarks').remove(room_jid)
|
| 566 | 576 | room.set_not_joined()
|
| 567 | - room.notify('room-destroyed', properties)
|
|
| 577 | + |
|
| 578 | + event = events.MUCRoomDestroyed(
|
|
| 579 | + timestamp=time.time(),
|
|
| 580 | + reason=properties.muc_destroyed.reason,
|
|
| 581 | + alternate=properties.muc_destroyed.alternate)
|
|
| 582 | + assert isinstance(room, GroupchatContact)
|
|
| 583 | + app.storage.events.store(room, event)
|
|
| 584 | + |
|
| 585 | + room.notify('room-destroyed', event)
|
|
| 568 | 586 | return
|
| 569 | 587 | |
| 570 | 588 | if properties.is_nickname_changed:
|
| ... | ... | @@ -592,10 +610,16 @@ def _on_muc_user_presence(self, |
| 592 | 610 | presence,
|
| 593 | 611 | occupant)
|
| 594 | 612 | |
| 595 | - room.notify('user-nickname-changed',
|
|
| 596 | - occupant,
|
|
| 597 | - new_occupant,
|
|
| 598 | - properties)
|
|
| 613 | + event = events.MUCNicknameChanged(
|
|
| 614 | + timestamp=time.time(),
|
|
| 615 | + is_self=properties.is_muc_self_presence,
|
|
| 616 | + new_name=new_occupant.name,
|
|
| 617 | + old_name=occupant.name)
|
|
| 618 | + |
|
| 619 | + assert isinstance(room, GroupchatContact)
|
|
| 620 | + app.storage.events.store(room, event)
|
|
| 621 | + |
|
| 622 | + room.notify('user-nickname-changed', event, occupant, new_occupant)
|
|
| 599 | 623 | return
|
| 600 | 624 | |
| 601 | 625 | is_joined = self._is_user_joined(properties.jid)
|
| ... | ... | @@ -624,7 +648,16 @@ def _on_muc_user_presence(self, |
| 624 | 648 | if properties.is_muc_self_presence and properties.is_kicked:
|
| 625 | 649 | self._set_muc_state(room_jid, MUCJoinedState.NOT_JOINED)
|
| 626 | 650 | room.set_not_joined()
|
| 627 | - room.notify('room-kicked', properties)
|
|
| 651 | + |
|
| 652 | + event = events.MUCRoomKicked(
|
|
| 653 | + timestamp=time.time(),
|
|
| 654 | + status_codes=properties.muc_status_codes,
|
|
| 655 | + reason=properties.muc_user.reason,
|
|
| 656 | + actor=properties.muc_user.actor)
|
|
| 657 | + assert isinstance(room, GroupchatContact)
|
|
| 658 | + app.storage.events.store(room, event)
|
|
| 659 | + room.notify('room-kicked', event)
|
|
| 660 | + |
|
| 628 | 661 | status_codes = properties.muc_status_codes or []
|
| 629 | 662 | if StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
|
| 630 | 663 | self._start_rejoin_timeout(room_jid)
|
| ... | ... | @@ -652,30 +685,84 @@ def _process_occupant_presence_change( |
| 652 | 685 | presence: MUCPresenceData,
|
| 653 | 686 | occupant: GroupchatParticipant) -> None:
|
| 654 | 687 | |
| 688 | + timestamp = time.time()
|
|
| 689 | + |
|
| 655 | 690 | if not occupant.is_available and presence.available:
|
| 691 | + |
|
| 692 | + event = events.MUCUserJoined(
|
|
| 693 | + timestamp=timestamp,
|
|
| 694 | + is_self=properties.is_muc_self_presence,
|
|
| 695 | + nick=occupant.name,
|
|
| 696 | + status_codes=properties.muc_status_codes)
|
|
| 697 | + |
|
| 698 | + if occupant.room.is_joined or properties.is_muc_self_presence:
|
|
| 699 | + # Don’t store initial presences on join
|
|
| 700 | + app.storage.events.store(occupant.room, event)
|
|
| 701 | + |
|
| 656 | 702 | occupant.update_presence(presence)
|
| 657 | - occupant.notify('user-joined', properties)
|
|
| 703 | + occupant.notify('user-joined', event)
|
|
| 658 | 704 | return
|
| 659 | 705 | |
| 660 | 706 | if not presence.available:
|
| 707 | + |
|
| 708 | + event = events.MUCUserLeft(
|
|
| 709 | + timestamp=timestamp,
|
|
| 710 | + is_self=properties.is_muc_self_presence,
|
|
| 711 | + nick=occupant.name,
|
|
| 712 | + status_codes=properties.muc_status_codes,
|
|
| 713 | + reason=properties.muc_user.reason,
|
|
| 714 | + actor=properties.muc_user.actor)
|
|
| 715 | + |
|
| 716 | + app.storage.events.store(occupant.room, event)
|
|
| 661 | 717 | occupant.update_presence(presence)
|
| 662 | - occupant.notify('user-left', properties)
|
|
| 718 | + occupant.notify('user-left', event)
|
|
| 663 | 719 | return
|
| 664 | 720 | |
| 665 | - signals: list[str] = []
|
|
| 721 | + signals_and_events: list[tuple[str, Any]] = []
|
|
| 722 | + |
|
| 666 | 723 | if occupant.affiliation != presence.affiliation:
|
| 667 | - signals.append('user-affiliation-changed')
|
|
| 724 | + |
|
| 725 | + event = events.MUCUserAffiliationChanged(
|
|
| 726 | + timestamp=timestamp,
|
|
| 727 | + is_self=properties.is_muc_self_presence,
|
|
| 728 | + nick=occupant.name,
|
|
| 729 | + affiliation=presence.affiliation,
|
|
| 730 | + reason=properties.muc_user.reason,
|
|
| 731 | + actor=properties.muc_user.actor)
|
|
| 732 | + |
|
| 733 | + app.storage.events.store(occupant.room, event)
|
|
| 734 | + signals_and_events.append(('user-affiliation-changed', event))
|
|
| 668 | 735 | |
| 669 | 736 | if occupant.role != presence.role:
|
| 670 | - signals.append('user-role-changed')
|
|
| 737 | + |
|
| 738 | + event = events.MUCUserRoleChanged(
|
|
| 739 | + timestamp=timestamp,
|
|
| 740 | + is_self=properties.is_muc_self_presence,
|
|
| 741 | + nick=occupant.name,
|
|
| 742 | + role=presence.role,
|
|
| 743 | + reason=properties.muc_user.reason,
|
|
| 744 | + actor=properties.muc_user.actor)
|
|
| 745 | + |
|
| 746 | + app.storage.events.store(occupant.room, event)
|
|
| 747 | + signals_and_events.append(('user-role-changed', event))
|
|
| 671 | 748 | |
| 672 | 749 | if (occupant.status != presence.status or
|
| 673 | 750 | occupant.show != presence.show):
|
| 674 | - signals.append('user-status-show-changed')
|
|
| 751 | + |
|
| 752 | + event = events.MUCUserStatusShowChanged(
|
|
| 753 | + timestamp=timestamp,
|
|
| 754 | + is_self=properties.is_muc_self_presence,
|
|
| 755 | + nick=occupant.name,
|
|
| 756 | + status=properties.status,
|
|
| 757 | + show_value=properties.show.value)
|
|
| 758 | + |
|
| 759 | + app.storage.events.store(occupant, event)
|
|
| 760 | + app.storage.events.store(occupant.room, event)
|
|
| 761 | + signals_and_events.append(('user-status-show-changed', event))
|
|
| 675 | 762 | |
| 676 | 763 | occupant.update_presence(presence)
|
| 677 | - for signal in signals:
|
|
| 678 | - occupant.notify(signal, properties)
|
|
| 764 | + for signal, event in signals_and_events:
|
|
| 765 | + occupant.notify(signal, event)
|
|
| 679 | 766 | |
| 680 | 767 | def _process_user_presence(self,
|
| 681 | 768 | properties: PresenceProperties
|
| ... | ... | @@ -953,12 +1040,21 @@ def _on_config_change(self, |
| 953 | 1040 | if not properties.is_muc_config_change:
|
| 954 | 1041 | return
|
| 955 | 1042 | |
| 956 | - room_jid = str(properties.muc_jid)
|
|
| 957 | 1043 | self._log.info('Received config change: %s %s',
|
| 958 | - room_jid, properties.muc_status_codes)
|
|
| 1044 | + properties.muc_jid, properties.muc_status_codes)
|
|
| 959 | 1045 | |
| 960 | - room = self._get_contact(room_jid)
|
|
| 961 | - room.notify('room-config-changed', properties)
|
|
| 1046 | + assert properties.muc_status_codes is not None
|
|
| 1047 | + event = events.MUCRoomConfigChanged(
|
|
| 1048 | + timestamp=time.time(),
|
|
| 1049 | + status_codes=properties.muc_status_codes)
|
|
| 1050 | + |
|
| 1051 | + assert properties.muc_jid is not None
|
|
| 1052 | + room = self._get_contact(properties.muc_jid)
|
|
| 1053 | + |
|
| 1054 | + assert isinstance(room, GroupchatContact)
|
|
| 1055 | + app.storage.events.store(room, event)
|
|
| 1056 | + |
|
| 1057 | + room.notify('room-config-changed', event)
|
|
| 962 | 1058 | |
| 963 | 1059 | raise nbxmpp.NodeProcessed
|
| 964 | 1060 |
| ... | ... | @@ -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 Iterator
|
|
| 20 | +from typing import NamedTuple
|
|
| 21 | + |
|
| 22 | +import json
|
|
| 23 | +import sqlite3
|
|
| 24 | +import logging
|
|
| 25 | +from collections import namedtuple
|
|
| 26 | + |
|
| 27 | +from nbxmpp.protocol import JID
|
|
| 28 | + |
|
| 29 | +from gajim.common import events
|
|
| 30 | +from gajim.common.storage.base import SqliteStorage
|
|
| 31 | +from gajim.common.storage.base import Encoder
|
|
| 32 | +from gajim.common.types import ChatContactT
|
|
| 33 | + |
|
| 34 | + |
|
| 35 | +EVENTS_SQL_STATEMENT = '''
|
|
| 36 | + CREATE TABLE events (
|
|
| 37 | + account TEXT,
|
|
| 38 | + jid TEXT,
|
|
| 39 | + event TEXT,
|
|
| 40 | + timestamp REAL,
|
|
| 41 | + data TEXT);
|
|
| 42 | + |
|
| 43 | + CREATE INDEX idx_account_jid ON events(account, jid);
|
|
| 44 | + |
|
| 45 | + PRAGMA user_version=1;
|
|
| 46 | + '''
|
|
| 47 | + |
|
| 48 | +EVENT_CLASSES: dict[str, Any] = {
|
|
| 49 | + 'muc-nickname-changed': events.MUCNicknameChanged,
|
|
| 50 | + 'muc-room-config-changed': events.MUCRoomConfigChanged,
|
|
| 51 | + 'muc-room-config-finished': events.MUCRoomConfigFinished,
|
|
| 52 | + 'muc-room-presence-error': events.MUCRoomPresenceError,
|
|
| 53 | + 'muc-room-kicked': events.MUCRoomKicked,
|
|
| 54 | + 'muc-room-destroyed': events.MUCRoomDestroyed,
|
|
| 55 | + 'muc-user-joined': events.MUCUserJoined,
|
|
| 56 | + 'muc-user-left': events.MUCUserLeft,
|
|
| 57 | + 'muc-user-role-changed': events.MUCUserRoleChanged,
|
|
| 58 | + 'muc-user-affiliation-changed': events.MUCUserAffiliationChanged,
|
|
| 59 | + 'muc-user-status-show-changed': events.MUCUserStatusShowChanged,
|
|
| 60 | +}
|
|
| 61 | + |
|
| 62 | +log = logging.getLogger('gajim.c.storage.events')
|
|
| 63 | + |
|
| 64 | + |
|
| 65 | +class EventRow(NamedTuple):
|
|
| 66 | + account: str
|
|
| 67 | + jid: JID
|
|
| 68 | + event: str
|
|
| 69 | + timestamp: float
|
|
| 70 | + data: dict[str, Any]
|
|
| 71 | + |
|
| 72 | + |
|
| 73 | +class EventStorage(SqliteStorage):
|
|
| 74 | + def __init__(self):
|
|
| 75 | + SqliteStorage.__init__(self,
|
|
| 76 | + log,
|
|
| 77 | + None,
|
|
| 78 | + EVENTS_SQL_STATEMENT)
|
|
| 79 | + |
|
| 80 | + def init(self, **kwargs: Any) -> None:
|
|
| 81 | + SqliteStorage.init(self,
|
|
| 82 | + detect_types=sqlite3.PARSE_COLNAMES)
|
|
| 83 | + self._con.row_factory = self._namedtuple_factory
|
|
| 84 | + |
|
| 85 | + @staticmethod
|
|
| 86 | + def _namedtuple_factory(cursor: sqlite3.Cursor,
|
|
| 87 | + row: tuple[Any, ...]) -> NamedTuple:
|
|
| 88 | + |
|
| 89 | + assert cursor.description is not None
|
|
| 90 | + fields = [col[0] for col in cursor.description]
|
|
| 91 | + Row = namedtuple('Row', fields) # type: ignore
|
|
| 92 | + return Row(*row)
|
|
| 93 | + |
|
| 94 | + def _migrate(self) -> None:
|
|
| 95 | + pass
|
|
| 96 | + |
|
| 97 | + def store(self,
|
|
| 98 | + contact: ChatContactT,
|
|
| 99 | + event: Any
|
|
| 100 | + ) -> None:
|
|
| 101 | + |
|
| 102 | + event_dict = dataclasses.asdict(event)
|
|
| 103 | + name = event_dict.pop('name')
|
|
| 104 | + timestamp = event_dict.pop('timestamp')
|
|
| 105 | + |
|
| 106 | + insert_sql = '''
|
|
| 107 | + INSERT INTO events(account, jid, event, timestamp, data)
|
|
| 108 | + VALUES(?, ?, ?, ?, ?)'''
|
|
| 109 | + |
|
| 110 | + self._con.execute(insert_sql, (contact.account,
|
|
| 111 | + contact.jid,
|
|
| 112 | + name,
|
|
| 113 | + timestamp,
|
|
| 114 | + json.dumps(event_dict, cls=Encoder)))
|
|
| 115 | + |
|
| 116 | + def load(self,
|
|
| 117 | + contact: ChatContactT,
|
|
| 118 | + before: bool,
|
|
| 119 | + timestamp: float
|
|
| 120 | + ) -> Iterator[EventRow]:
|
|
| 121 | + |
|
| 122 | + time_operator = '<' if before else '>'
|
|
| 123 | + |
|
| 124 | + insert_sql = '''
|
|
| 125 | + SELECT account, jid, event, timestamp, data as "data [JSON]"
|
|
| 126 | + FROM events WHERE account=? AND jid=? AND timestamp %s ?
|
|
| 127 | + ''' % time_operator
|
|
| 128 | + |
|
| 129 | + for row in self._con.execute(insert_sql, (contact.account,
|
|
| 130 | + contact.jid,
|
|
| 131 | + timestamp)).fetchall():
|
|
| 132 | + event_class = EVENT_CLASSES[row.event]
|
|
| 133 | + 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
|
| ... | ... | @@ -195,13 +193,7 @@ def _on_room_voice_request(self, *args: Any) -> None: |
| 195 | 193 | self._voice_requests_button.set_no_show_all(False)
|
| 196 | 194 | self._voice_requests_button.show_all()
|
| 197 | 195 | |
| 198 | - def _on_user_role_changed(self,
|
|
| 199 | - _contact: GroupchatContact,
|
|
| 200 | - _signal_name: str,
|
|
| 201 | - user_contact: GroupchatParticipant,
|
|
| 202 | - properties: PresenceProperties
|
|
| 203 | - ) -> None:
|
|
| 204 | - |
|
| 196 | + def _on_user_role_changed(self, *args: Any) -> None:
|
|
| 205 | 197 | self._update_content()
|
| 206 | 198 | |
| 207 | 199 | def _on_user_state_changed(self, *args: Any) -> None:
|
| ... | ... | @@ -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
|
| ... | ... | @@ -31,9 +28,7 @@ |
| 31 | 28 | |
| 32 | 29 | from nbxmpp import JID
|
| 33 | 30 | from nbxmpp.const import StatusCode
|
| 34 | -from nbxmpp.structs import MessageProperties
|
|
| 35 | 31 | from nbxmpp.structs import MucSubject
|
| 36 | -from nbxmpp.structs import PresenceProperties
|
|
| 37 | 32 | from nbxmpp.modules.security_labels import Displaymarking
|
| 38 | 33 | |
| 39 | 34 | from gajim.common import app
|
| ... | ... | @@ -100,12 +95,6 @@ def __init__(self) -> None: |
| 100 | 95 | self._muc_subjects: dict[
|
| 101 | 96 | types.ChatContactT, tuple[float, MucSubject]] = {}
|
| 102 | 97 | |
| 103 | - # Store 100 info messages per contact on a FIFO basis
|
|
| 104 | - self._info_messages: dict[
|
|
| 105 | - types.ChatContactT,
|
|
| 106 | - deque[tuple[float, str]]] = defaultdict(
|
|
| 107 | - partial(deque, maxlen=100)) # pyright: ignore
|
|
| 108 | - |
|
| 109 | 98 | self.widget = cast(Gtk.Box, self._ui.get_object('control_box'))
|
| 110 | 99 | self.widget.show_all()
|
| 111 | 100 | |
| ... | ... | @@ -177,7 +166,8 @@ def switch_contact(self, contact: Union[BareContact, |
| 177 | 166 | |
| 178 | 167 | if isinstance(contact, GroupchatParticipant):
|
| 179 | 168 | contact.multi_connect({
|
| 180 | - 'user-status-show-changed': self._on_user_status_show_changed,
|
|
| 169 | + 'user-status-show-changed':
|
|
| 170 | + self._on_participant_status_show_changed,
|
|
| 181 | 171 | })
|
| 182 | 172 | |
| 183 | 173 | elif isinstance(contact, GroupchatContact):
|
| ... | ... | @@ -186,8 +176,7 @@ def switch_contact(self, contact: Union[BareContact, |
| 186 | 176 | 'user-left': self._on_user_left,
|
| 187 | 177 | 'user-affiliation-changed': self._on_user_affiliation_changed,
|
| 188 | 178 | 'user-role-changed': self._on_user_role_changed,
|
| 189 | - 'user-status-show-changed':
|
|
| 190 | - self._on_muc_user_status_show_changed,
|
|
| 179 | + 'user-status-show-changed': self._on_user_status_show_changed,
|
|
| 191 | 180 | 'user-nickname-changed': self._on_user_nickname_changed,
|
| 192 | 181 | 'room-kicked': self._on_room_kicked,
|
| 193 | 182 | 'room-destroyed': self._on_room_destroyed,
|
| ... | ... | @@ -205,9 +194,6 @@ def switch_contact(self, contact: Union[BareContact, |
| 205 | 194 | for transfer in transfers:
|
| 206 | 195 | self.add_file_transfer(transfer)
|
| 207 | 196 | |
| 208 | - for timestamp, message in self._info_messages[contact]:
|
|
| 209 | - self.add_info_message(message, timestamp)
|
|
| 210 | - |
|
| 211 | 197 | if isinstance(contact, GroupchatContact):
|
| 212 | 198 | if (app.settings.get('show_subject_on_join') or
|
| 213 | 199 | not contact.is_joining):
|
| ... | ... | @@ -501,10 +487,6 @@ def add_info_message(self, |
| 501 | 487 | timestamp: Optional[float] = None
|
| 502 | 488 | ) -> None:
|
| 503 | 489 | |
| 504 | - if timestamp is None:
|
|
| 505 | - assert self._contact is not None
|
|
| 506 | - self._info_messages[self._contact].append((time.time(), text))
|
|
| 507 | - |
|
| 508 | 490 | self.conversation_view.add_info_message(text, timestamp)
|
| 509 | 491 | |
| 510 | 492 | def add_file_transfer(self, transfer: HTTPFileTransfer) -> None:
|
| ... | ... | @@ -619,6 +601,43 @@ def _fetch_n_lines_history(self, |
| 619 | 601 | # if self.conversation_view.reduce_message_count(before):
|
| 620 | 602 | # self._scrolled_view.set_history_complete(before, False)
|
| 621 | 603 | |
| 604 | + assert self._contact is not None
|
|
| 605 | + for event in app.storage.events.load(self._contact, before, timestamp):
|
|
| 606 | + if isinstance(event, events.MUCUserJoined):
|
|
| 607 | + self._process_muc_user_joined(event)
|
|
| 608 | + |
|
| 609 | + elif isinstance(event, events.MUCUserLeft):
|
|
| 610 | + self._process_muc_user_left(event)
|
|
| 611 | + |
|
| 612 | + elif isinstance(event, events.MUCNicknameChanged):
|
|
| 613 | + self._process_muc_nickname_changed(event)
|
|
| 614 | + |
|
| 615 | + elif isinstance(event, events.MUCRoomKicked):
|
|
| 616 | + self._process_muc_room_kicked(event)
|
|
| 617 | + |
|
| 618 | + elif isinstance(event, events.MUCUserAffiliationChanged):
|
|
| 619 | + self._process_muc_user_affiliation_changed(event)
|
|
| 620 | + |
|
| 621 | + elif isinstance(event, events.MUCUserRoleChanged):
|
|
| 622 | + self._process_muc_user_role_changed(event)
|
|
| 623 | + |
|
| 624 | + elif isinstance(event, events.MUCUserStatusShowChanged):
|
|
| 625 | + self._process_muc_user_status_show_changed(event)
|
|
| 626 | + |
|
| 627 | + elif isinstance(event, events.MUCRoomConfigChanged):
|
|
| 628 | + self._process_muc_room_config_changed(event)
|
|
| 629 | + |
|
| 630 | + elif isinstance(event, events.MUCRoomConfigFinished):
|
|
| 631 | + self._process_muc_room_config_finished(event)
|
|
| 632 | + |
|
| 633 | + elif isinstance(event, events.MUCRoomPresenceError):
|
|
| 634 | + self._process_muc_room_presence_error(event)
|
|
| 635 | + |
|
| 636 | + elif isinstance(event, events.MUCRoomDestroyed):
|
|
| 637 | + self._process_muc_room_destroyed(event)
|
|
| 638 | + else:
|
|
| 639 | + raise ValueError('Unknown event: %s' % event)
|
|
| 640 | + |
|
| 622 | 641 | self._scrolled_view.block_signals(False)
|
| 623 | 642 | |
| 624 | 643 | def add_messages(self, messages: list[ConversationRow]):
|
| ... | ... | @@ -703,120 +722,228 @@ def add_message(self, |
| 703 | 722 | additional_data=additional_data)
|
| 704 | 723 | |
| 705 | 724 | def _on_user_nickname_changed(self,
|
| 706 | - contact: types.GroupchatContact,
|
|
| 725 | + _contact: types.GroupchatContact,
|
|
| 707 | 726 | _signal_name: str,
|
| 708 | - old_contact: types.GroupchatParticipant,
|
|
| 709 | - new_contact: types.GroupchatParticipant,
|
|
| 710 | - properties: PresenceProperties
|
|
| 727 | + event: events.MUCNicknameChanged,
|
|
| 728 | + _old_contact: types.GroupchatParticipant,
|
|
| 729 | + _new_contact: types.GroupchatParticipant
|
|
| 711 | 730 | ) -> None:
|
| 712 | 731 | |
| 713 | - if properties.is_muc_self_presence:
|
|
| 714 | - message = _('You are now known as %s') % new_contact.name
|
|
| 732 | + self._process_muc_nickname_changed(event)
|
|
| 733 | + |
|
| 734 | + def _process_muc_nickname_changed(self,
|
|
| 735 | + event: events.MUCNicknameChanged
|
|
| 736 | + ) -> None:
|
|
| 737 | + |
|
| 738 | + if event.is_self:
|
|
| 739 | + message = _('You are now known as %s') % event.new_name
|
|
| 715 | 740 | else:
|
| 716 | 741 | message = _('{nick} is now known '
|
| 717 | - 'as {new_nick}').format(nick=old_contact.name,
|
|
| 718 | - new_nick=new_contact.name)
|
|
| 742 | + 'as {new_nick}').format(nick=event.old_name,
|
|
| 743 | + new_nick=event.new_name)
|
|
| 744 | + self.add_info_message(message, event.timestamp)
|
|
| 719 | 745 | |
| 720 | - self.add_info_message(message)
|
|
| 746 | + def _on_room_kicked(self,
|
|
| 747 | + _contact: GroupchatContact,
|
|
| 748 | + _signal_name: str,
|
|
| 749 | + event: events.MUCRoomKicked
|
|
| 750 | + ) -> None:
|
|
| 721 | 751 | |
| 722 | - def _on_muc_user_status_show_changed(self,
|
|
| 723 | - contact: GroupchatContact,
|
|
| 724 | - _signal_name: str,
|
|
| 725 | - user_contact: GroupchatParticipant,
|
|
| 726 | - properties: PresenceProperties
|
|
| 727 | - ) -> None:
|
|
| 752 | + self._process_muc_room_kicked(event)
|
|
| 728 | 753 | |
| 729 | - if not contact.settings.get('print_status'):
|
|
| 754 | + def _process_muc_room_kicked(self, event: events.MUCRoomKicked) -> None:
|
|
| 755 | + status_codes = event.status_codes or []
|
|
| 756 | + |
|
| 757 | + reason = event.reason
|
|
| 758 | + reason = '' if reason is None else f': {reason}'
|
|
| 759 | + |
|
| 760 | + actor = event.actor
|
|
| 761 | + # Group Chat: You have been kicked by Alice
|
|
| 762 | + actor = '' if actor is None else _(' by {actor}').format(
|
|
| 763 | + actor=actor)
|
|
| 764 | + |
|
| 765 | + # Group Chat: We have been removed from the room by Alice: reason
|
|
| 766 | + message = _('You have been removed from the '
|
|
| 767 | + 'group chat{actor}{reason}')
|
|
| 768 | + |
|
| 769 | + if StatusCode.REMOVED_ERROR in status_codes:
|
|
| 770 | + # Handle 333 before 307, some MUCs add both
|
|
| 771 | + # Group Chat: Server kicked us because of an server error
|
|
| 772 | + message = _('You have left due '
|
|
| 773 | + 'to an error{reason}').format(reason=reason)
|
|
| 774 | + |
|
| 775 | + elif StatusCode.REMOVED_KICKED in status_codes:
|
|
| 776 | + # Group Chat: We have been kicked by Alice: reason
|
|
| 777 | + message = _('You have been '
|
|
| 778 | + 'kicked{actor}{reason}').format(actor=actor,
|
|
| 779 | + reason=reason)
|
|
| 780 | + |
|
| 781 | + elif StatusCode.REMOVED_BANNED in status_codes:
|
|
| 782 | + # Group Chat: We have been banned by Alice: reason
|
|
| 783 | + message = _('You have been '
|
|
| 784 | + 'banned{actor}{reason}').format(actor=actor,
|
|
| 785 | + reason=reason)
|
|
| 786 | + |
|
| 787 | + elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
|
|
| 788 | + # Group Chat: We were removed because of an affiliation change
|
|
| 789 | + reason = _(': Affiliation changed')
|
|
| 790 | + message = message.format(actor=actor, reason=reason)
|
|
| 791 | + |
|
| 792 | + elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
|
|
| 793 | + # Group Chat: Room configuration changed
|
|
| 794 | + reason = _(': Group chat configuration changed to members-only')
|
|
| 795 | + message = message.format(actor=actor, reason=reason)
|
|
| 796 | + |
|
| 797 | + elif StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
|
|
| 798 | + # Group Chat: Kicked because of server shutdown
|
|
| 799 | + reason = ': System shutdown'
|
|
| 800 | + message = message.format(actor=actor, reason=reason)
|
|
| 801 | + |
|
| 802 | + else:
|
|
| 803 | + # No formatted message available
|
|
| 730 | 804 | return
|
| 731 | 805 | |
| 732 | - self.conversation_view.add_user_status(user_contact.name,
|
|
| 733 | - user_contact.show.value,
|
|
| 734 | - user_contact.status)
|
|
| 806 | + self.add_info_message(message, event.timestamp)
|
|
| 735 | 807 | |
| 736 | - def _on_user_status_show_changed(self,
|
|
| 737 | - _user_contact: GroupchatParticipant,
|
|
| 808 | + def _on_user_affiliation_changed(self,
|
|
| 809 | + _contact: GroupchatContact,
|
|
| 738 | 810 | _signal_name: str,
|
| 739 | - properties: PresenceProperties
|
|
| 811 | + user_contact: GroupchatParticipant,
|
|
| 812 | + event: events.MUCUserAffiliationChanged
|
|
| 740 | 813 | ) -> None:
|
| 741 | 814 | |
| 742 | - nick = properties.muc_nickname
|
|
| 743 | - status = properties.status
|
|
| 744 | - status = '' if not status else f' - {status}'
|
|
| 745 | - assert properties.show is not None
|
|
| 746 | - show = helpers.get_uf_show(properties.show.value)
|
|
| 815 | + self._process_muc_user_affiliation_changed(event)
|
|
| 747 | 816 | |
| 748 | - assert isinstance(self.contact, GroupchatParticipant)
|
|
| 749 | - if not self.contact.room.settings.get('print_status'):
|
|
| 750 | - return
|
|
| 817 | + def _process_muc_user_affiliation_changed(
|
|
| 818 | + self,
|
|
| 819 | + event: events.MUCUserAffiliationChanged) -> None:
|
|
| 751 | 820 | |
| 752 | - if properties.is_muc_self_presence:
|
|
| 753 | - message = _('You are now {show}{status}').format(show=show,
|
|
| 754 | - status=status)
|
|
| 821 | + affiliation = helpers.get_uf_affiliation(event.affiliation)
|
|
| 822 | + |
|
| 823 | + reason = event.reason
|
|
| 824 | + reason = '' if reason is None else f': {reason}'
|
|
| 825 | + |
|
| 826 | + actor = event.actor
|
|
| 827 | + # Group Chat: You have been kicked by Alice
|
|
| 828 | + actor = '' if actor is None else _(' by {actor}').format(
|
|
| 829 | + actor=actor)
|
|
| 755 | 830 | |
| 831 | + if event.is_self:
|
|
| 832 | + message = _('** Your Affiliation has been set to '
|
|
| 833 | + '{affiliation}{actor}{reason}').format(
|
|
| 834 | + affiliation=affiliation,
|
|
| 835 | + actor=actor,
|
|
| 836 | + reason=reason)
|
|
| 756 | 837 | else:
|
| 757 | - message = _('{nick} is now {show}{status}').format(nick=nick,
|
|
| 758 | - show=show,
|
|
| 759 | - status=status)
|
|
| 760 | - self.add_info_message(message)
|
|
| 838 | + message = _('** Affiliation of {nick} has been set to '
|
|
| 839 | + '{affiliation}{actor}{reason}').format(
|
|
| 840 | + nick=event.nick,
|
|
| 841 | + affiliation=affiliation,
|
|
| 842 | + actor=actor,
|
|
| 843 | + reason=reason)
|
|
| 761 | 844 | |
| 762 | - def add_muc_message(self,
|
|
| 763 | - text: str,
|
|
| 764 | - tim: float,
|
|
| 765 | - contact: str = '',
|
|
| 766 | - displaymarking: Optional[Displaymarking] = None,
|
|
| 767 | - message_id: Optional[str] = None,
|
|
| 768 | - stanza_id: Optional[str] = None,
|
|
| 769 | - additional_data: Optional[AdditionalDataDict] = None,
|
|
| 770 | - ) -> None:
|
|
| 845 | + self.add_info_message(message, event.timestamp)
|
|
| 771 | 846 | |
| 772 | - assert isinstance(self._contact, GroupchatContact)
|
|
| 847 | + def _on_user_role_changed(self,
|
|
| 848 | + _contact: GroupchatContact,
|
|
| 849 | + _signal_name: str,
|
|
| 850 | + user_contact: GroupchatParticipant,
|
|
| 851 | + event: events.MUCUserRoleChanged
|
|
| 852 | + ) -> None:
|
|
| 773 | 853 | |
| 774 | - if contact == self._contact.nickname:
|
|
| 775 | - kind = 'outgoing'
|
|
| 854 | + self._process_muc_user_role_changed(event)
|
|
| 855 | + |
|
| 856 | + def _process_muc_user_role_changed(self,
|
|
| 857 | + event: events.MUCUserRoleChanged
|
|
| 858 | + ) -> None:
|
|
| 859 | + |
|
| 860 | + role = helpers.get_uf_role(event.role)
|
|
| 861 | + nick = event.nick
|
|
| 862 | + |
|
| 863 | + reason = event.reason
|
|
| 864 | + reason = '' if reason is None else f': {reason}'
|
|
| 865 | + |
|
| 866 | + actor = event.actor
|
|
| 867 | + # Group Chat: You have been kicked by Alice
|
|
| 868 | + actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 869 | + |
|
| 870 | + if event.is_self:
|
|
| 871 | + message = _('** Your Role has been set to '
|
|
| 872 | + '{role}{actor}{reason}').format(role=role,
|
|
| 873 | + actor=actor,
|
|
| 874 | + reason=reason)
|
|
| 776 | 875 | else:
|
| 777 | - kind = 'incoming'
|
|
| 778 | - # muc-specific chatstate
|
|
| 876 | + message = _('** Role of {nick} has been set to '
|
|
| 877 | + '{role}{actor}{reason}').format(nick=nick,
|
|
| 878 | + role=role,
|
|
| 879 | + actor=actor,
|
|
| 880 | + reason=reason)
|
|
| 881 | + self.add_info_message(message, event.timestamp)
|
|
| 779 | 882 | |
| 780 | - self._add_message(text,
|
|
| 781 | - kind,
|
|
| 782 | - contact,
|
|
| 783 | - tim,
|
|
| 784 | - displaymarking=displaymarking,
|
|
| 785 | - message_id=message_id,
|
|
| 786 | - stanza_id=stanza_id,
|
|
| 787 | - additional_data=additional_data)
|
|
| 883 | + def _on_user_status_show_changed(self,
|
|
| 884 | + contact: GroupchatContact,
|
|
| 885 | + _signal_name: str,
|
|
| 886 | + _user_contact: GroupchatParticipant,
|
|
| 887 | + event: events.MUCUserStatusShowChanged
|
|
| 888 | + ) -> None:
|
|
| 788 | 889 | |
| 789 | - def _on_room_subject(self,
|
|
| 790 | - contact: GroupchatContact,
|
|
| 791 | - _signal_name: str,
|
|
| 792 | - subject: Optional[MucSubject]
|
|
| 793 | - ) -> None:
|
|
| 890 | + self._process_muc_user_status_show_changed(event)
|
|
| 794 | 891 | |
| 795 | - if subject is None:
|
|
| 796 | - return
|
|
| 892 | + def _on_participant_status_show_changed(
|
|
| 893 | + self,
|
|
| 894 | + contact: GroupchatParticipant,
|
|
| 895 | + _signal_name: str,
|
|
| 896 | + event: events.MUCUserStatusShowChanged) -> None:
|
|
| 797 | 897 | |
| 798 | - _timestamp, old_subject = self._muc_subjects.get(contact, (None, None))
|
|
| 799 | - if old_subject is not None and old_subject.text == subject.text:
|
|
| 800 | - # Probably a rejoin, we already showed that subject
|
|
| 898 | + self._process_muc_user_status_show_changed(event)
|
|
| 899 | + |
|
| 900 | + def _process_muc_user_status_show_changed(
|
|
| 901 | + self,
|
|
| 902 | + event: events.MUCUserStatusShowChanged) -> None:
|
|
| 903 | + |
|
| 904 | + if isinstance(self._contact, GroupchatContact):
|
|
| 905 | + contact = self._contact
|
|
| 906 | + elif isinstance(self._contact, GroupchatParticipant):
|
|
| 907 | + contact = self._contact.room
|
|
| 908 | + else:
|
|
| 909 | + raise AssertionError
|
|
| 910 | + |
|
| 911 | + if not contact.settings.get('print_status'):
|
|
| 801 | 912 | return
|
| 802 | 913 | |
| 803 | - self._muc_subjects[contact] = (time.time(), subject)
|
|
| 914 | + nick = event.nick
|
|
| 915 | + status = event.status
|
|
| 916 | + status = '' if not status else f' - {status}'
|
|
| 917 | + show = helpers.get_uf_show(event.show_value)
|
|
| 804 | 918 | |
| 805 | - if (app.settings.get('show_subject_on_join') or
|
|
| 806 | - not contact.is_joining):
|
|
| 807 | - self.conversation_view.add_muc_subject(subject)
|
|
| 919 | + if event.is_self:
|
|
| 920 | + message = _('You are now {show}{status}').format(show=show,
|
|
| 921 | + status=status)
|
|
| 922 | + |
|
| 923 | + else:
|
|
| 924 | + message = _('{nick} is now {show}{status}').format(
|
|
| 925 | + nick=nick,
|
|
| 926 | + show=show,
|
|
| 927 | + status=status)
|
|
| 928 | + |
|
| 929 | + self.add_info_message(message, event.timestamp)
|
|
| 808 | 930 | |
| 809 | 931 | def _on_room_config_changed(self,
|
| 810 | 932 | _contact: GroupchatContact,
|
| 811 | 933 | _signal_name: str,
|
| 812 | - properties: MessageProperties
|
|
| 934 | + event: events.MUCRoomConfigChanged
|
|
| 813 | 935 | ) -> None:
|
| 814 | - # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
|
|
| 815 | 936 | |
| 816 | - status_codes = properties.muc_status_codes
|
|
| 817 | - assert status_codes is not None
|
|
| 937 | + self._process_muc_room_config_changed(event)
|
|
| 938 | + |
|
| 939 | + def _process_muc_room_config_changed(self,
|
|
| 940 | + event: events.MUCRoomConfigChanged
|
|
| 941 | + ) -> None:
|
|
| 818 | 942 | |
| 943 | + # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
|
|
| 944 | + status_codes = event.status_codes
|
|
| 819 | 945 | changes: list[str] = []
|
| 946 | + |
|
| 820 | 947 | if StatusCode.SHOWING_UNAVAILABLE in status_codes:
|
| 821 | 948 | changes.append(_('Group chat now shows unavailable members'))
|
| 822 | 949 | |
| ... | ... | @@ -830,7 +957,8 @@ def _on_room_config_changed(self, |
| 830 | 957 | self.client.get_module('Discovery').disco_muc(self.contact.jid)
|
| 831 | 958 | |
| 832 | 959 | if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
|
| 833 | - # Can be a presence (see chg_contact_status in groupchat_control.py)
|
|
| 960 | + # Can be a presence
|
|
| 961 | + # (see chg_contact_status in groupchat_control.py)
|
|
| 834 | 962 | changes.append(_('Conversations are stored on the server'))
|
| 835 | 963 | |
| 836 | 964 | if StatusCode.CONFIG_NO_ROOM_LOGGING in status_codes:
|
| ... | ... | @@ -845,252 +973,202 @@ def _on_room_config_changed(self, |
| 845 | 973 | if StatusCode.CONFIG_FULL_ANONYMOUS in status_codes:
|
| 846 | 974 | changes.append(_('Group chat is now fully anonymous'))
|
| 847 | 975 | |
| 848 | - for change in changes:
|
|
| 849 | - self.add_info_message(change)
|
|
| 850 | - |
|
| 851 | - def rejoin(self) -> None:
|
|
| 852 | - self.client.get_module('MUC').join(self.contact.jid)
|
|
| 853 | - |
|
| 854 | - def _on_user_joined(self,
|
|
| 855 | - contact: GroupchatContact,
|
|
| 856 | - _signal_name: str,
|
|
| 857 | - user_contact: GroupchatParticipant,
|
|
| 858 | - properties: PresenceProperties
|
|
| 859 | - ) -> None:
|
|
| 860 | - |
|
| 861 | - nick = user_contact.name
|
|
| 862 | - if not properties.is_muc_self_presence:
|
|
| 863 | - if contact.is_joined:
|
|
| 864 | - self.conversation_view.add_muc_user_joined(nick)
|
|
| 865 | - return
|
|
| 866 | - |
|
| 867 | - status_codes = properties.muc_status_codes or []
|
|
| 868 | - |
|
| 869 | - if not contact.is_joined:
|
|
| 870 | - # We just joined the room
|
|
| 871 | - self.add_info_message(_('You (%s) joined the group chat') % nick)
|
|
| 872 | - |
|
| 873 | - if StatusCode.NON_ANONYMOUS in status_codes:
|
|
| 874 | - self.add_info_message(
|
|
| 875 | - _('Any participant is allowed to see your full XMPP Address'))
|
|
| 876 | - |
|
| 877 | - if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
|
|
| 878 | - self.add_info_message(_('Conversations are stored on the server'))
|
|
| 879 | - |
|
| 880 | - if StatusCode.NICKNAME_MODIFIED in status_codes:
|
|
| 881 | - self.add_info_message(
|
|
| 882 | - _('The server has assigned or modified your nickname in this '
|
|
| 883 | - 'group chat'))
|
|
| 976 | + for message in changes:
|
|
| 977 | + self.add_info_message(message, event.timestamp)
|
|
| 884 | 978 | |
| 885 | 979 | def _on_room_config_finished(self,
|
| 886 | 980 | _contact: GroupchatContact,
|
| 887 | - _signal_name: str
|
|
| 981 | + _signal_name: str,
|
|
| 982 | + event: events.MUCRoomConfigFinished
|
|
| 888 | 983 | ) -> None:
|
| 984 | + self._process_muc_room_config_finished(event)
|
|
| 985 | + |
|
| 986 | + def _process_muc_room_config_finished(self,
|
|
| 987 | + event: events.MUCRoomConfigFinished
|
|
| 988 | + ) -> None:
|
|
| 989 | + |
|
| 889 | 990 | self.add_info_message(_('A new group chat has been created'))
|
| 890 | 991 | |
| 891 | - def _on_user_affiliation_changed(self,
|
|
| 892 | - _contact: GroupchatContact,
|
|
| 893 | - _signal_name: str,
|
|
| 894 | - user_contact: GroupchatParticipant,
|
|
| 895 | - properties: PresenceProperties
|
|
| 896 | - ) -> None:
|
|
| 897 | - affiliation = helpers.get_uf_affiliation(user_contact.affiliation)
|
|
| 898 | - nick = user_contact.name
|
|
| 992 | + def _on_room_presence_error(self,
|
|
| 993 | + _contact: GroupchatContact,
|
|
| 994 | + _signal_name: str,
|
|
| 995 | + event: events.MUCRoomPresenceError
|
|
| 996 | + ) -> None:
|
|
| 899 | 997 | |
| 900 | - assert properties.muc_user is not None
|
|
| 901 | - reason = properties.muc_user.reason
|
|
| 902 | - reason = '' if reason is None else f': {reason}'
|
|
| 998 | + self._process_muc_room_presence_error(event)
|
|
| 903 | 999 | |
| 904 | - actor = properties.muc_user.actor
|
|
| 905 | - # Group Chat: You have been kicked by Alice
|
|
| 906 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1000 | + def _process_muc_room_presence_error(self,
|
|
| 1001 | + event: events.MUCRoomPresenceError
|
|
| 1002 | + ) -> None:
|
|
| 907 | 1003 | |
| 908 | - if properties.is_muc_self_presence:
|
|
| 909 | - message = _('** Your Affiliation has been set to '
|
|
| 910 | - '{affiliation}{actor}{reason}').format(
|
|
| 911 | - affiliation=affiliation,
|
|
| 912 | - actor=actor,
|
|
| 913 | - reason=reason)
|
|
| 914 | - else:
|
|
| 915 | - message = _('** Affiliation of {nick} has been set to '
|
|
| 916 | - '{affiliation}{actor}{reason}').format(
|
|
| 917 | - nick=nick,
|
|
| 918 | - affiliation=affiliation,
|
|
| 919 | - actor=actor,
|
|
| 920 | - reason=reason)
|
|
| 1004 | + error_message = to_user_string(event.error)
|
|
| 1005 | + self.add_info_message(_('Error: %s') % error_message)
|
|
| 921 | 1006 | |
| 922 | - self.add_info_message(message)
|
|
| 1007 | + def _on_room_destroyed(self,
|
|
| 1008 | + _contact: GroupchatContact,
|
|
| 1009 | + _signal_name: str,
|
|
| 1010 | + event: events.MUCRoomDestroyed
|
|
| 1011 | + ) -> None:
|
|
| 923 | 1012 | |
| 924 | - def _on_user_role_changed(self,
|
|
| 925 | - _contact: GroupchatContact,
|
|
| 926 | - _signal_name: str,
|
|
| 927 | - user_contact: GroupchatParticipant,
|
|
| 928 | - properties: PresenceProperties
|
|
| 929 | - ) -> None:
|
|
| 930 | - role = helpers.get_uf_role(user_contact.role)
|
|
| 931 | - nick = user_contact.name
|
|
| 1013 | + self._process_muc_room_destroyed(event)
|
|
| 1014 | + |
|
| 1015 | + def _process_muc_room_destroyed(self,
|
|
| 1016 | + event: events.MUCRoomDestroyed
|
|
| 1017 | + ) -> None:
|
|
| 932 | 1018 | |
| 933 | - assert properties.muc_user is not None
|
|
| 934 | - reason = properties.muc_user.reason
|
|
| 1019 | + reason = event.reason
|
|
| 935 | 1020 | reason = '' if reason is None else f': {reason}'
|
| 936 | 1021 | |
| 937 | - actor = properties.muc_user.actor
|
|
| 938 | - # Group Chat: You have been kicked by Alice
|
|
| 939 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1022 | + message = _('Group chat has been destroyed')
|
|
| 940 | 1023 | |
| 941 | - if properties.is_muc_self_presence:
|
|
| 942 | - message = _('** Your Role has been set to '
|
|
| 943 | - '{role}{actor}{reason}').format(role=role,
|
|
| 944 | - actor=actor,
|
|
| 945 | - reason=reason)
|
|
| 946 | - else:
|
|
| 947 | - message = _('** Role of {nick} has been set to '
|
|
| 948 | - '{role}{actor}{reason}').format(nick=nick,
|
|
| 949 | - role=role,
|
|
| 950 | - actor=actor,
|
|
| 951 | - reason=reason)
|
|
| 1024 | + if event.alternate is not None:
|
|
| 1025 | + message += '\n' + _('You can join this group chat instead: '
|
|
| 1026 | + 'xmpp:%s?join') % str(event.alternate)
|
|
| 952 | 1027 | |
| 953 | - self.add_info_message(message)
|
|
| 1028 | + self.add_info_message(message, event.timestamp)
|
|
| 954 | 1029 | |
| 955 | - def _on_room_kicked(self,
|
|
| 1030 | + def _on_user_joined(self,
|
|
| 956 | 1031 | _contact: GroupchatContact,
|
| 957 | 1032 | _signal_name: str,
|
| 958 | - properties: MessageProperties
|
|
| 1033 | + _user_contact: GroupchatParticipant,
|
|
| 1034 | + event: events.MUCUserJoined
|
|
| 959 | 1035 | ) -> None:
|
| 960 | - status_codes = properties.muc_status_codes or []
|
|
| 961 | 1036 | |
| 962 | - assert properties.muc_user is not None
|
|
| 963 | - reason = properties.muc_user.reason
|
|
| 964 | - reason = '' if reason is None else f': {reason}'
|
|
| 1037 | + self._process_muc_user_joined(event)
|
|
| 965 | 1038 | |
| 966 | - actor = properties.muc_user.actor
|
|
| 967 | - # Group Chat: You have been kicked by Alice
|
|
| 968 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1039 | + def _process_muc_user_joined(self, event: events.MUCUserJoined) -> None:
|
|
| 1040 | + assert isinstance(self.contact, GroupchatContact)
|
|
| 969 | 1041 | |
| 970 | - # Group Chat: We have been removed from the room by Alice: reason
|
|
| 971 | - message = _('You have been removed from the group chat{actor}{reason}')
|
|
| 1042 | + if not event.is_self:
|
|
| 1043 | + if self.contact.is_joined:
|
|
| 1044 | + self.conversation_view.add_muc_user_joined(event)
|
|
| 1045 | + return
|
|
| 972 | 1046 | |
| 973 | - if StatusCode.REMOVED_ERROR in status_codes:
|
|
| 974 | - # Handle 333 before 307, some MUCs add both
|
|
| 975 | - # Group Chat: Server kicked us because of an server error
|
|
| 976 | - message = _('You have left due '
|
|
| 977 | - 'to an error{reason}').format(reason=reason)
|
|
| 978 | - self.add_info_message(message)
|
|
| 1047 | + status_codes = event.status_codes or []
|
|
| 979 | 1048 | |
| 980 | - elif StatusCode.REMOVED_KICKED in status_codes:
|
|
| 981 | - # Group Chat: We have been kicked by Alice: reason
|
|
| 982 | - message = _('You have been '
|
|
| 983 | - 'kicked{actor}{reason}').format(actor=actor,
|
|
| 984 | - reason=reason)
|
|
| 985 | - self.add_info_message(message)
|
|
| 1049 | + message = None
|
|
| 1050 | + if not self.contact.is_joined:
|
|
| 1051 | + # We just joined the room
|
|
| 1052 | + message = _('You (%s) joined the group chat') % event.nick
|
|
| 986 | 1053 | |
| 987 | - elif StatusCode.REMOVED_BANNED in status_codes:
|
|
| 988 | - # Group Chat: We have been banned by Alice: reason
|
|
| 989 | - message = _('You have been '
|
|
| 990 | - 'banned{actor}{reason}').format(actor=actor,
|
|
| 991 | - reason=reason)
|
|
| 992 | - self.add_info_message(message)
|
|
| 1054 | + if StatusCode.NON_ANONYMOUS in status_codes:
|
|
| 1055 | + message = _('Any participant is allowed to see your full '
|
|
| 1056 | + 'XMPP Address')
|
|
| 993 | 1057 | |
| 994 | - elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
|
|
| 995 | - # Group Chat: We were removed because of an affiliation change
|
|
| 996 | - reason = _(': Affiliation changed')
|
|
| 997 | - message = message.format(actor=actor, reason=reason)
|
|
| 998 | - self.add_info_message(message)
|
|
| 1058 | + if StatusCode.CONFIG_ROOM_LOGGING in status_codes:
|
|
| 1059 | + message = _('Conversations are stored on the server')
|
|
| 999 | 1060 | |
| 1000 | - elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
|
|
| 1001 | - # Group Chat: Room configuration changed
|
|
| 1002 | - reason = _(': Group chat configuration changed to members-only')
|
|
| 1003 | - message = message.format(actor=actor, reason=reason)
|
|
| 1004 | - self.add_info_message(message)
|
|
| 1061 | + if StatusCode.NICKNAME_MODIFIED in status_codes:
|
|
| 1062 | + message = _('The server has assigned or modified your '
|
|
| 1063 | + 'nickname in this group chat')
|
|
| 1005 | 1064 | |
| 1006 | - elif StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes:
|
|
| 1007 | - # Group Chat: Kicked because of server shutdown
|
|
| 1008 | - reason = ': System shutdown'
|
|
| 1009 | - message = message.format(actor=actor, reason=reason)
|
|
| 1010 | - self.add_info_message(message)
|
|
| 1065 | + if message is not None:
|
|
| 1066 | + self.add_info_message(message, event.timestamp)
|
|
| 1011 | 1067 | |
| 1012 | 1068 | def _on_user_left(self,
|
| 1013 | 1069 | _contact: GroupchatContact,
|
| 1014 | 1070 | _signal_name: str,
|
| 1015 | - user_contact: GroupchatParticipant,
|
|
| 1016 | - properties: MessageProperties
|
|
| 1071 | + _user_contact: GroupchatParticipant,
|
|
| 1072 | + event: events.MUCUserLeft
|
|
| 1017 | 1073 | ) -> None:
|
| 1018 | - status_codes = properties.muc_status_codes or []
|
|
| 1019 | - nick = user_contact.name
|
|
| 1020 | 1074 | |
| 1021 | - assert properties.muc_user is not None
|
|
| 1022 | - reason = properties.muc_user.reason
|
|
| 1023 | - reason = '' if reason is None else f': {reason}'
|
|
| 1075 | + self._process_muc_user_left(event)
|
|
| 1024 | 1076 | |
| 1025 | - actor = properties.muc_user.actor
|
|
| 1026 | - # Group Chat: You have been kicked by Alice
|
|
| 1027 | - actor = '' if actor is None else _(' by {actor}').format(actor=actor)
|
|
| 1077 | + def _process_muc_user_left(self, event: events.MUCUserLeft) -> None:
|
|
| 1078 | + if event.is_self:
|
|
| 1079 | + return
|
|
| 1028 | 1080 | |
| 1029 | - # Group Chat: We have been removed from the room
|
|
| 1030 | - message = _('{nick} has been removed from the group chat{by}{reason}')
|
|
| 1081 | + status_codes = event.status_codes or []
|
|
| 1082 | + nick = event.nick
|
|
| 1031 | 1083 | |
| 1032 | 1084 | if StatusCode.REMOVED_ERROR in status_codes:
|
| 1033 | 1085 | # Handle 333 before 307, some MUCs add both
|
| 1034 | - self.conversation_view.add_muc_user_left(
|
|
| 1035 | - nick, properties.muc_user.reason, error=True)
|
|
| 1086 | + self.conversation_view.add_muc_user_left(event, error=True)
|
|
| 1087 | + return
|
|
| 1036 | 1088 | |
| 1037 | - elif StatusCode.REMOVED_KICKED in status_codes:
|
|
| 1038 | - # Group Chat: User was kicked by Alice: reason
|
|
| 1089 | + reason = event.reason
|
|
| 1090 | + reason = '' if reason is None else f': {reason}'
|
|
| 1091 | + |
|
| 1092 | + actor = event.actor
|
|
| 1093 | + # Group Chat: You have been kicked by Alice
|
|
| 1094 | + actor = '' if actor is None else _(' by {actor}').format(
|
|
| 1095 | + actor=actor)
|
|
| 1096 | + |
|
| 1097 | + message = _('{nick} has been removed from the group '
|
|
| 1098 | + 'chat{by}{reason}')
|
|
| 1099 | + |
|
| 1100 | + if StatusCode.REMOVED_KICKED in status_codes:
|
|
| 1039 | 1101 | message = _('{nick} has been '
|
| 1040 | 1102 | 'kicked{actor}{reason}').format(nick=nick,
|
| 1041 | 1103 | actor=actor,
|
| 1042 | 1104 | reason=reason)
|
| 1043 | - self.add_info_message(message)
|
|
| 1044 | 1105 | |
| 1045 | 1106 | elif StatusCode.REMOVED_BANNED in status_codes:
|
| 1046 | - # Group Chat: User was banned by Alice: reason
|
|
| 1047 | 1107 | message = _('{nick} has been '
|
| 1048 | 1108 | 'banned{actor}{reason}').format(nick=nick,
|
| 1049 | 1109 | actor=actor,
|
| 1050 | 1110 | reason=reason)
|
| 1051 | - self.add_info_message(message)
|
|
| 1052 | 1111 | |
| 1053 | 1112 | elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes:
|
| 1054 | 1113 | reason = _(': Affiliation changed')
|
| 1055 | 1114 | message = message.format(nick=nick, by=actor, reason=reason)
|
| 1056 | - self.add_info_message(message)
|
|
| 1057 | 1115 | |
| 1058 | 1116 | elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes:
|
| 1059 | 1117 | reason = _(': Group chat configuration changed to members-only')
|
| 1060 | 1118 | message = message.format(nick=nick, by=actor, reason=reason)
|
| 1061 | - self.add_info_message(message)
|
|
| 1062 | 1119 | |
| 1063 | 1120 | else:
|
| 1064 | - self.conversation_view.add_muc_user_left(
|
|
| 1065 | - nick, properties.muc_user.reason)
|
|
| 1121 | + self.conversation_view.add_muc_user_left(event)
|
|
| 1122 | + return
|
|
| 1066 | 1123 | |
| 1067 | - def _on_room_presence_error(self,
|
|
| 1068 | - _contact: GroupchatContact,
|
|
| 1069 | - _signal_name: str,
|
|
| 1070 | - properties: PresenceProperties
|
|
| 1071 | - ) -> None:
|
|
| 1124 | + self.add_info_message(message, event.timestamp)
|
|
| 1072 | 1125 | |
| 1073 | - assert properties.error is not None
|
|
| 1074 | - error_message = to_user_string(properties.error)
|
|
| 1075 | - self.add_info_message(_('Error: %s') % error_message)
|
|
| 1126 | + def add_muc_message(self,
|
|
| 1127 | + text: str,
|
|
| 1128 | + tim: float,
|
|
| 1129 | + contact: str = '',
|
|
| 1130 | + displaymarking: Optional[Displaymarking] = None,
|
|
| 1131 | + message_id: Optional[str] = None,
|
|
| 1132 | + stanza_id: Optional[str] = None,
|
|
| 1133 | + additional_data: Optional[AdditionalDataDict] = None,
|
|
| 1134 | + ) -> None:
|
|
| 1076 | 1135 | |
| 1077 | - def _on_room_destroyed(self,
|
|
| 1078 | - _contact: GroupchatContact,
|
|
| 1079 | - _signal_name: str,
|
|
| 1080 | - properties: PresenceProperties
|
|
| 1081 | - ) -> None:
|
|
| 1136 | + assert isinstance(self._contact, GroupchatContact)
|
|
| 1082 | 1137 | |
| 1083 | - destroyed = properties.muc_destroyed
|
|
| 1084 | - assert destroyed is not None
|
|
| 1138 | + if contact == self._contact.nickname:
|
|
| 1139 | + kind = 'outgoing'
|
|
| 1140 | + else:
|
|
| 1141 | + kind = 'incoming'
|
|
| 1142 | + # muc-specific chatstate
|
|
| 1085 | 1143 | |
| 1086 | - reason = destroyed.reason
|
|
| 1087 | - reason = '' if reason is None else f': {reason}'
|
|
| 1144 | + self._add_message(text,
|
|
| 1145 | + kind,
|
|
| 1146 | + contact,
|
|
| 1147 | + tim,
|
|
| 1148 | + displaymarking=displaymarking,
|
|
| 1149 | + message_id=message_id,
|
|
| 1150 | + stanza_id=stanza_id,
|
|
| 1151 | + additional_data=additional_data)
|
|
| 1088 | 1152 | |
| 1089 | - message = _('Group chat has been destroyed')
|
|
| 1090 | - self.add_info_message(message)
|
|
| 1153 | + def _on_room_subject(self,
|
|
| 1154 | + contact: GroupchatContact,
|
|
| 1155 | + _signal_name: str,
|
|
| 1156 | + subject: Optional[MucSubject]
|
|
| 1157 | + ) -> None:
|
|
| 1091 | 1158 | |
| 1092 | - alternate = destroyed.alternate
|
|
| 1093 | - if alternate is not None:
|
|
| 1094 | - join_message = _('You can join this group chat '
|
|
| 1095 | - 'instead: xmpp:%s?join') % str(alternate)
|
|
| 1096 | - self.add_info_message(join_message) |
|
| 1159 | + if subject is None:
|
|
| 1160 | + return
|
|
| 1161 | + |
|
| 1162 | + _timestamp, old_subject = self._muc_subjects.get(contact, (None, None))
|
|
| 1163 | + if old_subject is not None and old_subject.text == subject.text:
|
|
| 1164 | + # Probably a rejoin, we already showed that subject
|
|
| 1165 | + return
|
|
| 1166 | + |
|
| 1167 | + self._muc_subjects[contact] = (time.time(), subject)
|
|
| 1168 | + |
|
| 1169 | + if (app.settings.get('show_subject_on_join') or
|
|
| 1170 | + not contact.is_joining):
|
|
| 1171 | + self.conversation_view.add_muc_subject(subject)
|
|
| 1172 | + |
|
| 1173 | + def rejoin(self) -> None:
|
|
| 1174 | + self.client.get_module('MUC').join(self.contact.jid) |
| ... | ... | @@ -27,13 +27,19 @@ |
| 27 | 27 | |
| 28 | 28 | |
| 29 | 29 | class MUCJoinLeft(BaseRow):
|
| 30 | - def __init__(self, type_: str, account: str, nick: str,
|
|
| 30 | + def __init__(self,
|
|
| 31 | + type_: str,
|
|
| 32 | + account: str,
|
|
| 33 | + nick: str,
|
|
| 31 | 34 | reason: Optional[str] = None,
|
| 32 | - error: bool = False):
|
|
| 35 | + error: bool = False,
|
|
| 36 | + timestamp: Optional[float] = None
|
|
| 37 | + ) -> None:
|
|
| 38 | + |
|
| 33 | 39 | BaseRow.__init__(self, account)
|
| 34 | 40 | |
| 35 | 41 | self.type = type_
|
| 36 | - timestamp = time.time()
|
|
| 42 | + timestamp = timestamp or time.time()
|
|
| 37 | 43 | self.timestamp = datetime.fromtimestamp(timestamp)
|
| 38 | 44 | self.db_timestamp = timestamp
|
| 39 | 45 |
| ... | ... | @@ -37,9 +37,7 @@ |
| 37 | 37 | |
| 38 | 38 | from gajim.common import app
|
| 39 | 39 | from gajim.common import types
|
| 40 | -from gajim.common.events import JingleRequestReceived
|
|
| 41 | -from gajim.common.events import FileRequestReceivedEvent
|
|
| 42 | -from gajim.common.events import FileRequestSent
|
|
| 40 | +from gajim.common import events
|
|
| 43 | 41 | from gajim.common.helpers import AdditionalDataDict
|
| 44 | 42 | from gajim.common.helpers import to_user_string
|
| 45 | 43 | from gajim.common.helpers import get_start_of_day
|
| ... | ... | @@ -175,26 +173,29 @@ def add_muc_subject(self, |
| 175 | 173 | self._insert_message(muc_subject)
|
| 176 | 174 | |
| 177 | 175 | def add_muc_user_left(self,
|
| 178 | - nick: str,
|
|
| 179 | - reason: str,
|
|
| 180 | - error: bool = False) -> None:
|
|
| 176 | + event: events.MUCUserLeft,
|
|
| 177 | + error: bool = False
|
|
| 178 | + ) -> None:
|
|
| 179 | + |
|
| 181 | 180 | assert isinstance(self._contact, GroupchatContact)
|
| 182 | 181 | if not self._contact.settings.get('print_join_left'):
|
| 183 | 182 | return
|
| 184 | 183 | join_left = MUCJoinLeft('muc-user-left',
|
| 185 | 184 | self._contact.account,
|
| 186 | - nick,
|
|
| 187 | - reason=reason,
|
|
| 188 | - error=error)
|
|
| 185 | + event.nick,
|
|
| 186 | + reason=event.reason,
|
|
| 187 | + error=error,
|
|
| 188 | + timestamp=event.timestamp)
|
|
| 189 | 189 | self._insert_message(join_left)
|
| 190 | 190 | |
| 191 | - def add_muc_user_joined(self, nick: str) -> None:
|
|
| 191 | + def add_muc_user_joined(self, event: events.MUCUserJoined) -> None:
|
|
| 192 | 192 | assert isinstance(self._contact, GroupchatContact)
|
| 193 | 193 | if not self._contact.settings.get('print_join_left'):
|
| 194 | 194 | return
|
| 195 | 195 | join_left = MUCJoinLeft('muc-user-joined',
|
| 196 | 196 | self._contact.account,
|
| 197 | - nick)
|
|
| 197 | + event.nick,
|
|
| 198 | + timestamp=event.timestamp)
|
|
| 198 | 199 | self._insert_message(join_left)
|
| 199 | 200 | |
| 200 | 201 | def add_user_status(self, name: str, show: str, status: str) -> None:
|
| ... | ... | @@ -215,8 +216,8 @@ def add_file_transfer(self, transfer: HTTPFileTransfer) -> None: |
| 215 | 216 | |
| 216 | 217 | def add_jingle_file_transfer(self,
|
| 217 | 218 | event: Union[
|
| 218 | - FileRequestReceivedEvent,
|
|
| 219 | - FileRequestSent,
|
|
| 219 | + events.FileRequestReceivedEvent,
|
|
| 220 | + events.FileRequestSent,
|
|
| 220 | 221 | None] = None,
|
| 221 | 222 | db_message: Optional[ConversationRow] = None
|
| 222 | 223 | ) -> None:
|
| ... | ... | @@ -230,7 +231,7 @@ def add_jingle_file_transfer(self, |
| 230 | 231 | self._insert_message(jingle_transfer_row)
|
| 231 | 232 | |
| 232 | 233 | def add_call_message(self,
|
| 233 | - event: Optional[JingleRequestReceived] = None,
|
|
| 234 | + event: Optional[events.JingleRequestReceived] = None,
|
|
| 234 | 235 | db_message: Optional[ConversationRow] = None
|
| 235 | 236 | ) -> None:
|
| 236 | 237 | assert isinstance(self._contact, BareContact)
|
| ... | ... | @@ -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)
|
| ... | ... | @@ -38,7 +38,7 @@ |
| 38 | 38 | "gajim/common/regex.py",
|
| 39 | 39 | "gajim/common/setting_values.py",
|
| 40 | 40 | "gajim/common/sound.py",
|
| 41 | - "gajim/common/storage/",
|
|
| 41 | + "gajim/common/storage/*",
|
|
| 42 | 42 | "gajim/common/task_manager.py",
|
| 43 | 43 | "gajim/gajim.py",
|
| 44 | 44 | "gajim/gajim_remote.py",
|