server: Continue nio migration.

This commit is contained in:
Damir Jelić 2018-07-20 17:51:48 +02:00
parent 91eec1ad85
commit 45be743c07
7 changed files with 199 additions and 1069 deletions

34
main.py
View file

@ -145,14 +145,6 @@ def print_certificate_info(buff, sock, cert):
W.prnt(buff, message) W.prnt(buff, message)
@utf8_decode
def matrix_event_timer_cb(server_name, remaining_calls):
server = SERVERS[server_name]
server.handle_events()
return W.WEECHAT_RC_OK
def wrap_socket(server, file_descriptor): def wrap_socket(server, file_descriptor):
# type: (MatrixServer, int) -> None # type: (MatrixServer, int) -> None
sock = None # type: socket.socket sock = None # type: socket.socket
@ -399,32 +391,6 @@ def connect_cb(data, status, gnutls_rc, sock, error, ip_address):
return W.WEECHAT_RC_OK return W.WEECHAT_RC_OK
@utf8_decode
def room_input_cb(server_name, buffer, input_data):
server = SERVERS[server_name]
if not server.connected:
message = "{prefix}matrix: you are not connected to the server".format(
prefix=W.prefix("error"))
W.prnt(buffer, message)
return W.WEECHAT_RC_ERROR
room_id = key_from_value(server.buffers, buffer)
room = server.rooms[room_id]
formatted_data = Formatted.from_input_line(input_data)
if room.encrypted:
server.send_room_message(room_id, formatted_data)
return W.WEECHAT_RC_OK
message = MatrixSendMessage(
server.client, room_id=room_id, formatted_message=formatted_data)
server.send_or_queue(message)
return W.WEECHAT_RC_OK
@utf8_decode @utf8_decode
def room_close_cb(data, buffer): def room_close_cb(data, buffer):
W.prnt("", W.prnt("",

View file

@ -45,13 +45,12 @@ def matrix_bar_item_name(data, item, window, buffer, extra_info):
color = ("status_name_ssl" color = ("status_name_ssl"
if server.ssl_context.check_hostname else "status_name") if server.ssl_context.check_hostname else "status_name")
room_id = key_from_value(server.buffers, buffer) room_buffer = server.find_room_from_ptr(buffer)
room = room_buffer.room
room = server.rooms[room_id]
return "{color}{name}".format( return "{color}{name}".format(
color=W.color(color), color=W.color(color),
name=room.display_name(server.user_id)) name=room.display_name())
elif buffer == server.server_buffer: elif buffer == server.server_buffer:
color = ("status_name_ssl" color = ("status_name_ssl"
@ -92,14 +91,14 @@ def matrix_bar_item_buffer_modes(data, item, window, buffer, extra_info):
# pylint: disable=unused-argument # pylint: disable=unused-argument
for server in SERVERS.values(): for server in SERVERS.values():
if buffer in server.buffers.values(): if buffer in server.buffers.values():
room_id = key_from_value(server.buffers, buffer) room_buffer = server.find_room_from_ptr(buffer)
room = server.rooms[room_id] room = room_buffer.room
modes = [] modes = []
if room.encrypted: if room.encrypted:
modes.append("🔐") modes.append("🔐")
if room.backlog_pending: if room_buffer.backlog_pending:
modes.append("") modes.append("")
return "".join(modes) return "".join(modes)

View file

@ -20,6 +20,7 @@ from __future__ import unicode_literals
import time import time
from builtins import super from builtins import super
from functools import partial from functools import partial
from typing import NamedTuple
from .globals import W, SERVERS, OPTIONS, SCRIPT_NAME, ENCRYPTION from .globals import W, SERVERS, OPTIONS, SCRIPT_NAME, ENCRYPTION
from .utf import utf8_decode from .utf import utf8_decode
@ -32,29 +33,35 @@ from .utils import (
) )
from .plugin_options import RedactType from .plugin_options import RedactType
from nio import (
from .rooms import (
RoomNameEvent,
RoomAliasEvent,
RoomMembershipEvent,
RoomTopicEvent,
RoomMessageText, RoomMessageText,
RoomMessageEmote, RoomMemberEvent,
RoomMessageNotice, PowerLevelsEvent,
RoomMessageMedia,
RoomMessageUnknown,
RoomRedactionEvent,
RoomRedactedMessageEvent,
RoomEncryptionEvent, RoomEncryptionEvent,
RoomPowerLevels, RedactedEvent,
UndecryptedEvent RoomAliasEvent,
RoomTopicEvent,
RoomMessageEmote,
RoomNameEvent
) )
OwnMessage = NamedTuple("OwnMessage", [
("sender", str),
("age", int),
("event_id", str),
("formatted_message", Formatted)
])
class OwnAction(OwnMessage):
pass
@utf8_decode @utf8_decode
def room_buffer_input_cb(server_name, buffer, input_data): def room_buffer_input_cb(server_name, buffer, input_data):
server = SERVERS[server_name] server = SERVERS[server_name]
room, room_buffer = server.find_room_from_ptr(buffer) room_buffer = server.find_room_from_ptr(buffer)
if not room_buffer: if not room_buffer:
# TODO log error # TODO log error
@ -66,7 +73,7 @@ def room_buffer_input_cb(server_name, buffer, input_data):
formatted_data = Formatted.from_input_line(input_data) formatted_data = Formatted.from_input_line(input_data)
server.send_room_message(room, formatted_data) server.send_room_message(room_buffer.room, formatted_data)
return W.WEECHAT_RC_OK return W.WEECHAT_RC_OK
@ -669,6 +676,7 @@ class WeechatChannelBuffer(object):
class RoomBuffer(object): class RoomBuffer(object):
def __init__(self, room, server_name): def __init__(self, room, server_name):
self.room = room self.room = room
self.backlog_pending = False
# This dict remembers the connection from a user_id to the name we # This dict remembers the connection from a user_id to the name we
# displayed in the buffer # displayed in the buffer
@ -692,12 +700,14 @@ class RoomBuffer(object):
def join(event, date, is_state): def join(event, date, is_state):
user = self.room.users[event.sender] user = self.room.users[event.sender]
short_name = shorten_sender(user.user_id)
# TODO make this configurable # TODO make this configurable
if user.name in self.displayed_nicks.values(): if short_name in self.displayed_nicks.values():
# Use the full user id, but don't include the @ # Use the full user id, but don't include the @
nick = event.sender[1:] nick = event.sender[1:]
else: else:
nick = user.name nick = short_name
buffer_user = RoomUser(nick, event.sender, user.power_level) buffer_user = RoomUser(nick, event.sender, user.power_level)
self.displayed_nicks[event.sender] = nick self.displayed_nicks[event.sender] = nick
@ -708,11 +718,11 @@ class RoomBuffer(object):
self.weechat_buffer.join( self.weechat_buffer.join(
buffer_user, buffer_user,
server_ts_to_weechat(event.timestamp), server_ts_to_weechat(event.server_timestamp),
not is_state not is_state
) )
date = server_ts_to_weechat(event.timestamp) date = server_ts_to_weechat(event.server_timestamp)
if event.content["membership"] == "join": if event.content["membership"] == "join":
if event.prev_content and "membership" in event.prev_content: if event.prev_content and "membership" in event.prev_content:
@ -743,7 +753,7 @@ class RoomBuffer(object):
self.weechat_buffer.invite(event.state_key, date) self.weechat_buffer.invite(event.state_key, date)
return return
room_name = self.room.display_name(self.room.own_user_id) room_name = self.room.display_name()
self.weechat_buffer.short_name = room_name self.weechat_buffer.short_name = room_name
def _redact_line(self, event): def _redact_line(self, event):
@ -808,14 +818,14 @@ class RoomBuffer(object):
def _handle_redacted_message(self, event): def _handle_redacted_message(self, event):
nick = self.find_nick(event.sender) nick = self.find_nick(event.sender)
date = server_ts_to_weechat(event.timestamp) date = server_ts_to_weechat(event.server_timestamp)
tags = self.get_event_tags(event) tags = self.get_event_tags(event)
tags.append(SCRIPT_NAME + "_redacted") tags.append(SCRIPT_NAME + "_redacted")
reason = (", reason: \"{reason}\"".format(reason=event.reason) reason = (", reason: \"{reason}\"".format(reason=event.reason)
if event.reason else "") if event.reason else "")
censor = self.find_nick(event.censor) censor = self.find_nick(event.redacter)
data = ("{del_color}<{log_color}Message redacted by: " data = ("{del_color}<{log_color}Message redacted by: "
"{censor}{log_color}{reason}{del_color}>{ncolor}").format( "{censor}{log_color}{reason}{del_color}>{ncolor}").format(
@ -833,7 +843,7 @@ class RoomBuffer(object):
self.weechat_buffer.change_topic( self.weechat_buffer.change_topic(
nick, nick,
event.topic, event.topic,
server_ts_to_weechat(event.timestamp), server_ts_to_weechat(event.server_timestamp),
not is_state) not is_state)
@staticmethod @staticmethod
@ -841,12 +851,13 @@ class RoomBuffer(object):
return ["matrix_id_{}".format(event.event_id)] return ["matrix_id_{}".format(event.event_id)]
def _handle_power_level(self, event): def _handle_power_level(self, event):
for user_id in self.room.power_levels: for user_id in self.room.power_levels.users:
if user_id in self.displayed_nicks: if user_id in self.displayed_nicks:
nick = self.find_nick(user_id) nick = self.find_nick(user_id)
user = self.weechat_buffer.users[nick] user = self.weechat_buffer.users[nick]
user.power_level = self.room.power_levels[user_id] user.power_level = self.room.power_levels.get_user_level(
user_id)
# There is no way to change the group of a user without # There is no way to change the group of a user without
# removing him from the nicklist # removing him from the nicklist
@ -854,67 +865,48 @@ class RoomBuffer(object):
self.weechat_buffer._add_user_to_nicklist(user) self.weechat_buffer._add_user_to_nicklist(user)
def handle_state_event(self, event): def handle_state_event(self, event):
if isinstance(event, RoomMembershipEvent): if isinstance(event, RoomMemberEvent):
self.handle_membership_events(event, True) self.handle_membership_events(event, True)
elif isinstance(event, RoomTopicEvent): elif isinstance(event, RoomTopicEvent):
self._handle_topic(event, True) self._handle_topic(event, True)
elif isinstance(event, RoomPowerLevels): elif isinstance(event, PowerLevelsEvent):
self._handle_power_level(event) self._handle_power_level(event)
def handle_timeline_event(self, event): def handle_timeline_event(self, event):
if isinstance(event, RoomMembershipEvent): if isinstance(event, RoomMemberEvent):
self.handle_membership_events(event, False) self.handle_membership_events(event, False)
elif isinstance(event, (RoomNameEvent, RoomAliasEvent)): elif isinstance(event, (RoomNameEvent, RoomAliasEvent)):
room_name = self.room.display_name(self.room.own_user_id) room_name = self.room.display_name()
self.weechat_buffer.short_name = room_name self.weechat_buffer.short_name = room_name
elif isinstance(event, RoomTopicEvent): elif isinstance(event, RoomTopicEvent):
self._handle_topic(event, False) self._handle_topic(event, False)
elif isinstance(event, RoomMessageText): # Emotes are a subclass of RoomMessageText, so put them before the text
nick = self.find_nick(event.sender) # ones
data = (event.formatted_message.to_weechat()
if event.formatted_message else event.message)
date = server_ts_to_weechat(event.timestamp)
self.weechat_buffer.message(
nick,
data,
date,
self.get_event_tags(event)
)
elif isinstance(event, RoomMessageEmote): elif isinstance(event, RoomMessageEmote):
nick = self.find_nick(event.sender) nick = self.find_nick(event.sender)
date = server_ts_to_weechat(event.timestamp) date = server_ts_to_weechat(event.server_timestamp)
self.weechat_buffer.action( self.weechat_buffer.action(
nick, nick,
event.message, event.body,
date, date,
self.get_event_tags(event) self.get_event_tags(event)
) )
elif isinstance(event, RoomMessageNotice):
elif isinstance(event, RoomMessageText):
nick = self.find_nick(event.sender) nick = self.find_nick(event.sender)
date = server_ts_to_weechat(event.timestamp) formatted = None
self.weechat_buffer.notice(
nick,
event.message,
date,
self.get_event_tags(event)
)
elif isinstance(event, RoomMessageMedia): if event.formatted_body:
nick = self.find_nick(event.sender) formatted = Formatted.from_html(event.formatted_body)
date = server_ts_to_weechat(event.timestamp)
http_url = mxc_to_http(event.url)
url = http_url if http_url else event.url
description = ("/{}".format(event.description) data = (formatted.to_weechat()
if event.description else "") if formatted else event.body)
data = "{url}{desc}".format(url=url, desc=description)
date = server_ts_to_weechat(event.server_timestamp)
self.weechat_buffer.message( self.weechat_buffer.message(
nick, nick,
data, data,
@ -922,24 +914,51 @@ class RoomBuffer(object):
self.get_event_tags(event) self.get_event_tags(event)
) )
elif isinstance(event, RoomMessageUnknown): # elif isinstance(event, RoomMessageNotice):
nick = self.find_nick(event.sender) # nick = self.find_nick(event.sender)
date = server_ts_to_weechat(event.timestamp) # date = server_ts_to_weechat(event.server_timestamp)
data = ("Unknown message of type {t}, body: {body}").format( # self.weechat_buffer.notice(
t=event.message_type, # nick,
body=event.message # event.message,
) # date,
self.weechat_buffer.message( # self.get_event_tags(event)
nick, # )
data,
date,
self.get_event_tags(event)
)
elif isinstance(event, RoomRedactionEvent): # elif isinstance(event, RoomMessageMedia):
self._redact_line(event) # nick = self.find_nick(event.sender)
# date = server_ts_to_weechat(event.server_timestamp)
# http_url = mxc_to_http(event.url)
# url = http_url if http_url else event.url
elif isinstance(event, RoomRedactedMessageEvent): # description = ("/{}".format(event.description)
# if event.description else "")
# data = "{url}{desc}".format(url=url, desc=description)
# self.weechat_buffer.message(
# nick,
# data,
# date,
# self.get_event_tags(event)
# )
# elif isinstance(event, RoomMessageUnknown):
# nick = self.find_nick(event.sender)
# date = server_ts_to_weechat(event.server_timestamp)
# data = ("Unknown message of type {t}, body: {body}").format(
# t=event.message_type,
# body=event.message
# )
# self.weechat_buffer.message(
# nick,
# data,
# date,
# self.get_event_tags(event)
# )
# elif isinstance(event, RoomRedactionEvent):
# self._redact_line(event)
elif isinstance(event, RedactedEvent):
self._handle_redacted_message(event) self._handle_redacted_message(event)
elif isinstance(event, RoomEncryptionEvent): elif isinstance(event, RoomEncryptionEvent):
@ -951,38 +970,44 @@ class RoomBuffer(object):
"this room.") "this room.")
self.weechat_buffer.error(message) self.weechat_buffer.error(message)
elif isinstance(event, RoomPowerLevels): elif isinstance(event, PowerLevelsEvent):
self._handle_power_level(event) self._handle_power_level(event)
elif isinstance(event, UndecryptedEvent): # elif isinstance(event, UndecryptedEvent):
nick = self.find_nick(event.sender) # nick = self.find_nick(event.sender)
date = server_ts_to_weechat(event.timestamp) # date = server_ts_to_weechat(event.server_timestamp)
data = ("Error decrypting event session " # data = ("Error decrypting event session "
"id: {}".format(event.session_id)) # "id: {}".format(event.session_id))
self.weechat_buffer.message( # self.weechat_buffer.message(
nick, # nick,
data, # data,
date, # date,
self.get_event_tags(event) # self.get_event_tags(event)
) # )
else: else:
W.prnt("", "Unhandled event of type {}.".format( W.prnt("", "Unhandled event of type {}.".format(
type(event).__name__)) type(event).__name__))
def self_message(self, message): def self_message(self, message):
# type: (OwnMessage) -> None
nick = self.find_nick(self.room.own_user_id) nick = self.find_nick(self.room.own_user_id)
data = (message.formatted_message.to_weechat() data = message.formatted_message.to_weechat()
if message.formatted_message
else message.message)
date = server_ts_to_weechat(message.timestamp) # TODO event_id tag is missing
date = message.age
self.weechat_buffer.self_message(nick, data, date) self.weechat_buffer.self_message(nick, data, date)
def self_action(self, message): def self_action(self, message):
# type: (OwnMessage) -> None
nick = self.find_nick(self.room.own_user_id) nick = self.find_nick(self.room.own_user_id)
date = server_ts_to_weechat(message.timestamp) date = message.age
self.weechat_buffer.self_action(nick, message.message, date) # TODO event_id tag is missing
self.weechat_buffer.self_action(
nick,
message.formatted_message.to_weechat(),
date
)
def old_redacted(self, event): def old_redacted(self, event):
tags = [ tags = [
@ -994,7 +1019,7 @@ class RoomBuffer(object):
reason = (", reason: \"{reason}\"".format(reason=event.reason) reason = (", reason: \"{reason}\"".format(reason=event.reason)
if event.reason else "") if event.reason else "")
censor = self.find_nick(event.censor) censor = self.find_nick(event.redacter)
data = ("{del_color}<{log_color}Message redacted by: " data = ("{del_color}<{log_color}Message redacted by: "
"{censor}{log_color}{reason}{del_color}>{ncolor}").format( "{censor}{log_color}{reason}{del_color}>{ncolor}").format(
@ -1007,7 +1032,7 @@ class RoomBuffer(object):
tags += self.get_event_tags(event) tags += self.get_event_tags(event)
nick = self.find_nick(event.sender) nick = self.find_nick(event.sender)
user = self.weechat_buffer._get_user(nick) user = self.weechat_buffer._get_user(nick)
date = server_ts_to_weechat(event.timestamp) date = server_ts_to_weechat(event.server_timestamp)
self.weechat_buffer._print_message(user, data, date, tags) self.weechat_buffer._print_message(user, data, date, tags)
def old_message(self, event): def old_message(self, event):
@ -1022,7 +1047,7 @@ class RoomBuffer(object):
data = (event.formatted_message.to_weechat() data = (event.formatted_message.to_weechat()
if event.formatted_message else event.message) if event.formatted_message else event.message)
user = self.weechat_buffer._get_user(nick) user = self.weechat_buffer._get_user(nick)
date = server_ts_to_weechat(event.timestamp) date = server_ts_to_weechat(event.server_timestamp)
self.weechat_buffer._print_message(user, data, date, tags) self.weechat_buffer._print_message(user, data, date, tags)
def sort_messages(self): def sort_messages(self):
@ -1062,7 +1087,7 @@ class RoomBuffer(object):
for event in events: for event in events:
if isinstance(event, RoomMessageText): if isinstance(event, RoomMessageText):
self.old_message(event) self.old_message(event)
elif isinstance(event, RoomRedactedMessageEvent): elif isinstance(event, RedactedEvent):
self.old_redacted(event) self.old_redacted(event)
self.sort_messages() self.sort_messages()

View file

@ -67,23 +67,23 @@ def hook_commands():
'matrix_command_cb', 'matrix_command_cb',
'') '')
W.hook_command( # W.hook_command(
# Command name and short description # # Command name and short description
'redact', # 'redact',
'redact messages', # 'redact messages',
# Synopsis # # Synopsis
('<message-number>[:"<message-part>"] [<reason>]'), # ('<message-number>[:"<message-part>"] [<reason>]'),
# Description # # Description
("message-number: number of message to redact (starting from 1 for\n" # ("message-number: number of message to redact (starting from 1 for\n"
" the last message received, counting up)\n" # " the last message received, counting up)\n"
" message-part: an initial part of the message (ignored, only used\n" # " message-part: an initial part of the message (ignored, only used\n"
" as visual feedback when using completion)\n" # " as visual feedback when using completion)\n"
" reason: the redaction reason\n"), # " reason: the redaction reason\n"),
# Completions # # Completions
('%(matrix_messages)'), # ('%(matrix_messages)'),
# Function name # # Function name
'matrix_redact_command_cb', # 'matrix_redact_command_cb',
'') # '')
W.hook_command( W.hook_command(
# Command name and short description # Command name and short description
@ -101,15 +101,15 @@ def hook_commands():
matrix_hook_olm_command() matrix_hook_olm_command()
W.hook_command_run('/topic', 'matrix_command_topic_cb', '') # W.hook_command_run('/topic', 'matrix_command_topic_cb', '')
W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '') W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '')
W.hook_command_run('/join', 'matrix_command_join_cb', '') W.hook_command_run('/join', 'matrix_command_join_cb', '')
W.hook_command_run('/part', 'matrix_command_part_cb', '') W.hook_command_run('/part', 'matrix_command_part_cb', '')
W.hook_command_run('/invite', 'matrix_command_invite_cb', '') W.hook_command_run('/invite', 'matrix_command_invite_cb', '')
W.hook_command_run('/kick', 'matrix_command_kick_cb', '') W.hook_command_run('/kick', 'matrix_command_kick_cb', '')
if OPTIONS.enable_backlog: # if OPTIONS.enable_backlog:
hook_page_up() # hook_page_up()
@utf8_decode @utf8_decode
@ -123,7 +123,7 @@ def matrix_me_command_cb(data, buffer, args):
W.prnt(server.server_buffer, message) W.prnt(server.server_buffer, message)
return W.WEECHAT_RC_ERROR return W.WEECHAT_RC_ERROR
room_id = key_from_value(server.buffers, buffer) room_buffer = server.find_room_from_ptr(buffer)
if not args: if not args:
return W.WEECHAT_RC_OK return W.WEECHAT_RC_OK
@ -131,10 +131,10 @@ def matrix_me_command_cb(data, buffer, args):
formatted_data = Formatted.from_input_line(args) formatted_data = Formatted.from_input_line(args)
message = MatrixEmoteMessage( message = MatrixEmoteMessage(
server.client, server.client,
room_id=room_id, room_id=room_buffer.room.room_id,
formatted_message=formatted_data) formatted_message=formatted_data)
if server.rooms[room_id].encrypted: if room_buffer.room.encrypted:
return W.WEECHAT_RC_OK return W.WEECHAT_RC_OK
server.send_or_queue(message) server.send_or_queue(message)
@ -150,9 +150,10 @@ def matrix_me_command_cb(data, buffer, args):
def matrix_fetch_old_messages(server, room_id): def matrix_fetch_old_messages(server, room_id):
room = server.rooms[room_id] room_buffer = server.find_room_from_id(room_id)
room = room_buffer.room
if room.backlog_pending: if room_buffer.backlog_pending:
return return
prev_batch = room.prev_batch prev_batch = room.prev_batch
@ -165,7 +166,7 @@ def matrix_fetch_old_messages(server, room_id):
room_id=room_id, room_id=room_id,
token=prev_batch, token=prev_batch,
limit=OPTIONS.backlog_limit) limit=OPTIONS.backlog_limit)
room.backlog_pending = True room_buffer.backlog_pending = True
W.bar_item_update("buffer_modes") W.bar_item_update("buffer_modes")
server.send_or_queue(message) server.send_or_queue(message)
@ -913,14 +914,15 @@ def matrix_command_topic_cb(data, buffer, command):
for server in SERVERS.values(): for server in SERVERS.values():
if buffer in server.buffers.values(): if buffer in server.buffers.values():
topic = None topic = None
room_id = key_from_value(server.buffers, buffer) room_buffer = server.find_room_from_ptr(buffer)
split_command = command.split(' ', 1) split_command = command.split(' ', 1)
if len(split_command) == 2: if len(split_command) == 2:
topic = split_command[1] topic = split_command[1]
if not topic: if not topic:
room = server.rooms[room_id] room_buffer = server.find_room_from_ptr(buffer)
room = room_buffer.room
if not room.topic: if not room.topic:
return W.WEECHAT_RC_OK return W.WEECHAT_RC_OK
@ -938,8 +940,8 @@ def matrix_command_topic_cb(data, buffer, command):
topic=room.topic) topic=room.topic)
date = int(time.time()) date = int(time.time())
topic_date = room.topic_date.strftime("%a, %d %b %Y " topic_date = room_buffer.weechat_buffer.topic_date.strftime(
"%H:%M:%S") "%a, %d %b %Y %H:%M:%S")
tags = "matrix_topic,log1" tags = "matrix_topic,log1"
W.prnt_date_tags(buffer, date, tags, message) W.prnt_date_tags(buffer, date, tags, message)
@ -958,7 +960,7 @@ def matrix_command_topic_cb(data, buffer, command):
return W.WEECHAT_RC_OK_EAT return W.WEECHAT_RC_OK_EAT
message = MatrixTopicMessage( message = MatrixTopicMessage(
server.client, room_id=room_id, topic=topic) server.client, room_id=room.room_id, topic=topic)
server.send_or_queue(message) server.send_or_queue(message)
return W.WEECHAT_RC_OK_EAT return W.WEECHAT_RC_OK_EAT

View file

@ -27,12 +27,9 @@ from operator import itemgetter
from matrix.globals import W from matrix.globals import W
from matrix.utils import (tags_for_message, sanitize_id, sanitize_token, from matrix.utils import (tags_for_message, sanitize_id, sanitize_token,
sanitize_text, tags_from_line_data) sanitize_text, tags_from_line_data)
from matrix.rooms import (RoomInfo, RoomMessageText,
RoomMessageEvent, RoomRedactedMessageEvent,
RoomMessageEmote)
from matrix.encryption import OlmDeviceKey, OneTimeKey from matrix.encryption import OlmDeviceKey, OneTimeKey
from .buffer import RoomUser from .buffer import RoomUser, OwnMessage, OwnAction
try: try:
from olm.session import OlmMessage, OlmPreKeyMessage from olm.session import OlmMessage, OlmPreKeyMessage
@ -96,25 +93,6 @@ class MatrixKeyUploadEvent(MatrixEvent):
"keys", False, parsed_dict) "keys", False, parsed_dict)
class MatrixLoginEvent(MatrixEvent):
def __init__(self, server, user_id, device_id, access_token):
self.user_id = user_id
self.access_token = access_token
self.device_id = device_id
MatrixEvent.__init__(self, server)
@classmethod
def from_dict(cls, server, parsed_dict):
try:
return cls(server, sanitize_id(parsed_dict["user_id"]),
sanitize_id(parsed_dict["device_id"]),
sanitize_token(parsed_dict["access_token"]))
except (KeyError, TypeError, ValueError):
return MatrixErrorEvent.from_dict(server, "Error logging in", True,
parsed_dict)
class MatrixSendEvent(MatrixEvent): class MatrixSendEvent(MatrixEvent):
def __init__(self, server, room_id, message): def __init__(self, server, room_id, message):
@ -128,11 +106,9 @@ class MatrixSendEvent(MatrixEvent):
event_id = sanitize_id(parsed_dict["event_id"]) event_id = sanitize_id(parsed_dict["event_id"])
sender = server.user_id sender = server.user_id
age = 0 age = 0
plain_message = message.to_plain()
formatted_message = message formatted_message = message
message = RoomMessageText(event_id, sender, age, plain_message, message = OwnMessage(sender, age, event_id, formatted_message)
formatted_message)
return cls(server, room_id, message) return cls(server, room_id, message)
except (KeyError, TypeError, ValueError): except (KeyError, TypeError, ValueError):
@ -148,11 +124,9 @@ class MatrixEmoteEvent(MatrixSendEvent):
event_id = sanitize_id(parsed_dict["event_id"]) event_id = sanitize_id(parsed_dict["event_id"])
sender = server.user_id sender = server.user_id
age = 0 age = 0
plain_message = message.to_plain()
formatted_message = message formatted_message = message
message = RoomMessageEmote(event_id, sender, age, plain_message, message = OwnAction(sender, age, event_id, formatted_message)
formatted_message)
return cls(server, room_id, message) return cls(server, room_id, message)
except (KeyError, TypeError, ValueError): except (KeyError, TypeError, ValueError):
@ -410,127 +384,3 @@ class MatrixBacklogEvent(MatrixEvent):
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
return MatrixErrorEvent.from_dict(server, "Error fetching backlog", return MatrixErrorEvent.from_dict(server, "Error fetching backlog",
False, parsed_dict) False, parsed_dict)
class MatrixSyncEvent(MatrixEvent):
def __init__(self, server, next_batch, room_infos, invited_infos,
one_time_key_count):
self.next_batch = next_batch
self.joined_room_infos = room_infos
self.invited_room_infos = invited_infos
self.one_time_key_count = one_time_key_count
MatrixEvent.__init__(self, server)
@staticmethod
def _infos_from_dict(olm, 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(olm, room_id, room_dict))
return (join_infos, invite_infos)
@staticmethod
def _get_olm_device_event(server, parsed_dict):
device_key = server.olm.account.identity_keys["curve25519"]
if device_key not in parsed_dict["content"]["ciphertext"]:
return None
sender = sanitize_id(parsed_dict["sender"])
sender_key = sanitize_id(parsed_dict["content"]["sender_key"])
ciphertext = parsed_dict["content"]["ciphertext"].pop(device_key)
message = None
if ciphertext["type"] == 0:
message = OlmPreKeyMessage(ciphertext["body"])
elif ciphertext["type"] == 1:
message = OlmMessage(ciphertext["body"])
else:
raise ValueError("Invalid Olm message type")
olm = server.olm
plaintext = olm.decrypt(sender, sender_key, message)
if not plaintext:
return None
# TODO check sender key
decrypted_sender = plaintext["sender"]
decrypted_recepient = plaintext["recipient"]
decrypted_recepient_key = plaintext["recipient_keys"]["ed25519"]
if (sender != decrypted_sender or
server.user_id != decrypted_recepient or
olm.account.identity_keys["ed25519"] !=
decrypted_recepient_key):
error_message = ("{prefix}matrix: Mismatch in decrypted Olm "
"message").format(prefix=W.prefix("error"))
W.prnt("", error_message)
return None
if plaintext["type"] != "m.room_key":
return None
MatrixSyncEvent._handle_key_event(server, sender_key, plaintext)
@staticmethod
def _handle_key_event(server, sender_key, parsed_dict):
# type: (MatrixServer, str, Dict[Any, Any] -> None
olm = server.olm
content = parsed_dict.pop("content")
if content["algorithm"] != "m.megolm.v1.aes-sha2":
return
room_id = content["room_id"]
session_id = content["session_id"]
session_key = content["session_key"]
if session_id in olm.inbound_group_sessions[room_id]:
return
olm.create_group_session(room_id, session_id, session_key)
@staticmethod
def _get_to_device_events(server, parsed_dict):
# type: (MatrixServer, Dict[Any, Any]) -> None
for event in parsed_dict["events"]:
if event["type"] == "m.room.encrypted":
if (event["content"]["algorithm"] ==
'm.olm.v1.curve25519-aes-sha2'):
MatrixSyncEvent._get_olm_device_event(server, event)
@classmethod
def from_dict(cls, server, parsed_dict):
try:
next_batch = sanitize_id(parsed_dict["next_batch"])
one_time_key_count = 0
if "device_one_time_keys_count" in parsed_dict:
if ("signed_curve25519" in
parsed_dict["device_one_time_keys_count"]):
one_time_key_count = (
parsed_dict["device_one_time_keys_count"]["signed_curve25519"])
MatrixSyncEvent._get_to_device_events(
server, parsed_dict.pop("to_device"))
room_info_dict = parsed_dict["rooms"]
join_infos, invite_infos = MatrixSyncEvent._infos_from_dict(
server.olm, room_info_dict)
return cls(server, next_batch, join_infos, invite_infos,
one_time_key_count)
except (KeyError, ValueError, TypeError):
return MatrixErrorEvent.from_dict(server, "Error syncing", False,
parsed_dict)

