diff --git a/matrix/api.py b/matrix/api.py index 5f25fca..4e8c8d3 100644 --- a/matrix/api.py +++ b/matrix/api.py @@ -289,6 +289,11 @@ class MatrixSyncMessage(MatrixMessage): MatrixMessage.__init__(self, MessageType.SYNC, client.sync, data) + def decode_body(self, server): + object_hook = partial(MatrixEvents.MatrixSyncEvent.from_dict, server) + + return self._decode(server, object_hook) + class MatrixSendMessage(MatrixMessage): @@ -448,19 +453,3 @@ class MatrixUser: self.power_level = 0 # type: int self.nick_color = "" # type: str self.prefix = "" # type: str - - -class MatrixRoom: - - def __init__(self, room_id): - # type: (str) -> None - # yapf: disable - self.room_id = room_id # type: str - self.alias = room_id # type: str - self.topic = "" # type: str - self.topic_author = "" # type: str - self.topic_date = None # type: datetime.datetime - self.prev_batch = "" # type: str - self.users = dict() # type: Dict[str, MatrixUser] - self.encrypted = False # type: bool - # yapf: enable diff --git a/matrix/events.py b/matrix/events.py index a4721e9..0ffdd2f 100644 --- a/matrix/events.py +++ b/matrix/events.py @@ -27,9 +27,10 @@ from matrix.utils import ( date_from_age, sender_to_nick_and_color, tags_for_message, - add_event_tags + add_event_tags, ) from matrix.colors import Formatted +from matrix.rooms import matrix_create_room_buffer def sanitize_token(string): @@ -91,17 +92,13 @@ def sanitize_text(string): if not isinstance(string, str): raise TypeError - remap = { - ord('\b'): None, - ord('\f'): None, - ord('\r'): None, - ord('\0'): None - } + remap = {ord('\b'): None, ord('\f'): None, ord('\r'): None, ord('\0'): None} return string.translate(remap) class MatrixEvent(): + def __init__(self, server): self.server = server @@ -110,6 +107,7 @@ class MatrixEvent(): class MatrixErrorEvent(MatrixEvent): + def __init__(self, server, error_message, fatal=False): self.error_message = error_message self.fatal = fatal @@ -117,8 +115,7 @@ class MatrixErrorEvent(MatrixEvent): def execute(self): message = ("{prefix}matrix: {error}").format( - prefix=W.prefix("error"), - error=self.error_message) + prefix=W.prefix("error"), error=self.error_message) W.prnt(self.server.server_buffer, message) @@ -129,22 +126,17 @@ class MatrixErrorEvent(MatrixEvent): def from_dict(cls, server, error_prefix, fatal, parsed_dict): try: message = "{prefix}: {error}".format( - prefix=error_prefix, - error=parsed_dict["error"]) - return cls( - server, - message, - fatal=fatal - ) + prefix=error_prefix, error=parsed_dict["error"]) + return cls(server, message, fatal=fatal) except KeyError: return cls( - server, - ("{prefix}: Invalid JSON response " - "from server.").format(prefix=error_prefix), + server, ("{prefix}: Invalid JSON response " + "from server.").format(prefix=error_prefix), fatal=fatal) class MatrixLoginEvent(MatrixEvent): + def __init__(self, server, user_id, access_token): self.user_id = user_id self.access_token = access_token @@ -160,21 +152,15 @@ class MatrixLoginEvent(MatrixEvent): @classmethod def from_dict(cls, server, parsed_dict): try: - return cls( - server, - sanitize_id(parsed_dict["user_id"]), - sanitize_token(parsed_dict["access_token"]) - ) + return cls(server, sanitize_id(parsed_dict["user_id"]), + sanitize_token(parsed_dict["access_token"])) except (KeyError, TypeError, ValueError): - return MatrixErrorEvent.from_dict( - server, - "Error logging in", - True, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error logging in", True, + parsed_dict) class MatrixSendEvent(MatrixEvent): + def __init__(self, server, room_id, event_id, message): self.room_id = room_id self.event_id = event_id @@ -208,22 +194,15 @@ class MatrixSendEvent(MatrixEvent): @classmethod def from_dict(cls, server, room_id, message, parsed_dict): try: - return cls( - server, - room_id, - sanitize_id(parsed_dict["event_id"]), - message - ) + return cls(server, room_id, sanitize_id(parsed_dict["event_id"]), + message) except (KeyError, TypeError, ValueError): - return MatrixErrorEvent.from_dict( - server, - "Error sending message", - False, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error sending message", + False, parsed_dict) class MatrixTopicEvent(MatrixEvent): + def __init__(self, server, room_id, event_id, topic): self.room_id = room_id self.topic = topic @@ -233,22 +212,15 @@ class MatrixTopicEvent(MatrixEvent): @classmethod def from_dict(cls, server, room_id, topic, parsed_dict): try: - return cls( - server, - room_id, - sanitize_id(parsed_dict["event_id"]), - topic - ) + return cls(server, room_id, sanitize_id(parsed_dict["event_id"]), + topic) except (KeyError, TypeError, ValueError): - return MatrixErrorEvent.from_dict( - server, - "Error setting topic", - False, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error setting topic", + False, parsed_dict) class MatrixRedactEvent(MatrixEvent): + def __init__(self, server, room_id, event_id, reason): self.room_id = room_id self.topic = reason @@ -258,22 +230,15 @@ class MatrixRedactEvent(MatrixEvent): @classmethod def from_dict(cls, server, room_id, reason, parsed_dict): try: - return cls( - server, - room_id, - sanitize_id(parsed_dict["event_id"]), - reason - ) + return cls(server, room_id, sanitize_id(parsed_dict["event_id"]), + reason) except (KeyError, TypeError, ValueError): - return MatrixErrorEvent.from_dict( - server, - "Error redacting message", - False, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error redacting message", + False, parsed_dict) class MatrixJoinEvent(MatrixEvent): + def __init__(self, server, room, room_id): self.room = room self.room_id = room_id @@ -288,15 +253,12 @@ class MatrixJoinEvent(MatrixEvent): sanitize_id(parsed_dict["room_id"]), ) except (KeyError, TypeError, ValueError): - return MatrixErrorEvent.from_dict( - server, - "Error joining room", - False, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error joining room", + False, parsed_dict) class MatrixPartEvent(MatrixEvent): + def __init__(self, server, room_id): self.room_id = room_id MatrixEvent.__init__(self, server) @@ -305,21 +267,16 @@ class MatrixPartEvent(MatrixEvent): def from_dict(cls, server, room_id, parsed_dict): try: if parsed_dict == {}: - return cls( - server, - room_id) + return cls(server, room_id) raise KeyError except KeyError: - return MatrixErrorEvent.from_dict( - server, - "Error leaving room", - False, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error leaving room", + False, parsed_dict) class MatrixInviteEvent(MatrixEvent): + def __init__(self, server, room_id, user_id): self.room_id = room_id self.user_id = user_id @@ -329,22 +286,16 @@ class MatrixInviteEvent(MatrixEvent): def from_dict(cls, server, room_id, user_id, parsed_dict): try: if parsed_dict == {}: - return cls( - server, - room_id, - user_id) + return cls(server, room_id, user_id) raise KeyError except KeyError: - return MatrixErrorEvent.from_dict( - server, - "Error inviting user", - False, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error inviting user", + False, parsed_dict) class MatrixBacklogEvent(MatrixEvent): + def __init__(self, server, room_id, end_token, messages): self.room_id = room_id self.end_token = end_token @@ -369,30 +320,19 @@ class MatrixBacklogEvent(MatrixEvent): end_token = sanitize_id(parsed_dict["end"]) - message_func = partial( - MatrixBacklogEvent._message_from_event, - room_id - ) + message_func = partial(MatrixBacklogEvent._message_from_event, + room_id) - message_events = list(filter( - lambda event: event["type"] == "m.room.message", - parsed_dict["chunk"] - )) + message_events = list( + filter(lambda event: event["type"] == "m.room.message", + parsed_dict["chunk"])) messages = [message_func(m) for m in message_events] - return cls( - server, - room_id, - end_token, - messages) + return cls(server, room_id, end_token, messages) except (KeyError, ValueError, TypeError): - return MatrixErrorEvent.from_dict( - server, - "Error fetching backlog", - False, - parsed_dict - ) + return MatrixErrorEvent.from_dict(server, "Error fetching backlog", + False, parsed_dict) def execute(self): room = self.server.rooms[self.room_id] @@ -405,7 +345,119 @@ class MatrixBacklogEvent(MatrixEvent): room.prev_batch = self.end_token +class MatrixSyncEvent(MatrixEvent): + + def __init__(self, server, next_batch, room_infos, invited_infos): + self.next_batch = next_batch + self.joined_room_infos = room_infos + self.invited_room_infos = invited_infos + + MatrixEvent.__init__(self, server) + + @staticmethod + def _infos_from_dict(parsed_dict): + join_infos = [] + invite_infos = [] + + for room_id, room_dict in parsed_dict['join'].items(): + if not room_id: + continue + + join_infos.append(RoomInfo.from_dict(room_id, room_dict)) + + return (join_infos, invite_infos) + + @classmethod + def from_dict(cls, server, parsed_dict): + try: + next_batch = parsed_dict["next_batch"] + room_info_dict = parsed_dict["rooms"] + + join_infos, invite_infos = MatrixSyncEvent._infos_from_dict( + room_info_dict) + + return cls(server, next_batch, join_infos, invite_infos) + except (KeyError, ValueError, TypeError): + return MatrixErrorEvent.from_dict(server, "Error syncing", False, + parsed_dict) + + def _execute_joined_info(self, info): + server = self.server + + if info.room_id not in server.buffers: + matrix_create_room_buffer(server, info.room_id) + + room = server.rooms[info.room_id] + buf = server.buffers[info.room_id] + + if not room.prev_batch: + room.prev_batch = info.prev_batch + + tags = tags_for_message("message") + for message in info.events: + message.prnt(room, buf, tags) + + def execute(self): + server = self.server + + # we got the same batch again, nothing to do + if self.next_batch == server.next_batch: + server.sync() + return + + map(self._execute_joined_info, self.joined_room_infos) + + server.next_batch = self.next_batch + server.sync() + + +class RoomInfo(): + + def __init__(self, room_id, prev_batch, events): + # type: (str, str, List[Any]) -> None + self.room_id = room_id + self.prev_batch = prev_batch + self.events = events + + @staticmethod + def _message_from_event(event): + # The transaction id will only be present for events that are send out from + # this client, since we print out our own messages as soon as we get a + # receive confirmation from the server we don't care about our own messages + # in a sync event. More info under: + # https://github.com/matrix-org/matrix-doc/blob/master/api/client-server/definitions/event.yaml#L53 + if "transaction_id" in event["unsigned"]: + return None + + if "redacted_by" in event["unsigned"]: + return RedactedMessage.from_dict(event) + + return Message.from_dict(event) + + @staticmethod + def _event_from_dict(event): + if event['type'] == 'm.room.message': + return RoomInfo._message_from_event(event) + + @classmethod + def from_dict(cls, room_id, parsed_dict): + prev_batch = sanitize_id(parsed_dict['timeline']['prev_batch']) + + events = [] + + state_dict = parsed_dict['state']['events'] + timeline_dict = parsed_dict['timeline']['events'] + + for event in timeline_dict: + events.append(RoomInfo._event_from_dict(event)) + + filtered_events = list(filter(None, events)) + + return cls(room_id, prev_batch, filtered_events) + + class AbstractMessage(): + def __init__(self, event_id, sender, age): self.event_id = event_id self.sender = sender @@ -413,6 +465,7 @@ class AbstractMessage(): class RedactedMessage(AbstractMessage): + def __init__(self, event_id, sender, age, censor, reason=None): self.censor = censor self.reason = reason @@ -424,8 +477,7 @@ class RedactedMessage(AbstractMessage): sender = sanitize_id(event["sender"]) age = event["unsigned"]["age"] - censor = sanitize_id( - event['unsigned']['redacted_because']['sender']) + censor = sanitize_id(event['unsigned']['redacted_because']['sender']) reason = None if 'reason' in event['unsigned']['redacted_because']['content']: @@ -439,12 +491,7 @@ class RedactedMessage(AbstractMessage): color = color_for_tags(color_name) date = date_from_age(self.age) - event_tags = add_event_tags( - self.event_id, - nick, - color, - tags - ) + event_tags = add_event_tags(self.event_id, nick, color, tags) reason = (", reason: \"{reason}\"".format(reason=self.reason) if self.reason else "") @@ -469,14 +516,8 @@ class RedactedMessage(AbstractMessage): class Message(AbstractMessage): - def __init__( - self, - event_id, - sender, - age, - message, - formatted_message=None - ): + + def __init__(self, event_id, sender, age, message, formatted_message=None): self.message = message self.formatted_message = formatted_message AbstractMessage.__init__(self, event_id, sender, age) @@ -502,19 +543,13 @@ class Message(AbstractMessage): return cls(event_id, sender, age, msg, formatted_msg) def prnt(self, room, buff, tags): - msg = (self.formatted_message.to_weechat() if - self.formatted_message - else self.message) + msg = (self.formatted_message.to_weechat() + if self.formatted_message else self.message) nick, color_name = sender_to_nick_and_color(room, self.sender) color = color_for_tags(color_name) - event_tags = add_event_tags( - self.event_id, - nick, - color, - tags - ) + event_tags = add_event_tags(self.event_id, nick, color, tags) tags_string = ",".join(event_tags) diff --git a/matrix/messages.py b/matrix/messages.py index 5b9431b..7ed2df6 100644 --- a/matrix/messages.py +++ b/matrix/messages.py @@ -28,18 +28,12 @@ from matrix.colors import Formatted from matrix.globals import W, OPTIONS -from matrix.api import ( - MessageType, - MatrixRoom, - MatrixUser -) +from matrix.api import (MessageType, MatrixUser) -from matrix.utils import ( - server_buffer_prnt, - tags_from_line_data, - prnt_debug, - color_for_tags -) +from matrix.rooms import MatrixRoom + +from matrix.utils import (server_buffer_prnt, tags_from_line_data, prnt_debug, + color_for_tags) from matrix.plugin_options import RedactType, DebugType @@ -60,26 +54,14 @@ def add_user_to_nicklist(buf, user): group = W.nicklist_search_group(buf, "", group_name) # TODO make it configurable so we can use a display name or user_id here - W.nicklist_add_nick( - buf, - group, - user.display_name, - user.nick_color, - user.prefix, - get_prefix_color(user.prefix), - 1 - ) + W.nicklist_add_nick(buf, group, user.display_name, user.nick_color, + user.prefix, get_prefix_color(user.prefix), 1) 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 - ) + 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') @@ -142,10 +124,8 @@ def matrix_handle_room_members(server, room_id, event): if full_name == server.user_id: user.nick_color = "weechat.color.chat_nick_self" - W.buffer_set( - buf, - "highlight_words", - ",".join([full_name, user.name, user.display_name])) + W.buffer_set(buf, "highlight_words", + ",".join([full_name, user.name, user.display_name])) else: user.nick_color = W.info_get("nick_color_name", user.name) @@ -346,8 +326,7 @@ def matrix_redact_line(data, tags, event): tags.append("matrix_new_redacted") - new_data = {'tags_array': ','.join(tags), - 'message': message} + new_data = {'tags_array': ','.join(tags), 'message': message} W.hdata_update(hdata_line_data, data, new_data) @@ -363,11 +342,7 @@ def matrix_handle_room_redaction(server, room_id, event): if own_lines: hdata_line = W.hdata_get('line') - line = W.hdata_pointer( - W.hdata_get('lines'), - own_lines, - 'last_line' - ) + line = W.hdata_pointer(W.hdata_get('lines'), own_lines, 'last_line') while line: data = W.hdata_pointer(hdata_line, line, 'data') @@ -501,11 +476,11 @@ def matrix_handle_room_events(server, room_id, room_events): matrix_handle_room_power_levels(server, room_id, event) # These events are unimportant for us. - elif event["type"] in ["m.room.create", "m.room.join_rules", - "m.room.history_visibility", - "m.room.canonical_alias", - "m.room.guest_access", - "m.room.third_party_invite"]: + elif event["type"] in [ + "m.room.create", "m.room.join_rules", + "m.room.history_visibility", "m.room.canonical_alias", + "m.room.guest_access", "m.room.third_party_invite" + ]: pass elif event["type"] == "m.room.name": @@ -538,8 +513,7 @@ def matrix_handle_room_events(server, room_id, room_events): else: message = ("{prefix}Handling of room event type " "{type} not implemented").format( - type=event['type'], - prefix=W.prefix("error")) + type=event['type'], prefix=W.prefix("error")) W.prnt(server.server_buffer, message) @@ -582,11 +556,8 @@ def matrix_handle_room_info(server, room_info): matrix_handle_room_events(server, room_id, room['timeline']['events']) for room_id, room in room_info['invite'].items(): - matrix_handle_invite_events( - server, - room_id, - room['invite_state']['events'] - ) + matrix_handle_invite_events(server, room_id, + room['invite_state']['events']) def matrix_sort_old_messages(server, room_id): @@ -598,11 +569,7 @@ def matrix_sort_old_messages(server, room_id): if own_lines: hdata_line = W.hdata_get('line') hdata_line_data = W.hdata_get('line_data') - line = W.hdata_pointer( - W.hdata_get('lines'), - own_lines, - 'first_line' - ) + line = W.hdata_pointer(W.hdata_get('lines'), own_lines, 'first_line') while line: data = W.hdata_pointer(hdata_line, line, 'data') @@ -611,18 +578,18 @@ def matrix_sort_old_messages(server, room_id): if data: date = W.hdata_time(hdata_line_data, data, 'date') - print_date = W.hdata_time(hdata_line_data, data, - 'date_printed') + print_date = W.hdata_time(hdata_line_data, data, 'date_printed') tags = tags_from_line_data(data) prefix = W.hdata_string(hdata_line_data, data, 'prefix') - message = W.hdata_string(hdata_line_data, data, - 'message') + message = W.hdata_string(hdata_line_data, data, 'message') - line_data = {'date': date, - 'date_printed': print_date, - 'tags_array': ','.join(tags), - 'prefix': prefix, - 'message': message} + line_data = { + 'date': date, + 'date_printed': print_date, + 'tags_array': ','.join(tags), + 'prefix': prefix, + 'message': message + } lines.append(line_data) @@ -645,11 +612,7 @@ def matrix_update_buffer_lines(new_lines, own_lines): hdata_line = W.hdata_get('line') hdata_line_data = W.hdata_get('line_data') - line = W.hdata_pointer( - W.hdata_get('lines'), - own_lines, - 'first_line' - ) + line = W.hdata_pointer(W.hdata_get('lines'), own_lines, 'first_line') while line: data = W.hdata_pointer(hdata_line, line, 'data') @@ -661,8 +624,8 @@ def matrix_update_buffer_lines(new_lines, own_lines): def matrix_handle_message( - server, # type: MatrixServer - message, # type: MatrixMessage + server, # type: MatrixServer + message, # type: MatrixMessage ): # type: (...) -> None message_type = message.type @@ -702,20 +665,8 @@ def matrix_handle_message( matrix_sort_old_messages(server, message.room_id) elif message_type is MessageType.SYNC: - next_batch = response['next_batch'] - - # we got the same batch again, nothing to do - if next_batch == server.next_batch: - server.sync() - return - - room_info = response['rooms'] - matrix_handle_room_info(server, room_info) - - server.next_batch = next_batch - - # TODO add a delay to this - server.sync() + event = message.event + event.execute() else: server_buffer_prnt( @@ -737,8 +688,7 @@ def handle_http_response(server, message): # TODO try to resend the message if decoding has failed? message = ("{prefix}matrix: Error decoding json response from " "server: {error}").format( - prefix=W.prefix("error"), - error=error) + prefix=W.prefix("error"), error=error) W.prnt(server.server_buffer, message) return @@ -805,11 +755,9 @@ def handle_http_response(server, message): else: server_buffer_prnt( - server, - ("{prefix}Unhandled {status_code} error, please inform " - "the developers about this.").format( - prefix=W.prefix("error"), - status_code=status_code)) + server, ("{prefix}Unhandled {status_code} error, please inform " + "the developers about this.").format( + prefix=W.prefix("error"), status_code=status_code)) server_buffer_prnt(server, pprint.pformat(message.type)) server_buffer_prnt(server, pprint.pformat(message.request.payload)) @@ -828,7 +776,8 @@ def handle_http_response(server, message): s=(message.send_time - message.creation_time) * 1000, r=(message.receive_time - message.send_time) * 1000, h=(done_time - message.receive_time) * 1000, - total=(done_time - message.creation_time) * 1000,) + total=(done_time - message.creation_time) * 1000, + ) prnt_debug(DebugType.TIMING, server, info_message) return diff --git a/matrix/rooms.py b/matrix/rooms.py new file mode 100644 index 0000000..85c4984 --- /dev/null +++ b/matrix/rooms.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# Copyright © 2018 Damir Jelić +# +# Permission to use, copy, modify, and/or distribute this software for +# any purpose with or without fee is hereby granted, provided that the +# above copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER +# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from __future__ import unicode_literals + +from matrix.globals import W +from matrix.utils import strip_matrix_server + + +class MatrixRoom: + + def __init__(self, room_id): + # type: (str) -> None + # yapf: disable + self.room_id = room_id # type: str + self.alias = room_id # type: str + self.topic = "" # type: str + self.topic_author = "" # type: str + self.topic_date = None # type: datetime.datetime + self.prev_batch = "" # type: str + self.users = dict() # type: Dict[str, MatrixUser] + self.encrypted = False # type: bool + # 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") + + server.buffers[room_id] = buf + server.rooms[room_id] = MatrixRoom(room_id)