From 3b77689c7d57a53361b14b975218feb1fbc582a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?poljar=20=28Damir=20Jeli=C4=87=29?= Date: Fri, 16 Feb 2018 15:15:56 +0100 Subject: [PATCH] Add initial backlog event. The backlog event currently only supports non redacted messages. --- matrix/api.py | 17 ++++ matrix/events.py | 203 +++++++++++++++++++++++++++++++++++++++++++-- matrix/messages.py | 29 ++----- matrix/utils.py | 61 ++++++++++++++ 4 files changed, 280 insertions(+), 30 deletions(-) diff --git a/matrix/api.py b/matrix/api.py index e29fcc4..33b50ce 100644 --- a/matrix/api.py +++ b/matrix/api.py @@ -421,6 +421,23 @@ class MatrixBacklogMessage(MatrixMessage): data ) + def decode_body(self, server): + try: + parsed_dict = json.loads( + self.response.body, + encoding='utf-8', + ) + self.event = MatrixEvents.MatrixBacklogEvent.from_dict( + server, + self.room_id, + parsed_dict + ) + + return (True, None) + + except json.decoder.JSONDecodeError as error: + return (False, error) + class MatrixJoinMessage(MatrixMessage): def __init__(self, client, room_id): diff --git a/matrix/events.py b/matrix/events.py index bf5dbf6..d65cb80 100644 --- a/matrix/events.py +++ b/matrix/events.py @@ -18,9 +18,18 @@ from __future__ import unicode_literals from builtins import str import time +import math +from functools import partial from matrix.globals import W, OPTIONS -from matrix.utils import color_for_tags +from matrix.utils import ( + color_for_tags, + date_from_age, + sender_to_nick_and_color, + tags_for_message, + add_event_tags +) +from matrix.colors import Formatted def sanitize_id(string): # type: (unicode) -> unicode @@ -40,6 +49,38 @@ def sanitize_id(string): return string.translate(remap) +def sanitize_age(age): + # type: (int) -> int + if not isinstance(age, int): + raise TypeError + + if math.isnan(age): + raise ValueError + + if math.isinf(age): + raise ValueError + + if age < 0: + raise ValueError + + return age + + +def sanitize_text(string): + # type: (str) -> str + if not isinstance(string, str): + raise TypeError + + 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 @@ -104,7 +145,7 @@ class MatrixLoginEvent(MatrixEvent): sanitize_id(parsed_dict["user_id"]), sanitize_id(parsed_dict["access_token"]) ) - except (KeyError, TypeError): + except (KeyError, TypeError, ValueError): return MatrixErrorEvent.from_dict( server, "Error logging in", @@ -153,7 +194,7 @@ class MatrixSendEvent(MatrixEvent): sanitize_id(parsed_dict["event_id"]), message ) - except (KeyError, TypeError): + except (KeyError, TypeError, ValueError): return MatrixErrorEvent.from_dict( server, "Error sending message", @@ -178,7 +219,7 @@ class MatrixTopicEvent(MatrixEvent): sanitize_id(parsed_dict["event_id"]), topic ) - except (KeyError, TypeError): + except (KeyError, TypeError, ValueError): return MatrixErrorEvent.from_dict( server, "Error setting topic", @@ -203,7 +244,7 @@ class MatrixRedactEvent(MatrixEvent): sanitize_id(parsed_dict["event_id"]), reason ) - except (KeyError, TypeError): + except (KeyError, TypeError, ValueError): return MatrixErrorEvent.from_dict( server, "Error redacting message", @@ -226,7 +267,7 @@ class MatrixJoinEvent(MatrixEvent): room_id, sanitize_id(parsed_dict["room_id"]), ) - except (KeyError, TypeError): + except (KeyError, TypeError, ValueError): return MatrixErrorEvent.from_dict( server, "Error joining room", @@ -281,3 +322,153 @@ class MatrixInviteEvent(MatrixEvent): 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 + self.messages = messages + MatrixEvent.__init__(self, server) + + @staticmethod + def _message_from_event(room_id, event): + if room_id != event["room_id"]: + raise ValueError + + if "redacted_by" in event["unsigned"]: + return RedactedMessage.from_dict(event) + + return Message.from_dict(event) + + @classmethod + def from_dict(cls, server, room_id, parsed_dict): + try: + if not parsed_dict["chunk"]: + return cls(server, room_id, None, []) + + end_token = sanitize_id(parsed_dict["end"]) + + message_func = partial( + MatrixBacklogEvent._message_from_event, + room_id + ) + + 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) + except (KeyError, ValueError, TypeError): + return MatrixErrorEvent.from_dict( + server, + "Error fetching backlog", + False, + parsed_dict + ) + + def execute(self): + room = self.server.rooms[self.room_id] + buf = self.server.buffers[self.room_id] + tags = tags_for_message("backlog") + + for message in self.messages: + message.prnt(room, buf, tags) + + room.prev_batch = self.end_token + + +class AbstractMessage(): + def __init__(self, event_id, sender, age): + self.event_id = event_id + self.sender = sender + self.age = age + + +class RedactedMessage(AbstractMessage): + def __init__(self, event_id, sender, age, censor, reason=None): + self.censor = censor + self.reason = reason + AbstractMessage.__init__(self, event_id, sender, age) + + @classmethod + def from_dict(cls, event): + event_id = sanitize_id(event["event_id"]) + sender = sanitize_id(event["sender"]) + age = event["unsigned"]["age"] + + censor = sanitize_id( + event['unsigned']['redacted_because']['sender']) + reason = None + + if 'reason' in event['unsigned']['redacted_because']['content']: + reason = sanitize_text( + event['unsigned']['redacted_because']['content']['reason']) + + return cls(event_id, sender, age, censor, reason) + + def prnt(self, room, buff, tags): + pass + + +class Message(AbstractMessage): + 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) + + @classmethod + def from_dict(cls, event): + event_id = sanitize_id(event["event_id"]) + sender = sanitize_id(event["sender"]) + age = sanitize_age(event["unsigned"]["age"]) + + msg = "" + formatted_msg = None + + if event['content']['msgtype'] == 'm.text': + msg = sanitize_text(event['content']['body']) + + if ('format' in event['content'] and + 'formatted_body' in event['content']): + if event['content']['format'] == "org.matrix.custom.html": + formatted_msg = Formatted.from_html( + sanitize_text(event['content']['formatted_body'])) + + 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) + + 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 + ) + + tags_string = ",".join(event_tags) + + data = "{author}\t{msg}".format(author=nick, msg=msg) + + date = date_from_age(self.age) + W.prnt_date_tags(buff, date, tags_string, data) diff --git a/matrix/messages.py b/matrix/messages.py index 9cd9fe3..5b9431b 100644 --- a/matrix/messages.py +++ b/matrix/messages.py @@ -660,17 +660,6 @@ def matrix_update_buffer_lines(new_lines, own_lines): line = W.hdata_move(hdata_line, line, 1) -def matrix_handle_old_messages(server, room_id, events): - for event in events: - if event['type'] == 'm.room.message': - matrix_handle_room_messages(server, room_id, event, old=True) - # TODO do we wan't to handle topics joins/quits here? - else: - pass - - matrix_sort_old_messages(server, room_id) - - def matrix_handle_message( server, # type: MatrixServer message, # type: MatrixMessage @@ -707,6 +696,11 @@ def matrix_handle_message( event = message.event event.execute() + elif message_type == MessageType.ROOM_MSG: + event = message.event + event.execute() + matrix_sort_old_messages(server, message.room_id) + elif message_type is MessageType.SYNC: next_batch = response['next_batch'] @@ -723,19 +717,6 @@ def matrix_handle_message( # TODO add a delay to this server.sync() - elif message_type == MessageType.ROOM_MSG: - # Response has no messages, that is we already got the oldest message - # in a previous request, nothing to do - if not response['chunk']: - return - - room_id = response['chunk'][0]['room_id'] - room = server.rooms[room_id] - - matrix_handle_old_messages(server, room_id, response['chunk']) - - room.prev_batch = response['end'] - else: server_buffer_prnt( server, diff --git a/matrix/utils.py b/matrix/utils.py index 29da1cd..d012738 100644 --- a/matrix/utils.py +++ b/matrix/utils.py @@ -119,3 +119,64 @@ def color_for_tags(color): option = W.config_get(color) return W.config_string(option) return color + + +def date_from_age(age): + # type: (float) -> int + now = time.time() + date = int(now - (age / 1000)) + return date + + +def strip_matrix_server(string): + # type: (str) -> str + return string.rsplit(":", 1)[0] + + +def shorten_sender(sender): + # type: (str) -> str + return strip_matrix_server(sender)[1:] + + +def sender_to_nick_and_color(room, sender): + nick = sender + nick_color_name = "default" + + if sender in room.users: + user = room.users[sender] + nick = user.display_name + nick_color_name = user.nick_color + else: + nick = shorten_sender(sender) + nick_color_name = W.info_get("nick_color_name", nick) + + return (nick, nick_color_name) + + +def tags_for_message(message_type): + default_tags = { + "message": [ + "matrix_message", + "notify_message", + "log1" + ], + "backlog": [ + "matrix_message", + "notify_message", + "no_log", + "no_highlight" + ] + } + + return default_tags[message_type] + + +def add_event_tags(event_id, nick, color, tags): + if not tags: + tags = tags_for_message("message") + + tags.append("nick_{nick}".format(nick=nick)) + tags.append("perfix_nick_{color}".format(color=color_for_tags(color))) + tags.append("matrix_id_{event_id}".format(event_id=event_id)) + + return tags