From 3b9777eb66b713d68a09fb65887d3c6580a8e45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 1 Dec 2022 01:39:24 +0100 Subject: [PATCH 1/2] commands: Add /query support, to create a private chat with another user Support initiating chats with other users using /query command. When inside a matrix channel or server user id can be in the form: - @user:server.ext - user:server.ext - @user (it looks for @user:current-server.ext) - user (it looks for @user:current-server.ext) When inside a channel, can be just the nickname of the user, and we'll figure out the corresponding id. Re-initiating a left chat is supported, but may need some fixes. --- main.py | 2 +- matrix/buffer.py | 7 +++++ matrix/commands.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++ matrix/server.py | 22 +++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 765043a..9138624 100644 --- a/main.py +++ b/main.py @@ -74,7 +74,7 @@ matrix_olm_command_cb, matrix_devices_command_cb, matrix_room_command_cb, matrix_uploads_command_cb, matrix_upload_command_cb, matrix_send_anyways_cb, - matrix_reply_command_cb, + matrix_reply_command_cb, matrix_query_command_cb, matrix_cursor_reply_signal_cb) from matrix.completion import (init_completion, matrix_command_completion_cb, matrix_debug_completion_cb, diff --git a/matrix/buffer.py b/matrix/buffer.py index ce981b1..1fb4605 100644 --- a/matrix/buffer.py +++ b/matrix/buffer.py @@ -32,6 +32,7 @@ RedactedEvent, RedactionEvent, RoomAliasEvent, + RoomCreateEvent, RoomEncryptionEvent, RoomMemberEvent, RoomMessage, @@ -749,6 +750,9 @@ def invite(self, nick, date, extra_tags=None): message = self._membership_message(user, "invite") self.print_date_tags(message, date, tags + (extra_tags or [])) + def display(self): + W.buffer_set(self._ptr, "display", "1") + def remove_user_from_nicklist(self, user): # type: (WeechatUser) -> None nick_pointer = W.nicklist_search_nick(self._ptr, "", user.nick) @@ -1543,6 +1547,9 @@ def handle_timeline_event(self, event, extra_tags=None): elif isinstance(event, UnknownEvent): pass + elif isinstance(event, RoomCreateEvent): + pass + elif isinstance(event, BadEvent): self.print_bad_event(event, extra_tags) diff --git a/matrix/commands.py b/matrix/commands.py index 598f859..a97ccc9 100644 --- a/matrix/commands.py +++ b/matrix/commands.py @@ -90,6 +90,13 @@ def invite(args): return WeechatCommandParser._run_parser(parser, args) + @staticmethod + def query(args): + parser = WeechatArgParse(prog="query") + parser.add_argument("user_id") + + return WeechatCommandParser._run_parser(parser, args) + @staticmethod def join(args): parser = WeechatArgParse(prog="join") @@ -388,6 +395,23 @@ def hook_commands(): "", ) + W.hook_command( + # Command name and short description + "query", + "query an user", + # Synopsis + (""), + # Description + ( + "user-id: user-id of the room to join" + ), + # Completions + "", + # Callback + "matrix_query_command_cb", + "", + ) + W.hook_command( # Command name and short description "part", @@ -1106,6 +1130,50 @@ def matrix_join_command_cb(data, buffer, args): return W.WEECHAT_RC_OK +@utf8_decode +def matrix_query_command_cb(data, buffer, args): + parsed_args = WeechatCommandParser.query(args) + if not parsed_args: + return W.WEECHAT_RC_OK + + user_id = parsed_args.user_id + matrix_user_id = ':' in user_id and '.' in user_id.split(':')[-1] + + if not user_id.startswith('@'): + user_id = f"@{user_id}" + + def join_or_create_user_room(user_id): + room = server.find_room_from_user_id(user_id) + if room and room.joined: + room.weechat_buffer.display() + return + # if not room.joined: + # server.room_join(room.room.room_id) + # FIXME: we may still need to create a new room and replace the + # one already existing + server.create_private_messaging_room(user_id) + + for server in SERVERS.values(): + if buffer == server.server_buffer: + if not matrix_user_id: + user_id = f"{user_id}:{server.address}" + + join_or_create_user_room(user_id) + break + else: + buffer = server.find_room_from_ptr(buffer) + if buffer: + if not matrix_user_id: + user_id = f"{user_id}:{server.address}" + for room_user in buffer.weechat_buffer.users.values(): + if parsed_args.user_id == room_user.nick: + user_id = room_user.host + break + join_or_create_user_room(user_id) + break + + return W.WEECHAT_RC_OK + @utf8_decode def matrix_part_command_cb(data, buffer, args): diff --git a/matrix/server.py b/matrix/server.py index 0f34c1e..3921709 100644 --- a/matrix/server.py +++ b/matrix/server.py @@ -59,6 +59,7 @@ DeleteDevicesAuthResponse, DeleteDevicesResponse, TransportType, + RoomCreateResponse, RoomMessagesResponse, RoomMessagesError, EncryptionError, @@ -1651,6 +1652,14 @@ def handle_response(self, response): elif isinstance(response, DeleteDevicesResponse): self.info("Device successfully deleted") + elif isinstance(response, RoomCreateResponse): + try: + buffer = self.find_room_from_id(response.room_id) + except KeyError: + self.create_room_buffer(response.room_id, self.next_batch) + buffer = self.find_room_from_id(response.room_id) + buffer.weechat_buffer.display() + elif isinstance(response, KeysQueryResponse): self.keys_queried = False W.bar_item_update("buffer_modes") @@ -1766,6 +1775,10 @@ def create_room_buffer(self, room_id, prev_batch): self.room_buffers[room_id] = buf self.buffers[room_id] = buf.weechat_buffer._ptr + def create_private_messaging_room(self, user_id): + _, request = self.client.room_create(is_direct=True, invite={user_id}) + self.send_or_queue(request) + def find_room_from_ptr(self, pointer): try: room_id = key_from_value(self.buffers, pointer) @@ -1779,6 +1792,15 @@ def find_room_from_id(self, room_id): room_buffer = self.room_buffers[room_id] return room_buffer + def find_room_from_user_id(self, user_id): + for room_buffer in self.room_buffers.values(): + if (room_buffer.weechat_buffer.type == "private" and + len(room_buffer.weechat_buffer.users) <= 2): + user_ids = [ + u.host for u in room_buffer.weechat_buffer.users.values()] + if self.user_id and user_id in user_ids: + return room_buffer + def garbage_collect_users(self): """ Remove inactive users. This tries to keep the number of users added to the nicklist less than From b3d7608bdd34a8ba003e3730220f4bfa6bd74f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 1 Dec 2022 01:44:59 +0100 Subject: [PATCH 2/2] server: Handle the JoinedRoomsResponse event This is now easy enough to handle. --- matrix/server.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/matrix/server.py b/matrix/server.py index 3921709..ae05cf1 100644 --- a/matrix/server.py +++ b/matrix/server.py @@ -45,6 +45,7 @@ LocalProtocolError, LoginResponse, LoginInfoResponse, + JoinedRoomsResponse, Response, Rooms, RoomSendResponse, @@ -1637,6 +1638,17 @@ def handle_response(self, response): elif isinstance(response, RoomSendResponse): self.handle_own_messages(response) + elif isinstance(response, JoinedRoomsResponse): + for room_id in response.rooms: + try: + self.find_room_from_id(room_id) + except KeyError: + self.create_room_buffer(room_id, self.next_batch) + + if len(response.rooms) == 1: + buffer = self.find_room_from_id(response.rooms[0]) + buffer.weechat_buffer.display() + elif isinstance(response, RoomMessagesResponse): self.handle_backlog_response(response)