Add initial backlog event.

The backlog event currently only supports non redacted messages.
This commit is contained in:
poljar (Damir Jelić) 2018-02-16 15:15:56 +01:00
parent afd595b780
commit 3b77689c7d
4 changed files with 280 additions and 30 deletions

View file

@ -421,6 +421,23 @@ class MatrixBacklogMessage(MatrixMessage):
data 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): class MatrixJoinMessage(MatrixMessage):
def __init__(self, client, room_id): def __init__(self, client, room_id):

View file

@ -18,9 +18,18 @@ from __future__ import unicode_literals
from builtins import str from builtins import str
import time import time
import math
from functools import partial
from matrix.globals import W, OPTIONS 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): def sanitize_id(string):
# type: (unicode) -> unicode # type: (unicode) -> unicode
@ -40,6 +49,38 @@ def sanitize_id(string):
return string.translate(remap) 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(): class MatrixEvent():
def __init__(self, server): def __init__(self, server):
self.server = server self.server = server
@ -104,7 +145,7 @@ class MatrixLoginEvent(MatrixEvent):
sanitize_id(parsed_dict["user_id"]), sanitize_id(parsed_dict["user_id"]),
sanitize_id(parsed_dict["access_token"]) sanitize_id(parsed_dict["access_token"])
) )
except (KeyError, TypeError): except (KeyError, TypeError, ValueError):
return MatrixErrorEvent.from_dict( return MatrixErrorEvent.from_dict(
server, server,
"Error logging in", "Error logging in",
@ -153,7 +194,7 @@ class MatrixSendEvent(MatrixEvent):
sanitize_id(parsed_dict["event_id"]), sanitize_id(parsed_dict["event_id"]),
message message
) )
except (KeyError, TypeError): except (KeyError, TypeError, ValueError):
return MatrixErrorEvent.from_dict( return MatrixErrorEvent.from_dict(
server, server,
"Error sending message", "Error sending message",
@ -178,7 +219,7 @@ class MatrixTopicEvent(MatrixEvent):
sanitize_id(parsed_dict["event_id"]), sanitize_id(parsed_dict["event_id"]),
topic topic
) )
except (KeyError, TypeError): except (KeyError, TypeError, ValueError):
return MatrixErrorEvent.from_dict( return MatrixErrorEvent.from_dict(
server, server,
"Error setting topic", "Error setting topic",
@ -203,7 +244,7 @@ class MatrixRedactEvent(MatrixEvent):
sanitize_id(parsed_dict["event_id"]), sanitize_id(parsed_dict["event_id"]),
reason reason
) )
except (KeyError, TypeError): except (KeyError, TypeError, ValueError):
return MatrixErrorEvent.from_dict( return MatrixErrorEvent.from_dict(
server, server,
"Error redacting message", "Error redacting message",
@ -226,7 +267,7 @@ class MatrixJoinEvent(MatrixEvent):
room_id, room_id,
sanitize_id(parsed_dict["room_id"]), sanitize_id(parsed_dict["room_id"]),
) )
except (KeyError, TypeError): except (KeyError, TypeError, ValueError):
return MatrixErrorEvent.from_dict( return MatrixErrorEvent.from_dict(
server, server,
"Error joining room", "Error joining room",
@ -281,3 +322,153 @@ class MatrixInviteEvent(MatrixEvent):
False, False,
parsed_dict 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)

View file

@ -660,17 +660,6 @@ def matrix_update_buffer_lines(new_lines, own_lines):
line = W.hdata_move(hdata_line, line, 1) 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( def matrix_handle_message(
server, # type: MatrixServer server, # type: MatrixServer
message, # type: MatrixMessage message, # type: MatrixMessage
@ -707,6 +696,11 @@ def matrix_handle_message(
event = message.event event = message.event
event.execute() 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: elif message_type is MessageType.SYNC:
next_batch = response['next_batch'] next_batch = response['next_batch']
@ -723,19 +717,6 @@ def matrix_handle_message(
# TODO add a delay to this # TODO add a delay to this
server.sync() 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: else:
server_buffer_prnt( server_buffer_prnt(
server, server,

View file

@ -119,3 +119,64 @@ def color_for_tags(color):
option = W.config_get(color) option = W.config_get(color)
return W.config_string(option) return W.config_string(option)
return color 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