From 38d6a14a33702fbb7bcef587181e51b8ff44145e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?poljar=20=28Damir=20Jeli=C4=87=29?= Date: Thu, 5 Jul 2018 15:13:19 +0200 Subject: [PATCH] matrix: Change the way responses and events are executed. --- main.py | 1 + matrix/buffer.py | 77 ++++++++++++++++++++---- matrix/encryption.py | 5 +- matrix/events.py | 5 +- matrix/rooms.py | 138 +++++++++++++------------------------------ matrix/server.py | 124 ++++++++++++++++++++++++++++++++------ 6 files changed, 218 insertions(+), 132 deletions(-) diff --git a/main.py b/main.py index 7d924c8..ac6c95b 100644 --- a/main.py +++ b/main.py @@ -46,6 +46,7 @@ from matrix.commands import (hook_commands, hook_page_up, matrix_command_cb, matrix_command_pgup_cb, matrix_redact_command_cb, matrix_command_buf_clear_cb, matrix_me_command_cb, matrix_command_kick_cb) +from matrix.buffer import room_buffer_input_cb, room_buffer_close_cb from matrix.server import ( MatrixServer, diff --git a/matrix/buffer.py b/matrix/buffer.py index cb20d62..447235c 100644 --- a/matrix/buffer.py +++ b/matrix/buffer.py @@ -28,7 +28,7 @@ from builtins import super @utf8_decode def room_buffer_input_cb(server_name, buffer, input_data): server = SERVERS[server_name] - room, room_buffer = server.find_room(buffer) + room, room_buffer = server.find_room_from_ptr(buffer) if not room_buffer: # TODO log error @@ -40,9 +40,7 @@ def room_buffer_input_cb(server_name, buffer, input_data): formatted_data = Formatted.from_input_line(input_data) - if room.encrypted: - server.send_room_message(room.id, formatted_data) - return W.WEECHAT_RC_OK + server.send_room_message(room, formatted_data) return W.WEECHAT_RC_OK @@ -86,6 +84,13 @@ class WeechatChannelBuffer(object): "notify_message", "log1" ], + "self_message": [ + SCRIPT_NAME + "_message", + "notify_none", + "no_highlight", + "self_msg", + "log1" + ], "old_message": [ SCRIPT_NAME + "_message", "notify_message", @@ -107,6 +112,10 @@ class WeechatChannelBuffer(object): "invite": [ SCRIPT_NAME + "_invite", "log4" + ], + "topic": [ + SCRIPT_NAME + "_topic", + "log3", ] } @@ -123,13 +132,17 @@ class WeechatChannelBuffer(object): name, "room_buffer_input_cb", server_name, - "room_buffer_input_cb", + "room_buffer_close_cb", server_name, ) self.name = "" self.users = {} # type: Dict[str, RoomUser] + self.topic = "" + self.topic_author = "" + self.topic_date = None + W.buffer_set(self._ptr, "localvar_set_type", 'channel') W.buffer_set(self._ptr, "type", 'formatted') @@ -216,7 +229,7 @@ class WeechatChannelBuffer(object): def _message_tags(self, user, message_type): # type: (str, RoomUser, str) -> List[str] - tags = self.tags[message_type].copy() + tags = list(self.tags[message_type]) tags.append("nick_{nick}".format(nick=user.nick)) @@ -235,10 +248,10 @@ class WeechatChannelBuffer(object): # A message from a non joined user return RoomUser(nick) - def message(self, nick, message, date): + def message(self, nick, message, date, tags=[]): # type: (str, str, int, str) -> None user = self._get_user(nick) - tags = self._message_tags(user, "message") + tags = tags or self._message_tags(user, "message") prefix_string = ("" if not user.prefix else "{}{}{}".format( W.color(self._get_prefix_color(user.prefix)), @@ -264,10 +277,10 @@ class WeechatChannelBuffer(object): self.message(nick, data, date) - def action(self, nick, message, date): + def action(self, nick, message, date, tags=[]): # type: (str, str, int) -> None user = self._get_user(nick) - tags = self._message_tags(user, "action") + tags = tags or self._message_tags(user, "action") nick_prefix = ("" if not user.prefix else "{}{}{}".format( W.color(self._get_prefix_color(user.prefix)), @@ -282,7 +295,7 @@ class WeechatChannelBuffer(object): nick_color=W.color(user.color), author=nick, ncolor=W.color("reset"), - msg=self.message) + msg=message) self.print_date_tags(data, date, tags) @@ -293,9 +306,9 @@ class WeechatChannelBuffer(object): if user.prefix == "&": group_name = "000|o" - elif user.power_level == "@": + elif user.prefix == "@": group_name = "001|h" - elif user.power_level > "+": + elif user.prefix > "+": group_name = "002|v" return group_name @@ -400,3 +413,41 @@ class WeechatChannelBuffer(object): def kick(self, user, date, message=True, extra_tags=[]): # type: (WeechatUser, int, Optional[bool], Optional[List[str]]) -> None self._leave(user, date, message, "kick", extra_tags=[]) + + def _print_topic(self, nick, topic, date): + user = self._get_user(nick) + tags = self._message_tags(user, "topic") + + data = ("{prefix}{nick} has changed " + "the topic for {chan_color}{room}{ncolor} " + "to \"{topic}\"").format( + prefix=W.prefix("network"), + nick=user.nick, + chan_color=W.color("chat_channel"), + ncolor=W.color("reset"), + room=self.name, + topic=topic + ) + + self.print_date_tags(data, date, tags) + + def topic(self, nick, topic, date, message=True): + W.buffer_set(self._ptr, "title", topic) + + if message: + self._print_topic(nick, topic, date) + + self.topic = topic + self.topic_author = nick + self.topic_date = date + + def self_message(self, nick, message, date): + user = self._get_user(nick) + tags = self._message_tags(user, "self_message") + self.message(nick, message, date, tags) + + def self_action(self, nick, message, date): + user = self._get_user(nick) + tags = self._message_tags(user, "self_message") + tags.append(SCRIPT_NAME + "_action") + self.action(nick, message, date, tags) diff --git a/matrix/encryption.py b/matrix/encryption.py index 9ff5112..d8a332e 100644 --- a/matrix/encryption.py +++ b/matrix/encryption.py @@ -73,8 +73,9 @@ class WeechatArgParse(argparse.ArgumentParser): def error(self, message): m = ("{prefix}Error: {message} for command {command} " - "(see /help {command})").format(prefix=W.prefix("error"), - message=message, command=self.prog) + "(see /help {command})").format(prefix=W.prefix("error"), + message=message, + command=self.prog) W.prnt("", m) raise ParseError diff --git a/matrix/events.py b/matrix/events.py index 9b4330e..31f1f81 100644 --- a/matrix/events.py +++ b/matrix/events.py @@ -27,11 +27,12 @@ from operator import itemgetter from matrix.globals import W from matrix.utils import (tags_for_message, sanitize_id, sanitize_token, sanitize_text, tags_from_line_data) -from matrix.rooms import (matrix_create_room_buffer, RoomInfo, RoomMessageText, +from matrix.rooms import (RoomInfo, RoomMessageText, RoomMessageEvent, RoomRedactedMessageEvent, RoomMessageEmote) from matrix.encryption import OlmDeviceKey, OneTimeKey +from .buffer import RoomUser try: from olm.session import OlmMessage, OlmPreKeyMessage @@ -657,7 +658,7 @@ class MatrixSyncEvent(MatrixEvent): info = self.joined_room_infos.pop() if info.room_id not in server.buffers: - matrix_create_room_buffer(server, info.room_id) + server.create_room_buffer(info.room_id) room = server.rooms[info.room_id] diff --git a/matrix/rooms.py b/matrix/rooms.py index 86c616d..434f943 100644 --- a/matrix/rooms.py +++ b/matrix/rooms.py @@ -156,45 +156,15 @@ class MatrixUser: # yapf: enable -def matrix_create_room_buffer(server, room_id): - # type: (MatrixServer, str) -> None - buf = W.buffer_new(room_id, "room_input_cb", server.name, "room_close_cb", - server.name) - - W.buffer_set(buf, "localvar_set_type", 'channel') - W.buffer_set(buf, "type", 'formatted') - - W.buffer_set(buf, "localvar_set_channel", room_id) - - W.buffer_set(buf, "localvar_set_nick", server.user) - - W.buffer_set(buf, "localvar_set_server", server.name) - - short_name = strip_matrix_server(room_id) - W.buffer_set(buf, "short_name", short_name) - - W.nicklist_add_group(buf, '', "000|o", "weechat.color.nicklist_group", 1) - W.nicklist_add_group(buf, '', "001|h", "weechat.color.nicklist_group", 1) - W.nicklist_add_group(buf, '', "002|v", "weechat.color.nicklist_group", 1) - W.nicklist_add_group(buf, '', "999|...", "weechat.color.nicklist_group", 1) - - W.buffer_set(buf, "nicklist", "1") - W.buffer_set(buf, "nicklist_display_groups", "0") - - # TODO make this configurable - W.buffer_set(buf, "highlight_tags_restrict", "matrix_message") - - server.buffers[room_id] = buf - server.rooms[room_id] = MatrixRoom(room_id) - - class RoomInfo(): - def __init__(self, room_id, prev_batch, events): + def __init__(self, room_id, prev_batch, state, timeline): # type: (str, str, List[Any], List[Any]) -> None self.room_id = room_id self.prev_batch = prev_batch - self.events = deque(events) + + self.state = deque(state) + self.timeline = deque(timeline) @staticmethod def _message_from_event(event): @@ -219,66 +189,40 @@ class RoomInfo(): raise ValueError if event_dict["content"]["membership"] == "join": - event = RoomMemberJoin.from_dict(event_dict) - - try: - message = RoomMembershipMessage( - event.event_id, event.sender, event.timestamp, - "has joined", "join") - - return event, message - except AttributeError: - return event, None + return RoomMemberJoin.from_dict(event_dict) elif event_dict["content"]["membership"] == "leave": - event = RoomMemberLeave.from_dict(event_dict) + return RoomMemberLeave.from_dict(event_dict) - try: - msg = ("has left" if event.sender == event.leaving_user else - "has been kicked") - message = RoomMembershipMessage( - event.event_id, event.leaving_user, event.timestamp, msg, "quit") - return event, message - except AttributeError: - return event, None - - return None, None + return None @staticmethod def parse_event(olm, room_id, event_dict): # type: (Dict[Any, Any]) -> (RoomEvent, RoomEvent) - state_event = None - message_event = None + event = None if "redacted_by" in event_dict["unsigned"]: - message_event = RoomRedactedMessageEvent.from_dict(event_dict) + event = RoomRedactedMessageEvent.from_dict(event_dict) elif event_dict["type"] == "m.room.message": - message_event = RoomInfo._message_from_event(event_dict) + event = RoomInfo._message_from_event(event_dict) elif event_dict["type"] == "m.room.member": - state_event, message_event = ( - RoomInfo._membership_from_dict(event_dict)) + event = RoomInfo._membership_from_dict(event_dict) elif event_dict["type"] == "m.room.power_levels": - state_event = RoomPowerLevels.from_dict(event_dict) + event = RoomPowerLevels.from_dict(event_dict) elif event_dict["type"] == "m.room.topic": - state_event = RoomTopicEvent.from_dict(event_dict) - message_event = RoomTopiceMessage( - state_event.event_id, - state_event.sender, - state_event.timestamp, - state_event.topic) + event = RoomTopicEvent.from_dict(event_dict) elif event_dict["type"] == "m.room.redaction": - message_event = RoomRedactionEvent.from_dict(event_dict) + event = RoomRedactionEvent.from_dict(event_dict) elif event_dict["type"] == "m.room.name": - state_event = RoomNameEvent.from_dict(event_dict) + event = RoomNameEvent.from_dict(event_dict) elif event_dict["type"] == "m.room.canonical_alias": - state_event = RoomAliasEvent.from_dict(event_dict) + event = RoomAliasEvent.from_dict(event_dict) elif event_dict["type"] == "m.room.encryption": - state_event = RoomEncryptionEvent.from_dict(event_dict) + event = RoomEncryptionEvent.from_dict(event_dict) elif event_dict["type"] == "m.room.encrypted": - state_event, message_event = RoomInfo._decrypt_event(olm, room_id, - event_dict) + event = RoomInfo._decrypt_event(olm, room_id, event_dict) - return state_event, message_event + return event @staticmethod def _decrypt_event(olm, room_id, event_dict): @@ -287,7 +231,7 @@ class RoomInfo(): plaintext = olm.group_decrypt(room_id, session_id, ciphertext) if not plaintext: - return None, None + return None parsed_plaintext = json.loads(plaintext, encoding="utf-8") @@ -297,18 +241,13 @@ class RoomInfo(): return RoomInfo.parse_event(olm, room_id, event_dict) @staticmethod - def _parse_events(olm, room_id, parsed_dict, messages=True, state=True): - state_events = [] - message_events = [] - - if not messages and not state: - return [] + def _parse_events(olm, room_id, parsed_dict): + events = [] try: for event in parsed_dict: - m_event, s_event = RoomInfo.parse_event(olm, room_id, event) - state_events.append(m_event) - message_events.append(s_event) + e = RoomInfo.parse_event(olm, room_id, event) + events.append(e) except (ValueError, TypeError, KeyError) as error: message = ("{prefix}matrix: Error parsing " "room event of type {type}: {error}\n{event}").format( @@ -319,14 +258,6 @@ class RoomInfo(): W.prnt("", message) raise - events = [] - - if state: - events = events + state_events - - if messages: - events = events + message_events - return events @classmethod @@ -336,12 +267,23 @@ class RoomInfo(): state_dict = parsed_dict['state']['events'] timeline_dict = parsed_dict['timeline']['events'] - state_events = RoomInfo._parse_events(olm, room_id, state_dict, messages=False) - timeline_events = RoomInfo._parse_events(olm, room_id, timeline_dict) + state_events = RoomInfo._parse_events( + olm, + room_id, + state_dict + ) + timeline_events = RoomInfo._parse_events( + olm, + room_id, + timeline_dict + ) - events = state_events + timeline_events - - return cls(room_id, prev_batch, list(filter(None, events))) + return cls( + room_id, + prev_batch, + list(filter(None, state_events)), + list(filter(None, timeline_events)) + ) class RoomEvent(): diff --git a/matrix/server.py b/matrix/server.py index 163b4be..0af619d 100644 --- a/matrix/server.py +++ b/matrix/server.py @@ -29,10 +29,19 @@ from http_parser.pyparser import HttpParser from matrix.plugin_options import Option, DebugType from matrix.utils import (key_from_value, prnt_debug, server_buffer_prnt, - create_server_buffer, tags_for_message) + create_server_buffer, tags_for_message, + server_ts_to_weechat, shorten_sender) from matrix.utf import utf8_decode from matrix.globals import W, SERVERS, OPTIONS import matrix.api as API +from .buffer import WeechatChannelBuffer, RoomUser +from .rooms import ( + MatrixRoom, + RoomMessageText, + RoomMessageEmote, + MatrixUser, + RoomMemberJoin +) from matrix.api import ( MatrixClient, MatrixSyncMessage, @@ -40,10 +49,13 @@ from matrix.api import ( MatrixKeyUploadMessage, MatrixKeyQueryMessage, MatrixToDeviceMessage, + MatrixSendMessage, MatrixEncryptedMessage, MatrixKeyClaimMessage ) +from .events import MatrixSendEvent + from matrix.encryption import ( Olm, EncryptionError, @@ -77,6 +89,7 @@ class MatrixServer: self.password = "" # type: str self.rooms = dict() # type: Dict[str, MatrixRoom] + self.room_buffers = dict() # type: Dict[str, WeechatChannelBuffer] self.buffers = dict() # type: Dict[str, weechat.buffer] self.server_buffer = None # type: weechat.buffer self.fd_hook = None # type: weechat.hook @@ -433,7 +446,7 @@ class MatrixServer: if self.server_buffer: message = ("{prefix}matrix: disconnected from server" - ).format(prefix=W.prefix("network")) + ).format(prefix=W.prefix("network")) server_buffer_prnt(self, message) if reconnect: @@ -486,16 +499,20 @@ class MatrixServer: message = MatrixSyncMessage(self.client, self.next_batch, limit) self.send_queue.append(message) + def _send_unencrypted_message(self, room_id, formatted_data): + message = MatrixSendMessage( + self.client, room_id=room_id, formatted_message=formatted_data) + self.send_or_queue(message) + def send_room_message( self, - room_id, + room, formatted_data, already_claimed=False ): # type: (str, Formatted) -> None - room = self.rooms[room_id] - if not room.encrypted: + self._send_unencrypted_message(room.room_id, formatted_data) return # TODO don't send messages unless all the devices are verified @@ -505,8 +522,8 @@ class MatrixServer: W.prnt("", "{prefix}matrix: Olm session missing for room, can't" " encrypt message.") W.prnt("", pprint.pformat(missing)) - self.encryption_queue[room_id].append(formatted_data) - message = MatrixKeyClaimMessage(self.client, room_id, missing) + self.encryption_queue[room.room_id].append(formatted_data) + message = MatrixKeyClaimMessage(self.client, room.room_id, missing) self.send_or_queue(message) return @@ -525,7 +542,7 @@ class MatrixServer: try: payload_dict, to_device_dict = self.olm.group_encrypt( - room_id, + room.room_id, plaintext_dict, self.user_id, room.users.keys() @@ -538,7 +555,7 @@ class MatrixServer: message = MatrixEncryptedMessage( self.client, - room_id, + room.room_id, formatted_data, payload_dict ) @@ -612,19 +629,46 @@ class MatrixServer: server_buffer_prnt(self, pprint.pformat(message.request.payload)) server_buffer_prnt(self, pprint.pformat(message.response.body)) + def handle_room_event(self, room, room_buffer, event, is_state_event): + if isinstance(event, RoomMemberJoin): + if event.sender in room.users: + user = room.users[event.sender] + if event.display_name: + user.display_name = event.display_name + else: + short_name = shorten_sender(event.sender) + user = MatrixUser(short_name, event.display_name) + buffer_user = RoomUser(user.name, event.sender) + room.users[event.sender] = user + + if self.user_id == event.sender: + buffer_user.color = "weechat.color.chat_nick_self" + user.nick_color = "weechat.color.chat_nick_self" + + room_buffer.join( + buffer_user, + server_ts_to_weechat(event.timestamp), + not is_state_event + ) + else: + tags = tags_for_message("message") + event.execute(self, room, room_buffer._ptr, tags) + def _loop_events(self, info, n): for i in range(n+1): + is_state = False try: - event = info.events.popleft() + event = info.state.popleft() + is_state = True except IndexError: - return i + try: + event = info.timeline.popleft() + except IndexError: + return i - room = self.rooms[info.room_id] - buf = self.buffers[info.room_id] - - tags = tags_for_message("message") - event.execute(self, room, buf, tags) + room, room_buffer = self.find_room_from_id(info.room_id) + self.handle_room_event(room, room_buffer, event, is_state) self.event_queue.appendleft(info) return i @@ -657,6 +701,32 @@ class MatrixServer: return + def handle_own_messages(self, room_buffer, message): + if isinstance(message, RoomMessageText): + msg = (message.formatted_message.to_weechat() + if message.formatted_message + else message.message) + + date = server_ts_to_weechat(message.timestamp) + room_buffer.self_message(self.user, msg, date) + + return + elif isinstance(message, RoomMessageEmote): + date = server_ts_to_weechat(message.timestamp) + room_buffer.self_action(self.user, message.message, date) + + return + + raise NotImplementedError("Unsupported message of type {}".format( + type(message))) + + def handle_matrix_response(self, response): + if isinstance(response, MatrixSendEvent): + _, room_buffer = self.find_room_from_id(response.room_id) + self.handle_own_messages(room_buffer, response.message) + else: + response.execute() + def handle_response(self, message): # type: (MatrixMessage) -> None @@ -674,7 +744,7 @@ class MatrixServer: return event = message.event - event.execute() + self.handle_matrix_response(event) else: status_code = message.response.status if status_code == 504: @@ -705,6 +775,26 @@ class MatrixServer: return + def create_room_buffer(self, room_id): + buf = WeechatChannelBuffer(room_id, self.name, self.user) + # TODO this should turned into a propper class + self.room_buffers[room_id] = buf + self.buffers[room_id] = buf._ptr + self.rooms[room_id] = MatrixRoom(room_id) + pass + + def find_room_from_ptr(self, pointer): + room_id = key_from_value(self.buffers, pointer) + room = self.rooms[room_id] + room_buffer = self.room_buffers[room_id] + + return room, room_buffer + + def find_room_from_id(self, room_id): + room = self.rooms[room_id] + room_buffer = self.room_buffers[room_id] + return room, room_buffer + @utf8_decode def matrix_config_server_read_cb(data, config_file, section, option_name,