server: Continue nio migration.
This commit is contained in:
parent
91eec1ad85
commit
45be743c07
7 changed files with 199 additions and 1069 deletions
34
main.py
34
main.py
|
@ -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("",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
223
matrix/buffer.py
223
matrix/buffer.py
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -144,15 +144,16 @@ def matrix_me_command_cb(data, buffer, args):
|
||||||
elif buffer == server.server_buffer:
|
elif buffer == server.server_buffer:
|
||||||
message = ("{prefix}matrix: command \"me\" must be "
|
message = ("{prefix}matrix: command \"me\" must be "
|
||||||
"executed on a Matrix channel buffer"
|
"executed on a Matrix channel buffer"
|
||||||
).format(prefix=W.prefix("error"))
|
).format(prefix=W.prefix("error"))
|
||||||
W.prnt("", message)
|
W.prnt("", message)
|
||||||
return W.WEECHAT_RC_OK
|
return W.WEECHAT_RC_OK
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
156
matrix/events.py
156
matrix/events.py
|
@ -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)
|
|
||||||
|
|
642
matrix/rooms.py
642
matrix/rooms.py
|
@ -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)
|
|
134
matrix/server.py
134
matrix/server.py
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue