Add initial sync event implementation.

This commit is contained in:
poljar (Damir Jelić) 2018-02-22 13:39:37 +01:00
parent 9b25026a3a
commit 2ac8bef499
4 changed files with 282 additions and 244 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

65
matrix/rooms.py Normal file
View file

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright © 2018 Damir Jelić <poljar@termina.org.uk>
#
# 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)