View file

@ -1,642 +0,0 @@
# -*- 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
import json
from pprint import pformat
from collections import namedtuple, deque
from matrix.globals import W
from matrix.colors import Formatted
from matrix.utils import (
strip_matrix_server, color_for_tags, server_ts_to_weechat,
sender_to_nick_and_color, add_event_tags, sanitize_id, sanitize_ts,
sanitize_string, sanitize_text, shorten_sender, add_user_to_nicklist,
get_prefix_for_level, sanitize_power_level, string_strikethrough,
line_pointer_and_tags_from_event, sender_to_prefix_and_color)
PowerLevel = namedtuple('PowerLevel', ['user', 'level'])
class MatrixRoom:
def __init__(self, room_id, own_user_id):
# type: (str) -> None
# yapf: disable
self.room_id = room_id # type: str
self.own_user_id = own_user_id
self.canonical_alias = None # type: str
self.name = None # type: str
self.prev_batch = "" # type: str
self.users = dict() # type: Dict[str, MatrixUser]
self.encrypted = False # type: bool
self.backlog_pending = False # type: bool
self.power_levels = {}
# yapf: enable
def display_name(self, own_user_id):
"""
Calculate display name for a room.
Prefer returning the room name if it exists, falling back to
a group-style name if not.
Mostly follows:
https://matrix.org/docs/spec/client_server/r0.3.0.html#id268
An exception is that we prepend '#' before the room name to make it
visually distinct from private messages and unnamed groups of users
("direct chats") in weechat's buffer list.
"""
if self.is_named():
return self.named_room_name()
else:
return self.group_name(own_user_id)
def named_room_name(self):
"""
Returns the name of the room, if it's a named room. Otherwise return
None.
"""
if self.name:
return "#" + self.name
elif self.canonical_alias:
return self.canonical_alias
else:
return None
def group_name(self, own_user_id):
"""
Returns the group-style name of the room, i.e. a name based on the room
members.
"""
# Sort user display names, excluding our own user and using the
# mxid as the sorting key.
#
# TODO: Hook the user display name disambiguation algorithm here.
# Currently, we use the user display names as is, which may not be
# unique.
users = [user.name for mxid, user
in sorted(self.users.items(), key=lambda t: t[0])
if mxid != own_user_id]
num_users = len(users)
if num_users == 1:
return users[0]
elif num_users == 2:
return " and ".join(users)
elif num_users >= 3:
return "{first_user} and {num} others".format(
first_user=users[0],
num=num_users-1)
else:
return "Empty room?"
def machine_name(self):
"""
Calculate an unambiguous, unique machine name for a room.
Either use the more human-friendly canonical alias, if it exists, or
the internal room ID if not.
"""
if self.canonical_alias:
return self.canonical_alias
else:
return self.room_id
def is_named(self):
"""
Is this a named room?
A named room is a room with either the name or a canonical alias set.
"""
return self.canonical_alias or self.name
def is_group(self):
"""
Is this an ad hoc group of users?
A group is an unnamed room with no canonical alias.
"""
return not self.is_named()
def _handle_membership(self, event):
if event.content["membership"] == "join":
if event.sender in self.users:
user = self.users[event.sender]
if "display_name" in event.content:
user.display_name = event.content["display_name"]
else:
short_name = shorten_sender(event.sender)
# TODO the default power level doesn't have to be 0
level = (self.power_levels[event.sender] if event.sender in
self.power_levels else 0)
display_name = (event.content["display_name"]
if "display_name" in event.content else None)
user = MatrixUser(short_name, display_name, level)
self.users[event.sender] = user
return True
elif event.content["membership"] == "leave":
if event.state_key in self.users:
del self.users[event.state_key]
return True
elif event.content["membership"] == "invite":
pass
def handle_event(self, event):
if isinstance(event, RoomMembershipEvent):
return self._handle_membership(event)
elif isinstance(event, RoomNameEvent):
self.name = event.name
elif isinstance(event, RoomAliasEvent):
self.canonical_alias = event.canonical_alias
elif isinstance(event, RoomEncryptionEvent):
self.encrypted = True
return True
elif isinstance(event, RoomPowerLevels):
self.power_levels = event.power_levels
# Update the power levels of the joined users
for user_id, level in self.power_levels.items():
if user_id in self.users:
self.users[user_id].power_level = level
return False
class MatrixUser:
def __init__(self, name, display_name=None, power_level=0):
# yapf: disable
self.name = name # type: str
self.display_name = display_name # type: str
self.power_level = power_level # type: int
# yapf: enable
class RoomInfo():
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.state = deque(state)
self.timeline = deque(timeline)
@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 RoomRedactedMessageEvent.from_dict(event)
return RoomMessageEvent.from_dict(event)
@staticmethod
def parse_event(olm, room_id, event_dict):
# type: (Dict[Any, Any]) -> (RoomEvent, RoomEvent)
event = None
if "redacted_by" in event_dict["unsigned"]:
event = RoomRedactedMessageEvent.from_dict(event_dict)
elif event_dict["type"] == "m.room.message":
event = RoomInfo._message_from_event(event_dict)
elif event_dict["type"] == "m.room.member":
event = RoomMembershipEvent.from_dict(event_dict)
elif event_dict["type"] == "m.room.power_levels":
event = RoomPowerLevels.from_dict(event_dict)
elif event_dict["type"] == "m.room.topic":
event = RoomTopicEvent.from_dict(event_dict)
elif event_dict["type"] == "m.room.redaction":
event = RoomRedactionEvent.from_dict(event_dict)
elif event_dict["type"] == "m.room.name":
event = RoomNameEvent.from_dict(event_dict)
elif event_dict["type"] == "m.room.canonical_alias":
event = RoomAliasEvent.from_dict(event_dict)
elif event_dict["type"] == "m.room.encryption":
event = RoomEncryptionEvent.from_dict(event_dict)
elif event_dict["type"] == "m.room.encrypted":
event = RoomInfo._decrypt_event(olm, room_id, event_dict)
return event
@staticmethod
def _decrypt_event(olm, room_id, event_dict):
session_id = event_dict["content"]["session_id"]
ciphertext = event_dict["content"]["ciphertext"]
plaintext, message_index = olm.group_decrypt(
room_id,
session_id,
ciphertext
)
if not plaintext:
return UndecryptedEvent.from_dict(event_dict)
parsed_plaintext = json.loads(plaintext, encoding="utf-8")
event_dict["content"] = parsed_plaintext["content"]
event_dict["type"] = parsed_plaintext["type"]
return RoomInfo.parse_event(olm, room_id, event_dict)
@staticmethod
def _parse_events(olm, room_id, parsed_dict):
events = []
for event in parsed_dict:
try:
e = RoomInfo.parse_event(olm, room_id, event)
except (ValueError, TypeError, KeyError) as error:
message = ("{prefix}matrix: Error parsing "
"room event of type {type}: "
"{error}\n{event}").format(
prefix=W.prefix("error"),
type=event["type"],
error=pformat(error),
event=pformat(event))
W.prnt("", message)
e = BadEvent.from_dict(event)
events.append(e)
return events
@classmethod
def from_dict(cls, olm, room_id, parsed_dict):
prev_batch = sanitize_id(parsed_dict['timeline']['prev_batch'])
state_dict = parsed_dict['state']['events']
timeline_dict = parsed_dict['timeline']['events']
state_events = RoomInfo._parse_events(
olm,
room_id,
state_dict
)
timeline_events = RoomInfo._parse_events(
olm,
room_id,
timeline_dict
)
return cls(
room_id,
prev_batch,
list(filter(None, state_events)),
list(filter(None, timeline_events))
)
class RoomEvent(object):
def __init__(self, event_id, sender, timestamp):
self.event_id = event_id
self.sender = sender
self.timestamp = timestamp
class UndecryptedEvent(RoomEvent):
def __init__(self, event_id, sender, timestamp, session_id):
self.session_id = session_id
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event):
event_id = (sanitize_id(event["event_id"])
if "event_id" in event else None)
sender = (sanitize_id(event["sender"])
if "sender" in event else None)
timestamp = (sanitize_ts(event["origin_server_ts"])
if "origin_server_ts" in event else None)
session_id = event["content"]["session_id"]
return cls(event_id, sender, timestamp, session_id)
class BadEvent(RoomEvent):
def __init__(self, event_id, sender, timestamp, source):
RoomEvent.__init__(self, event_id, sender, timestamp)
self.source = source
@classmethod
def from_dict(cls, event):
event_id = (sanitize_id(event["event_id"])
if "event_id" in event else None)
sender = (sanitize_id(event["sender"])
if "sender" in event else None)
timestamp = (sanitize_ts(event["origin_server_ts"])
if "origin_server_ts" in event else None)
source = json.dumps(event)
return cls(event_id, sender, timestamp, source)
class RoomRedactedMessageEvent(RoomEvent):
def __init__(self, event_id, sender, timestamp, censor, reason=None):
self.censor = censor
self.reason = reason
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event):
event_id = sanitize_id(event["event_id"])
sender = sanitize_id(event["sender"])
timestamp = sanitize_ts(event["origin_server_ts"])
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, timestamp, censor, reason)
class RoomMessageEvent(RoomEvent):
@classmethod
def from_dict(cls, event):
if event['content']['msgtype'] == 'm.text':
return RoomMessageText.from_dict(event)
elif event['content']['msgtype'] == 'm.image':
return RoomMessageMedia.from_dict(event)
elif event['content']['msgtype'] == 'm.audio':
return RoomMessageMedia.from_dict(event)
elif event['content']['msgtype'] == 'm.file':
return RoomMessageMedia.from_dict(event)
elif event['content']['msgtype'] == 'm.video':
return RoomMessageMedia.from_dict(event)
elif event['content']['msgtype'] == 'm.emote':
return RoomMessageEmote.from_dict(event)
elif event['content']['msgtype'] == 'm.notice':
return RoomMessageNotice.from_dict(event)
return RoomMessageUnknown.from_dict(event)
def _print_message(self, message, room, buff, tags):
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)
prefix, prefix_color = sender_to_prefix_and_color(room, self.sender)
prefix_string = ("" if not prefix else "{}{}{}".format(
W.color(prefix_color), prefix, W.color("reset")))
data = "{prefix}{color}{author}{ncolor}\t{msg}".format(
prefix=prefix_string,
color=W.color(color_name),
author=nick,
ncolor=W.color("reset"),
msg=message)
date = server_ts_to_weechat(self.timestamp)
W.prnt_date_tags(buff, date, tags_string, data)
class RoomMessageSimple(RoomMessageEvent):
def __init__(self, event_id, sender, timestamp, message, message_type):
self.message = message
self.message_type = message_type
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event):
event_id = sanitize_id(event["event_id"])
sender = sanitize_id(event["sender"])
timestamp = sanitize_ts(event["origin_server_ts"])
message = sanitize_text(event["content"]["body"])
message_type = sanitize_text(event["content"]["msgtype"])
return cls(event_id, sender, timestamp, message, message_type)
class RoomMessageUnknown(RoomMessageSimple):
pass
class RoomMessageText(RoomMessageEvent):
def __init__(self, event_id, sender, timestamp, message, formatted_message=None):
self.message = message
self.formatted_message = formatted_message
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event):
event_id = sanitize_id(event["event_id"])
sender = sanitize_id(event["sender"])
timestamp = sanitize_ts(event["origin_server_ts"])
msg = ""
formatted_msg = None
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, timestamp, msg, formatted_msg)
class RoomMessageEmote(RoomMessageSimple):
pass
class RoomMessageNotice(RoomMessageText):
pass
class RoomMessageMedia(RoomMessageEvent):
def __init__(self, event_id, sender, timestamp, url, description):
self.url = url
self.description = description
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event):
event_id = sanitize_id(event["event_id"])
sender = sanitize_id(event["sender"])
timestamp = sanitize_ts(event["origin_server_ts"])
mxc_url = sanitize_text(event['content']['url'])
description = sanitize_text(event["content"]["body"])
return cls(event_id, sender, timestamp, mxc_url, description)
class RoomMembershipEvent(RoomEvent):
def __init__(
self,
event_id,
sender,
timestamp,
state_key,
content,
prev_content
):
self.state_key = state_key
self.content = content
self.prev_content = prev_content
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event_dict):
event_id = sanitize_id(event_dict["event_id"])
sender = sanitize_id(event_dict["sender"])
timestamp = sanitize_ts(event_dict["origin_server_ts"])
state_key = sanitize_id(event_dict["state_key"])
content = event_dict["content"]
prev_content = (event_dict["unsigned"]["prev_content"]
if "prev_content" in event_dict["unsigned"] else None)
return cls(
event_id,
sender,
timestamp,
state_key,
content,
prev_content
)
class RoomPowerLevels(RoomEvent):
def __init__(self, event_id, sender, timestamp, power_levels):
self.power_levels = power_levels
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event_dict):
event_id = sanitize_id(event_dict["event_id"])
sender = sanitize_id(event_dict["sender"])
timestamp = sanitize_ts(event_dict["origin_server_ts"])
power_levels = event_dict["content"].pop("users")
return cls(event_id, sender, timestamp, power_levels)
class RoomTopicEvent(RoomEvent):
def __init__(self, event_id, sender, timestamp, topic):
self.topic = topic
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event_dict):
event_id = sanitize_id(event_dict["event_id"])
sender = sanitize_id(event_dict["sender"])
timestamp = sanitize_ts(event_dict["origin_server_ts"])
topic = sanitize_text(event_dict["content"]["topic"])
return cls(event_id, sender, timestamp, topic)
class RoomRedactionEvent(RoomEvent):
def __init__(self, event_id, sender, timestamp, redaction_id, reason=None):
self.redaction_id = redaction_id
self.reason = reason
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event_dict):
event_id = sanitize_id(event_dict["event_id"])
sender = sanitize_id(event_dict["sender"])
timestamp = sanitize_ts(event_dict["origin_server_ts"])
redaction_id = sanitize_id(event_dict["redacts"])
reason = (sanitize_text(event_dict["content"]["reason"])
if "reason" in event_dict["content"] else None)
return cls(event_id, sender, timestamp, redaction_id, reason)
class RoomNameEvent(RoomEvent):
def __init__(self, event_id, sender, timestamp, name):
self.name = name
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event_dict):
event_id = sanitize_id(event_dict["event_id"])
sender = sanitize_id(event_dict["sender"])
timestamp = sanitize_ts(event_dict["origin_server_ts"])
name = sanitize_string(event_dict['content']['name'])
return cls(event_id, sender, timestamp, name)
class RoomAliasEvent(RoomEvent):
def __init__(self, event_id, sender, timestamp, canonical_alias):
self.canonical_alias = canonical_alias
RoomEvent.__init__(self, event_id, sender, timestamp)
@classmethod
def from_dict(cls, event_dict):
event_id = sanitize_id(event_dict["event_id"])
sender = sanitize_id(event_dict["sender"])
timestamp = sanitize_ts(event_dict["origin_server_ts"])
canonical_alias = sanitize_id(event_dict["content"]["alias"])
return cls(event_id, sender, timestamp, canonical_alias)
class RoomEncryptionEvent(RoomEvent):
@classmethod
def from_dict(cls, event_dict):
event_id = sanitize_id(event_dict["event_id"])
sender = sanitize_id(event_dict["sender"])
timestamp = sanitize_ts(event_dict["origin_server_ts"])
return cls(event_id, sender, timestamp)

View file

@ -28,7 +28,7 @@ import json
from collections import deque, defaultdict from collections import deque, defaultdict
from http_parser.pyparser import HttpParser from http_parser.pyparser import HttpParser
from nio import Client, LoginResponse from nio import Client, LoginResponse, SyncRepsponse
from matrix.plugin_options import Option, DebugType from matrix.plugin_options import Option, DebugType
from matrix.utils import (key_from_value, prnt_debug, server_buffer_prnt, from matrix.utils import (key_from_value, prnt_debug, server_buffer_prnt,
@ -37,12 +37,7 @@ from matrix.utils import (key_from_value, prnt_debug, server_buffer_prnt,
from matrix.utf import utf8_decode from matrix.utf import utf8_decode
from matrix.globals import W, SERVERS, OPTIONS from matrix.globals import W, SERVERS, OPTIONS
import matrix.api as API import matrix.api as API
from .buffer import RoomBuffer from .buffer import RoomBuffer, OwnMessage, OwnAction
from .rooms import (
MatrixRoom,
RoomMessageText,
RoomMessageEmote,
)
from matrix.api import ( from matrix.api import (
MatrixClient, MatrixClient,
MatrixSyncMessage, MatrixSyncMessage,
@ -56,8 +51,6 @@ from matrix.api import (
) )
from .events import ( from .events import (
MatrixLoginEvent,
MatrixSyncEvent,
MatrixSendEvent, MatrixSendEvent,
MatrixBacklogEvent, MatrixBacklogEvent,
MatrixErrorEvent, MatrixErrorEvent,
@ -97,7 +90,6 @@ class MatrixServer:
self.user = "" # type: str self.user = "" # type: str
self.password = "" # type: str self.password = "" # type: str
self.rooms = dict() # type: Dict[str, MatrixRoom]
self.room_buffers = dict() # type: Dict[str, WeechatChannelBuffer] self.room_buffers = dict() # type: Dict[str, WeechatChannelBuffer]
self.buffers = dict() # type: Dict[str, weechat.buffer] self.buffers = dict() # type: Dict[str, weechat.buffer]
self.server_buffer = None # type: weechat.buffer self.server_buffer = None # type: weechat.buffer
@ -610,10 +602,10 @@ class MatrixServer:
def query_keys(self): def query_keys(self):
users = [] users = []
for room in self.rooms.values(): for room_buffer in self.room_buffers.values():
if not room.encrypted: if not room_buffer.room.encrypted:
continue continue
users += list(room.users) users += list(room_buffer.room.users)
if not users: if not users:
return return
@ -642,68 +634,13 @@ class MatrixServer:
server_buffer_prnt(self, pprint.pformat(message.request.payload)) server_buffer_prnt(self, pprint.pformat(message.request.payload))
server_buffer_prnt(self, pprint.pformat(message.response.body)) server_buffer_prnt(self, pprint.pformat(message.response.body))
def _loop_events(self, info, n):
for i in range(n+1):
is_state = False
try:
event = info.state.popleft()
is_state = True
except IndexError:
try:
event = info.timeline.popleft()
except IndexError:
return i
room, room_buffer = self.find_room_from_id(info.room_id)
# The room changed it's members, if the room is encrypted update
# the device list
if room.handle_event(event) and room.encrypted:
self.device_check_timestamp = None
if is_state:
room_buffer.handle_state_event(event)
else:
room_buffer.handle_timeline_event(event)
self.event_queue.appendleft(info)
return i
def handle_events(self):
n = 25
while True:
try:
info = self.event_queue.popleft()
except IndexError:
if self.event_queue_timer:
W.unhook(self.event_queue_timer)
self.event_queue_timer = None
self.sync()
return
ret = self._loop_events(info, n)
if ret < n:
n = n - ret
else:
self.event_queue.appendleft(info)
if not self.event_queue_timer:
hook = W.hook_timer(1 * 100, 0, 0, "matrix_event_timer_cb",
self.name)
self.event_queue_timer = hook
return
def handle_own_messages(self, room_buffer, message): def handle_own_messages(self, room_buffer, message):
if isinstance(message, RoomMessageText): if isinstance(message, OwnAction):
room_buffer.self_message(message)
return
elif isinstance(message, RoomMessageEmote):
room_buffer.self_action(message) room_buffer.self_action(message)
return return
elif isinstance(message, OwnMessage):
room_buffer.self_message(message)
return
raise NotImplementedError("Unsupported message of type {}".format( raise NotImplementedError("Unsupported message of type {}".format(
type(message))) type(message)))
@ -736,19 +673,18 @@ class MatrixServer:
self.sync() self.sync()
def _queue_joined_info(self, response): def _handle_room_info(self, response):
while response.joined_room_infos: for room_id, join_info in response.rooms.join.items():
info = response.joined_room_infos.pop() if room_id not in self.buffers:
self.create_room_buffer(room_id)
if info.room_id not in self.buffers: room_buffer = self.find_room_from_id(room_id)
self.create_room_buffer(info.room_id)
room = self.rooms[info.room_id] for event in join_info.state:
room_buffer.handle_state_event(event)
if not room.prev_batch: for event in join_info.timeline.events:
room.prev_batch = info.prev_batch room_buffer.handle_timeline_event(event)
self.event_queue.append(info)
def _handle_sync(self, response): def _handle_sync(self, response):
# we got the same batch again, nothing to do # we got the same batch again, nothing to do
@ -756,27 +692,20 @@ class MatrixServer:
self.sync() self.sync()
return return
self._queue_joined_info(response) self._handle_room_info(response)
# self._queue_joined_info(response)
self.next_batch = response.next_batch self.next_batch = response.next_batch
# self.check_one_time_keys(response.one_time_key_count) # self.check_one_time_keys(response.one_time_key_count)
self.handle_events() # self.handle_events()
def handle_matrix_response(self, response): def handle_matrix_response(self, response):
if isinstance(response, MatrixLoginEvent): if isinstance(response, MatrixSendEvent):
self._handle_login(response) room_buffer = self.find_room_from_id(response.room_id)
elif isinstance(response, MatrixSyncEvent):
self._handle_sync(response)
elif isinstance(response, MatrixSendEvent):
_, room_buffer = self.find_room_from_id(response.room_id)
self.handle_own_messages(room_buffer, response.message) self.handle_own_messages(room_buffer, response.message)
elif isinstance(response, MatrixBacklogEvent): elif isinstance(response, MatrixBacklogEvent):
room, room_buffer = self.find_room_from_id(response.room_id) room_buffer = self.find_room_from_id(response.room_id)
room_buffer.handle_backlog(response.events) room_buffer.handle_backlog(response.events)
room.prev_batch = response.end_token
room.backlog_pending = False
W.bar_item_update("buffer_modes") W.bar_item_update("buffer_modes")
elif isinstance(response, MatrixErrorEvent): elif isinstance(response, MatrixErrorEvent):
@ -787,10 +716,14 @@ class MatrixServer:
if isinstance(response, LoginResponse): if isinstance(response, LoginResponse):
self._handle_login(response) self._handle_login(response)
elif isinstance(response, SyncRepsponse):
self._handle_sync(response)
def nio_parse_response(self, response): def nio_parse_response(self, response):
if isinstance(response, MatrixLoginMessage): if isinstance(response, MatrixLoginMessage):
self.nio_client.receive("login", response.response.body) self.nio_client.receive("login", response.response.body)
elif isinstance(response, MatrixSyncMessage):
self.nio_client.receive("sync", response.response.body)
self.nio_receive() self.nio_receive()
@ -804,7 +737,7 @@ class MatrixServer:
if ('content-type' in message.response.headers and if ('content-type' in message.response.headers and
message.response.headers['content-type'] == 'application/json'): message.response.headers['content-type'] == 'application/json'):
if isinstance(message, MatrixLoginMessage): if isinstance(message, (MatrixLoginMessage, MatrixSyncMessage)):
self.nio_parse_response(message) self.nio_parse_response(message)
else: else:
@ -850,24 +783,21 @@ class MatrixServer:
return return
def create_room_buffer(self, room_id): def create_room_buffer(self, room_id):
room = MatrixRoom(room_id, self.user_id) room = self.nio_client.rooms[room_id]
buf = RoomBuffer(room, self.name) buf = RoomBuffer(room, self.name)
# TODO this should turned into a propper class # TODO this should turned into a propper class
self.room_buffers[room_id] = buf self.room_buffers[room_id] = buf
self.buffers[room_id] = buf.weechat_buffer._ptr self.buffers[room_id] = buf.weechat_buffer._ptr
self.rooms[room_id] = room
def find_room_from_ptr(self, pointer): def find_room_from_ptr(self, pointer):
room_id = key_from_value(self.buffers, pointer) room_id = key_from_value(self.buffers, pointer)
room = self.rooms[room_id]
room_buffer = self.room_buffers[room_id] room_buffer = self.room_buffers[room_id]
return room, room_buffer return room_buffer
def find_room_from_id(self, room_id): def find_room_from_id(self, room_id):
room = self.rooms[room_id]
room_buffer = self.room_buffers[room_id] room_buffer = self.room_buffers[room_id]
return room, room_buffer return room_buffer
@utf8_decode @utf8_decode