matrix: Switch to the nio http client.
This commit is contained in:
parent
45be743c07
commit
fc4c879e0d
6 changed files with 55 additions and 2482 deletions
46
main.py
46
main.py
|
@ -32,6 +32,8 @@ from future.utils import bytes_to_native_str as n
|
|||
# pylint: disable=unused-import
|
||||
from typing import (List, Set, Dict, Tuple, Text, Optional, AnyStr, Deque, Any)
|
||||
|
||||
from nio import TransportType
|
||||
|
||||
from matrix.colors import Formatted
|
||||
from matrix.utf import utf8_decode
|
||||
from matrix.http import HttpResponse
|
||||
|
@ -288,49 +290,31 @@ def receive_cb(server_name, file_descriptor):
|
|||
server.disconnect()
|
||||
break
|
||||
|
||||
received = len(data) # type: int
|
||||
parsed_bytes = server.http_parser.execute(data, received)
|
||||
server.client.receive(data)
|
||||
|
||||
assert parsed_bytes == received
|
||||
response = server.client.next_response()
|
||||
|
||||
if server.http_parser.is_partial_body():
|
||||
server.http_buffer.append(server.http_parser.recv_body())
|
||||
|
||||
if server.http_parser.is_message_complete():
|
||||
status = server.http_parser.get_status_code()
|
||||
headers = server.http_parser.get_headers()
|
||||
body = b"".join(server.http_buffer)
|
||||
|
||||
message = server.receive_queue.popleft()
|
||||
message.response = HttpResponse(status, headers, body)
|
||||
receive_time = time.time()
|
||||
server.lag = (receive_time - message.send_time) * 1000
|
||||
server.lag_done = True
|
||||
W.bar_item_update("lag")
|
||||
message.receive_time = receive_time
|
||||
|
||||
prnt_debug(DebugType.MESSAGING, server,
|
||||
("{prefix}Received message of type {t} and "
|
||||
"status {s}").format(
|
||||
prefix=W.prefix("error"),
|
||||
t=message.__class__.__name__,
|
||||
s=status))
|
||||
|
||||
# Message done, reset the parser state.
|
||||
server.reset_parser()
|
||||
|
||||
server.handle_response(message)
|
||||
if response:
|
||||
server.handle_response(response)
|
||||
break
|
||||
|
||||
return W.WEECHAT_RC_OK
|
||||
|
||||
|
||||
def finalize_connection(server):
|
||||
hook = W.hook_fd(server.socket.fileno(), 1, 0, 0, "receive_cb", server.name)
|
||||
hook = W.hook_fd(
|
||||
server.socket.fileno(),
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
"receive_cb",
|
||||
server.name
|
||||
)
|
||||
|
||||
server.fd_hook = hook
|
||||
server.connected = True
|
||||
server.connecting = False
|
||||
server.client.connect(TransportType.HTTP)
|
||||
|
||||
server.login()
|
||||
|
||||
|
|
720
matrix/api.py
720
matrix/api.py
|
@ -1,720 +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
|
||||
from builtins import str
|
||||
|
||||
import time
|
||||
import json
|
||||
from enum import Enum, unique
|
||||
from functools import partial
|
||||
|
||||
try:
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError:
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
try:
|
||||
from urllib import quote, urlencode
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import quote, urlencode, urlparse
|
||||
|
||||
from matrix.http import RequestType, HttpRequest
|
||||
import matrix.events as MatrixEvents
|
||||
|
||||
MATRIX_API_PATH = "/_matrix/client/r0" # type: str
|
||||
|
||||
|
||||
class MatrixClient:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host, # type: str
|
||||
access_token="", # type: str
|
||||
user_agent="" # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.host = host
|
||||
self.user_agent = user_agent
|
||||
self.access_token = access_token
|
||||
self.txn_id = 0 # type: int
|
||||
|
||||
def _get_txn_id(self):
|
||||
txn_id = self.txn_id
|
||||
self.txn_id = self.txn_id + 1
|
||||
return txn_id
|
||||
|
||||
def login(self, user, password, device_name="", device_id=""):
|
||||
# type (str, str, str) -> HttpRequest
|
||||
path = ("{api}/login").format(api=MATRIX_API_PATH)
|
||||
|
||||
post_data = {
|
||||
"type": "m.login.password",
|
||||
"user": user,
|
||||
"password": password
|
||||
}
|
||||
|
||||
if device_id:
|
||||
post_data["device_id"] = device_id
|
||||
|
||||
if device_name:
|
||||
post_data["initial_device_display_name"] = device_name
|
||||
|
||||
return HttpRequest(RequestType.POST, self.host, path, post_data)
|
||||
|
||||
def sync(self, next_batch="", sync_filter=None):
|
||||
# type: (str, Dict[Any, Any]) -> HttpRequest
|
||||
assert self.access_token
|
||||
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
if sync_filter:
|
||||
query_parameters["filter"] = json.dumps(
|
||||
sync_filter, separators=(",", ":"))
|
||||
|
||||
if next_batch:
|
||||
query_parameters["since"] = next_batch
|
||||
|
||||
path = ("{api}/sync?{query_params}").format(
|
||||
api=MATRIX_API_PATH, query_params=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.GET, self.host, path)
|
||||
|
||||
def room_encrypted_message(self, room_id, content):
|
||||
# type: (str, Dict[Any, Any]) -> HttpRequest
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
path = ("{api}/rooms/{room}/send/m.room.encrypted/"
|
||||
"{tx_id}?{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room=quote(room_id),
|
||||
tx_id=quote(str(self._get_txn_id())),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.PUT, self.host, path, content)
|
||||
|
||||
def room_send_message(self,
|
||||
room_id,
|
||||
message_type,
|
||||
content,
|
||||
formatted_content=None):
|
||||
# type: (str, str, str) -> HttpRequest
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
body = {"msgtype": message_type, "body": content}
|
||||
|
||||
if formatted_content:
|
||||
body["format"] = "org.matrix.custom.html"
|
||||
body["formatted_body"] = formatted_content
|
||||
|
||||
path = ("{api}/rooms/{room}/send/m.room.message/"
|
||||
"{tx_id}?{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room=quote(room_id),
|
||||
tx_id=quote(str(self._get_txn_id())),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.PUT, self.host, path, body)
|
||||
|
||||
def room_topic(self, room_id, topic):
|
||||
# type: (str, str) -> HttpRequest
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
content = {"topic": topic}
|
||||
|
||||
path = ("{api}/rooms/{room}/state/m.room.topic?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room=quote(room_id),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.PUT, self.host, path, content)
|
||||
|
||||
def room_redact(self, room_id, event_id, reason=None):
|
||||
# type: (str, str, str) -> HttpRequest
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
content = {}
|
||||
|
||||
if reason:
|
||||
content["reason"] = reason
|
||||
|
||||
path = ("{api}/rooms/{room}/redact/{event_id}/{tx_id}?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room=quote(room_id),
|
||||
event_id=quote(event_id),
|
||||
tx_id=quote(str(self._get_txn_id())),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.PUT, self.host, path, content)
|
||||
|
||||
def room_get_messages(self,
|
||||
room_id,
|
||||
start_token,
|
||||
end_token="",
|
||||
limit=10,
|
||||
direction='b'):
|
||||
query_parameters = {
|
||||
"access_token": self.access_token,
|
||||
"from": start_token,
|
||||
"dir": direction,
|
||||
"limit": str(limit)
|
||||
}
|
||||
|
||||
if end_token:
|
||||
query_parameters["to"] = end_token
|
||||
|
||||
path = ("{api}/rooms/{room}/messages?{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room=quote(room_id),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.GET, self.host, path)
|
||||
|
||||
def room_join(self, room_id):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
path = ("{api}/join/{room_id}?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room_id=quote(room_id),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.POST, self.host, path)
|
||||
|
||||
def room_leave(self, room_id):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
path = ("{api}/rooms/{room_id}/leave?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room_id=quote(room_id),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.POST, self.host, path)
|
||||
|
||||
def room_invite(self, room_id, user_id):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
content = {"user_id": user_id}
|
||||
|
||||
path = ("{api}/rooms/{room_id}/invite?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room_id=quote(room_id),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.POST, self.host, path, content)
|
||||
|
||||
def room_kick(self, room_id, user_id, reason=None):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
content = {"user_id": user_id}
|
||||
|
||||
if reason:
|
||||
content["reason"] = reason
|
||||
|
||||
path = ("{api}/rooms/{room_id}/kick?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
room_id=quote(room_id),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
h = HttpRequest(RequestType.POST, self.host, path, content)
|
||||
return h
|
||||
|
||||
def keys_upload(self, user_id, device_id, olm, keys=None,
|
||||
one_time_keys=None):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
path = ("{api}/keys/upload?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
content = {}
|
||||
|
||||
if keys:
|
||||
device_keys = {
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"device_id": device_id,
|
||||
"user_id": user_id,
|
||||
"keys": {
|
||||
"curve25519:" + device_id: keys["curve25519"],
|
||||
"ed25519:" + device_id: keys["ed25519"]
|
||||
}
|
||||
}
|
||||
|
||||
signature = olm.sign_json(device_keys)
|
||||
|
||||
device_keys["signatures"] = {
|
||||
user_id: {
|
||||
"ed25519:" + device_id: signature
|
||||
}
|
||||
}
|
||||
|
||||
content["device_keys"] = device_keys
|
||||
|
||||
if one_time_keys:
|
||||
one_time_key_dict = {}
|
||||
|
||||
for key_id, key in one_time_keys.items():
|
||||
key_dict = {"key": key}
|
||||
signature = olm.sign_json(key_dict)
|
||||
|
||||
one_time_key_dict["signed_curve25519:" + key_id] = {
|
||||
"key": key_dict.pop("key"),
|
||||
"signatures": {
|
||||
user_id: {
|
||||
"ed25519:" + device_id: signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content["one_time_keys"] = one_time_key_dict
|
||||
|
||||
return HttpRequest(RequestType.POST, self.host, path, content)
|
||||
|
||||
def keys_query(self, users):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
path = ("{api}/keys/query?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
content = {
|
||||
"device_keys": {user: {} for user in users}
|
||||
}
|
||||
|
||||
return HttpRequest(RequestType.POST, self.host, path, content)
|
||||
|
||||
def keys_claim(self, key_dict):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
path = ("{api}/keys/claim?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
content = {
|
||||
"one_time_keys": key_dict,
|
||||
"timeout": 10000
|
||||
}
|
||||
|
||||
return HttpRequest(RequestType.POST, self.host, path, content)
|
||||
|
||||
def to_device(self, event_type, content):
|
||||
query_parameters = {"access_token": self.access_token}
|
||||
|
||||
path = ("{api}/sendToDevice/{event_type}/{tx_id}?"
|
||||
"{query_parameters}").format(
|
||||
api=MATRIX_API_PATH,
|
||||
event_type=event_type,
|
||||
tx_id=quote(str(self._get_txn_id())),
|
||||
query_parameters=urlencode(query_parameters))
|
||||
|
||||
return HttpRequest(RequestType.PUT, self.host, path, content)
|
||||
|
||||
@staticmethod
|
||||
def mxc_to_http(mxc):
|
||||
# type: (str) -> str
|
||||
url = urlparse(mxc)
|
||||
|
||||
if url.scheme != "mxc":
|
||||
return None
|
||||
|
||||
if not url.netloc or not url.path:
|
||||
return None
|
||||
|
||||
return "https://{}/_matrix/media/r0/download/{}{}".format(
|
||||
url.netloc,
|
||||
url.netloc,
|
||||
url.path
|
||||
)
|
||||
|
||||
|
||||
class MatrixMessage():
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request_func, # type: Callable[[...], HttpRequest]
|
||||
func_args,
|
||||
):
|
||||
# type: (...) -> None
|
||||
# yapf: disable
|
||||
|
||||
self.request = None # type: HttpRequest
|
||||
self.response = None # type: HttpResponse
|
||||
self.decoded_response = None # type: Dict[Any, Any]
|
||||
|
||||
self.creation_time = time.time() # type: float
|
||||
self.send_time = None # type: float
|
||||
self.receive_time = None # type: float
|
||||
self.event = None
|
||||
|
||||
self.request = request_func(**func_args)
|
||||
# yapf: enable
|
||||
|
||||
def decode_body(self, server):
|
||||
try:
|
||||
self.decoded_response = json.loads(
|
||||
self.response.body, encoding='utf-8')
|
||||
return (True, None)
|
||||
except Exception as error:
|
||||
return (False, error)
|
||||
|
||||
def _decode(self, server, object_hook):
|
||||
try:
|
||||
parsed_dict = json.loads(
|
||||
self.response.body,
|
||||
encoding='utf-8',
|
||||
)
|
||||
|
||||
self.event = object_hook(parsed_dict)
|
||||
|
||||
return (True, None)
|
||||
|
||||
except JSONDecodeError as error:
|
||||
return (False, error)
|
||||
|
||||
|
||||
class MatrixLoginMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, user, password, device_name, device_id=None):
|
||||
data = {"user": user, "password": password, "device_name": device_name}
|
||||
|
||||
if device_id:
|
||||
data["device_id"] = device_id
|
||||
|
||||
MatrixMessage.__init__(self, client.login, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixLoginEvent.from_dict, server)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixSyncMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, next_batch=None, limit=None):
|
||||
data = {}
|
||||
|
||||
if next_batch:
|
||||
data["next_batch"] = next_batch
|
||||
|
||||
if limit:
|
||||
data["sync_filter"] = {"room": {"timeline": {"limit": limit}}}
|
||||
|
||||
MatrixMessage.__init__(self, client.sync, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixSyncEvent.from_dict, server)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixSendMessage(MatrixMessage):
|
||||
|
||||
def __init__(self,
|
||||
client,
|
||||
room_id,
|
||||
formatted_message,
|
||||
message_type="m.text"):
|
||||
self.room_id = room_id
|
||||
self.formatted_message = formatted_message
|
||||
|
||||
data = {
|
||||
"room_id": self.room_id,
|
||||
"message_type": message_type,
|
||||
"content": self.formatted_message.to_plain()
|
||||
}
|
||||
|
||||
if self.formatted_message.is_formatted():
|
||||
data["formatted_content"] = self.formatted_message.to_html()
|
||||
|
||||
MatrixMessage.__init__(self, client.room_send_message, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(
|
||||
MatrixEvents.MatrixSendEvent.from_dict,
|
||||
server,
|
||||
self.room_id,
|
||||
self.formatted_message,
|
||||
)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixEmoteMessage(MatrixSendMessage):
|
||||
|
||||
def __init__(self, client, room_id, formatted_message):
|
||||
MatrixSendMessage.__init__(self, client, room_id, formatted_message,
|
||||
"m.emote")
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(
|
||||
MatrixEvents.MatrixEmoteEvent.from_dict,
|
||||
server,
|
||||
self.room_id,
|
||||
self.formatted_message,
|
||||
)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixTopicMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id, topic):
|
||||
self.room_id = room_id
|
||||
self.topic = topic
|
||||
|
||||
data = {"room_id": self.room_id, "topic": self.topic}
|
||||
|
||||
MatrixMessage.__init__(self, client.room_topic, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(
|
||||
MatrixEvents.MatrixTopicEvent.from_dict,
|
||||
server,
|
||||
self.room_id,
|
||||
self.topic,
|
||||
)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixRedactMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id, event_id, reason=None):
|
||||
self.room_id = room_id
|
||||
self.event_id = event_id
|
||||
self.reason = reason
|
||||
|
||||
data = {"room_id": self.room_id, "event_id": self.event_id}
|
||||
|
||||
if reason:
|
||||
data["reason"] = reason
|
||||
|
||||
MatrixMessage.__init__(self, client.room_redact, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(
|
||||
MatrixEvents.MatrixRedactEvent.from_dict,
|
||||
server,
|
||||
self.room_id,
|
||||
self.reason,
|
||||
)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixBacklogMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id, token, limit):
|
||||
self.room_id = room_id
|
||||
|
||||
data = {
|
||||
"room_id": self.room_id,
|
||||
"start_token": token,
|
||||
"direction": "b",
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
MatrixMessage.__init__(self, client.room_get_messages, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixBacklogEvent.from_dict, server,
|
||||
self.room_id)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixJoinMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id):
|
||||
self.room_id = room_id
|
||||
|
||||
data = {"room_id": self.room_id}
|
||||
|
||||
MatrixMessage.__init__(self, client.room_join, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixJoinEvent.from_dict, server,
|
||||
self.room_id)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixPartMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id):
|
||||
self.room_id = room_id
|
||||
|
||||
data = {"room_id": self.room_id}
|
||||
|
||||
MatrixMessage.__init__(self, client.room_leave, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixPartEvent.from_dict, server,
|
||||
self.room_id)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixInviteMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id, user_id):
|
||||
self.room_id = room_id
|
||||
self.user_id = user_id
|
||||
|
||||
data = {"room_id": self.room_id, "user_id": self.user_id}
|
||||
|
||||
MatrixMessage.__init__(self, client.room_invite, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixInviteEvent.from_dict, server,
|
||||
self.room_id, self.user_id)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixKickMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id, user_id, reason=None):
|
||||
self.room_id = room_id
|
||||
self.user_id = user_id
|
||||
self.reason = reason
|
||||
|
||||
data = {"room_id": self.room_id,
|
||||
"user_id": self.user_id,
|
||||
"reason": reason}
|
||||
|
||||
MatrixMessage.__init__(self, client.room_kick, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(
|
||||
MatrixEvents.MatrixKickEvent.from_dict,
|
||||
server,
|
||||
self.room_id,
|
||||
self.user_id,
|
||||
self.reason)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixKeyUploadMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, user_id, device_id, olm, keys=None,
|
||||
one_time_keys=None):
|
||||
data = {
|
||||
"device_id": device_id,
|
||||
"user_id": user_id,
|
||||
"olm": olm,
|
||||
"keys": keys,
|
||||
"one_time_keys": one_time_keys
|
||||
}
|
||||
|
||||
self.device_keys = True if keys else False
|
||||
|
||||
MatrixMessage.__init__(self, client.keys_upload, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixKeyUploadEvent.from_dict,
|
||||
server, self.device_keys)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixKeyQueryMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, users):
|
||||
data = {
|
||||
"users": users,
|
||||
}
|
||||
|
||||
MatrixMessage.__init__(self, client.keys_query, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixKeyQueryEvent.from_dict,
|
||||
server)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixKeyClaimMessage(MatrixMessage):
|
||||
|
||||
def __init__(self, client, room_id, key_dict):
|
||||
self.room_id = room_id
|
||||
data = {
|
||||
"key_dict": key_dict,
|
||||
}
|
||||
|
||||
MatrixMessage.__init__(self, client.keys_claim, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixKeyClaimEvent.from_dict,
|
||||
server, self.room_id)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixToDeviceMessage(MatrixMessage):
|
||||
def __init__(self, client, to_device_dict):
|
||||
data = {
|
||||
"content": to_device_dict,
|
||||
"event_type": "m.room.encrypted"
|
||||
}
|
||||
|
||||
MatrixMessage.__init__(self, client.to_device, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(MatrixEvents.MatrixToDeviceEvent.from_dict,
|
||||
server)
|
||||
|
||||
return self._decode(server, object_hook)
|
||||
|
||||
|
||||
class MatrixEncryptedMessage(MatrixMessage):
|
||||
|
||||
def __init__(self,
|
||||
client,
|
||||
room_id,
|
||||
formatted_message,
|
||||
content):
|
||||
self.room_id = room_id
|
||||
self.formatted_message = formatted_message
|
||||
|
||||
data = {
|
||||
"room_id": self.room_id,
|
||||
"content": content
|
||||
}
|
||||
|
||||
MatrixMessage.__init__(self, client.room_encrypted_message, data)
|
||||
|
||||
def decode_body(self, server):
|
||||
object_hook = partial(
|
||||
MatrixEvents.MatrixSendEvent.from_dict,
|
||||
server,
|
||||
self.room_id,
|
||||
self.formatted_message,
|
||||
)
|
||||
|
||||
return self._decode(server, object_hook)
|
|
@ -1,913 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Weechat Matrix Protocol Script
|
||||
# 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 os
|
||||
import json
|
||||
import sqlite3
|
||||
import pprint
|
||||
import argparse
|
||||
|
||||
# pylint: disable=redefined-builtin
|
||||
from builtins import str, bytes
|
||||
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from functools import wraps
|
||||
from future.moves.itertools import zip_longest
|
||||
|
||||
try:
|
||||
FileNotFoundError
|
||||
except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
import matrix.globals
|
||||
|
||||
try:
|
||||
from olm import (
|
||||
Account,
|
||||
OlmAccountError,
|
||||
Session,
|
||||
InboundSession,
|
||||
OutboundSession,
|
||||
OlmSessionError,
|
||||
OlmPreKeyMessage,
|
||||
InboundGroupSession,
|
||||
OutboundGroupSession,
|
||||
OlmGroupSessionError
|
||||
)
|
||||
except ImportError:
|
||||
matrix.globals.ENCRYPTION = False
|
||||
|
||||
from matrix.globals import W, SERVERS
|
||||
from matrix.utils import sanitize_id
|
||||
from matrix.utf import utf8_decode
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OlmTrustError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WeechatArgParse(argparse.ArgumentParser):
|
||||
def print_usage(self, file):
|
||||
pass
|
||||
|
||||
def error(self, message):
|
||||
m = ("{prefix}Error: {message} for command {command} "
|
||||
"(see /help {command})").format(prefix=W.prefix("error"),
|
||||
message=message,
|
||||
command=self.prog)
|
||||
W.prnt("", m)
|
||||
raise ParseError
|
||||
|
||||
|
||||
def own_buffer_or_error(f):
|
||||
|
||||
@wraps(f)
|
||||
def wrapper(data, buffer, *args, **kwargs):
|
||||
|
||||
for server in SERVERS.values():
|
||||
if buffer in server.buffers.values():
|
||||
return f(server.name, buffer, *args, **kwargs)
|
||||
elif buffer == server.server_buffer:
|
||||
return f(server.name, buffer, *args, **kwargs)
|
||||
|
||||
W.prnt("", "{prefix}matrix: command \"olm\" must be executed on a "
|
||||
"matrix buffer (server or channel)".format(
|
||||
prefix=W.prefix("error")))
|
||||
|
||||
return W.WEECHAT_RC_OK
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def encrypt_enabled(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwds):
|
||||
if matrix.globals.ENCRYPTION:
|
||||
return f(*args, **kwds)
|
||||
return None
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@encrypt_enabled
|
||||
def matrix_hook_olm_command():
|
||||
W.hook_command(
|
||||
# Command name and short description
|
||||
"olm",
|
||||
"Matrix olm encryption command",
|
||||
# Synopsis
|
||||
("info all|blacklisted|private|unverified|verified <filter>||"
|
||||
"blacklist <user-id> <device-id> ||"
|
||||
"unverify <user-id> <device-id> ||"
|
||||
"verify <user-id> <device-id>"),
|
||||
# Description
|
||||
(" info: show info about known devices and their keys\n"
|
||||
"blacklist: blacklist a device\n"
|
||||
" unverify: unverify a device\n"
|
||||
" verify: verify a device\n\n"
|
||||
"Examples:\n"),
|
||||
# Completions
|
||||
('info all|blacklisted|private|unverified|verified ||'
|
||||
'blacklist %(device_ids) ||'
|
||||
'unverify %(user_ids) %(device_ids) ||'
|
||||
'verify %(olm_user_ids) %(olm_devices)'),
|
||||
# Function name
|
||||
'matrix_olm_command_cb',
|
||||
'')
|
||||
|
||||
|
||||
def olm_cmd_parse_args(args):
|
||||
parser = WeechatArgParse(prog="olm")
|
||||
subparsers = parser.add_subparsers(dest="subcommand")
|
||||
|
||||
info_parser = subparsers.add_parser("info")
|
||||
info_parser.add_argument(
|
||||
"category", nargs="?", default="private",
|
||||
choices=[
|
||||
"all",
|
||||
"blacklisted",
|
||||
"private",
|
||||
"unverified",
|
||||
"verified"
|
||||
])
|
||||
info_parser.add_argument("filter", nargs="?")
|
||||
|
||||
verify_parser = subparsers.add_parser("verify")
|
||||
verify_parser.add_argument("user_filter")
|
||||
verify_parser.add_argument("device_filter", nargs="?")
|
||||
|
||||
try:
|
||||
parsed_args = parser.parse_args(args.split())
|
||||
return parsed_args
|
||||
except ParseError:
|
||||
return None
|
||||
|
||||
|
||||
def grouper(iterable, n, fillvalue=None):
|
||||
"Collect data into fixed-length chunks or blocks"
|
||||
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
|
||||
args = [iter(iterable)] * n
|
||||
return zip_longest(*args, fillvalue=fillvalue)
|
||||
|
||||
|
||||
def partition_key(key):
|
||||
groups = grouper(key, 4, " ")
|
||||
return ' '.join(''.join(g) for g in groups)
|
||||
|
||||
|
||||
def olm_info_command(server, args):
|
||||
olm = server.olm
|
||||
|
||||
if args.category == "private":
|
||||
device_msg = (" - Device ID: {}\n".format(server.device_id)
|
||||
if server.device_id else "")
|
||||
id_key = partition_key(olm.account.identity_keys["curve25519"])
|
||||
fp_key = partition_key(olm.account.identity_keys["ed25519"])
|
||||
message = ("{prefix}matrix: Identity keys:\n"
|
||||
" - User: {user}\n"
|
||||
"{device_msg}"
|
||||
" - Identity key: {id_key}\n"
|
||||
" - Fingerprint key: {fp_key}\n").format(
|
||||
prefix=W.prefix("network"),
|
||||
user=server.user,
|
||||
device_msg=device_msg,
|
||||
id_key=id_key,
|
||||
fp_key=fp_key)
|
||||
W.prnt(server.server_buffer, message)
|
||||
elif args.category == "all":
|
||||
for user, keys in olm.device_keys.items():
|
||||
message = ("{prefix}matrix: Identity keys:\n"
|
||||
" - User: {user}\n").format(
|
||||
prefix=W.prefix("network"),
|
||||
user=user)
|
||||
W.prnt(server.server_buffer, message)
|
||||
|
||||
for key in keys:
|
||||
id_key = partition_key(key.keys["curve25519"])
|
||||
fp_key = partition_key(key.keys["ed25519"])
|
||||
device_msg = (" - Device ID: {}\n".format(
|
||||
key.device_id) if key.device_id else "")
|
||||
message = ("{device_msg}"
|
||||
" - Identity key: {id_key}\n"
|
||||
" - Fingerprint key: {fp_key}\n\n").format(
|
||||
device_msg=device_msg,
|
||||
id_key=id_key,
|
||||
fp_key=fp_key)
|
||||
W.prnt(server.server_buffer, message)
|
||||
|
||||
|
||||
def olm_verify_command(server, args):
|
||||
olm = server.olm
|
||||
devices = olm.device_keys
|
||||
filtered_devices = []
|
||||
|
||||
if args.user_filter == "*":
|
||||
filtered_devices = devices.values()
|
||||
else:
|
||||
device_keys = filter(lambda x: args.user_filter in x, devices)
|
||||
filtered_devices = [devices[x] for x in device_keys]
|
||||
|
||||
filtered_devices = chain.from_iterable(filtered_devices)
|
||||
|
||||
if args.device_filter and args.device_filter != "*":
|
||||
filtered_devices = filter(
|
||||
lambda x: args.device_filter in x.device_id,
|
||||
filtered_devices
|
||||
)
|
||||
|
||||
key_list = []
|
||||
|
||||
for device in list(filtered_devices):
|
||||
if olm.verify_device(device):
|
||||
key_list.append(str(device))
|
||||
|
||||
if not key_list:
|
||||
message = ("{prefix}matrix: No matching unverified devices "
|
||||
"found.").format(prefix=W.prefix("error"))
|
||||
W.prnt(server.server_buffer, message)
|
||||
return
|
||||
|
||||
key_str = "\n - ".join(key_list)
|
||||
message = ("{prefix}matrix: Verified keys:\n"
|
||||
" - {key_str}").format(prefix=W.prefix("join"),
|
||||
key_str=key_str)
|
||||
|
||||
W.prnt(server.server_buffer, message)
|
||||
|
||||
|
||||
@own_buffer_or_error
|
||||
@utf8_decode
|
||||
def matrix_olm_command_cb(server_name, buffer, args):
|
||||
server = SERVERS[server_name]
|
||||
parsed_args = olm_cmd_parse_args(args)
|
||||
|
||||
if not parsed_args:
|
||||
return W.WEECHAT_RC_OK
|
||||
|
||||
if not parsed_args.subcommand or parsed_args.subcommand == "info":
|
||||
olm_info_command(server, parsed_args)
|
||||
elif parsed_args.subcommand == "verify":
|
||||
olm_verify_command(server, parsed_args)
|
||||
else:
|
||||
message = ("{prefix}matrix: Command not implemented.".format(
|
||||
prefix=W.prefix("error")))
|
||||
W.prnt(server.server_buffer, message)
|
||||
|
||||
return W.WEECHAT_RC_OK
|
||||
|
||||
|
||||
class EncryptionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DeviceStore(object):
|
||||
def __init__(self, filename):
|
||||
# type: (str) -> None
|
||||
self._entries = []
|
||||
self._filename = filename
|
||||
|
||||
self._load(filename)
|
||||
|
||||
def _load(self, filename):
|
||||
# type: (str) -> None
|
||||
try:
|
||||
with open(filename, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
entry = StoreEntry.from_line(line)
|
||||
|
||||
if not entry:
|
||||
continue
|
||||
|
||||
self._entries.append(entry)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
def _save_store(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
self = args[0]
|
||||
ret = f(*args, **kwargs)
|
||||
self._save()
|
||||
return ret
|
||||
|
||||
return decorated
|
||||
|
||||
def _save(self):
|
||||
# type: (str) -> None
|
||||
with open(self._filename, "w") as f:
|
||||
for entry in self._entries:
|
||||
line = entry.to_line()
|
||||
f.write(line)
|
||||
|
||||
@_save_store
|
||||
def add(self, device):
|
||||
# type: (OlmDeviceKey) -> None
|
||||
new_entries = StoreEntry.from_olmdevice(device)
|
||||
self._entries += new_entries
|
||||
|
||||
# Remove duplicate entries
|
||||
self._entries = list(set(self._entries))
|
||||
|
||||
self._save()
|
||||
|
||||
@_save_store
|
||||
def remove(self, device):
|
||||
# type: (OlmDeviceKey) -> int
|
||||
removed = 0
|
||||
entries = StoreEntry.from_olmdevice(device)
|
||||
|
||||
for entry in entries:
|
||||
if entry in self._entries:
|
||||
self._entries.remove(entry)
|
||||
removed += 1
|
||||
|
||||
self._save()
|
||||
|
||||
return removed
|
||||
|
||||
def check(self, device):
|
||||
# type: (OlmDeviceKey) -> bool
|
||||
entries = StoreEntry.from_olmdevice(device)
|
||||
result = map(lambda entry: entry in self._entries, entries)
|
||||
|
||||
if False in result:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class StoreEntry(object):
|
||||
def __init__(self, user_id, device_id, key_type, key):
|
||||
# type: (str, str, str, str) -> None
|
||||
self.user_id = user_id
|
||||
self.device_id = device_id
|
||||
self.key_type = key_type
|
||||
self.key = key
|
||||
|
||||
@classmethod
|
||||
def from_line(cls, line):
|
||||
# type: (str) -> StoreEntry
|
||||
fields = line.split(' ')
|
||||
|
||||
if len(fields) < 4:
|
||||
return None
|
||||
|
||||
user_id, device_id, key_type, key = fields[:4]
|
||||
|
||||
if key_type == "matrix-ed25519":
|
||||
return cls(user_id, device_id, "ed25519", key)
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_olmdevice(cls, device_key):
|
||||
# type: (OlmDeviceKey) -> [StoreEntry]
|
||||
entries = []
|
||||
|
||||
user_id = device_key.user_id
|
||||
device_id = device_key.device_id
|
||||
|
||||
for key_type, key in device_key.keys.items():
|
||||
if key_type == "ed25519":
|
||||
entries.append(cls(user_id, device_id, "ed25519", key))
|
||||
|
||||
return entries
|
||||
|
||||
def to_line(self):
|
||||
# type: () -> str
|
||||
key_type = "matrix-{}".format(self.key_type)
|
||||
line = "{} {} {} {}\n".format(
|
||||
self.user_id,
|
||||
self.device_id,
|
||||
key_type,
|
||||
self.key
|
||||
)
|
||||
return line
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
return hash(str(self))
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
key_type = "matrix-{}".format(self.key_type)
|
||||
line = "{} {} {} {}".format(
|
||||
self.user_id,
|
||||
self.device_id,
|
||||
key_type,
|
||||
self.key
|
||||
)
|
||||
return line
|
||||
|
||||
def __eq__(self, value):
|
||||
# type: (StoreEntry) -> bool
|
||||
if (self.user_id == value.user_id
|
||||
and self.device_id == value.device_id
|
||||
and self.key_type == value.key_type and self.key == value.key):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class OlmDeviceKey():
|
||||
def __init__(self, user_id, device_id, key_dict):
|
||||
# type: (str, str, Dict[str, str])
|
||||
self.user_id = user_id
|
||||
self.device_id = device_id
|
||||
self.keys = key_dict
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "{} {} {}".format(
|
||||
self.user_id, self.device_id, self.keys["ed25519"])
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return str(self)
|
||||
|
||||
|
||||
class OneTimeKey():
|
||||
def __init__(self, user_id, device_id, key):
|
||||
# type: (str, str, str) -> None
|
||||
self.user_id = user_id
|
||||
self.device_id = device_id
|
||||
self.key = key
|
||||
|
||||
|
||||
class Olm():
|
||||
|
||||
@encrypt_enabled
|
||||
def __init__(
|
||||
self,
|
||||
user,
|
||||
device_id,
|
||||
session_path,
|
||||
database=None,
|
||||
account=None,
|
||||
sessions=None,
|
||||
inbound_group_sessions=None
|
||||
):
|
||||
# type: (str, str, str, Account, Dict[str, List[Session]) -> None
|
||||
self.user = user
|
||||
self.device_id = device_id
|
||||
self.session_path = session_path
|
||||
self.database = database
|
||||
self.device_keys = {}
|
||||
self.shared_sessions = []
|
||||
|
||||
if not database:
|
||||
db_file = "{}_{}.db".format(user, device_id)
|
||||
db_path = os.path.join(session_path, db_file)
|
||||
self.database = sqlite3.connect(db_path)
|
||||
Olm._check_db_tables(self.database)
|
||||
|
||||
if account:
|
||||
self.account = account
|
||||
|
||||
else:
|
||||
self.account = Account()
|
||||
self._insert_acc_to_db()
|
||||
|
||||
if not sessions:
|
||||
sessions = defaultdict(lambda: defaultdict(list))
|
||||
|
||||
if not inbound_group_sessions:
|
||||
inbound_group_sessions = defaultdict(dict)
|
||||
|
||||
self.sessions = sessions
|
||||
self.inbound_group_sessions = inbound_group_sessions
|
||||
self.outbound_group_sessions = {}
|
||||
|
||||
trust_file_path = "{}_{}.trusted_devices".format(user, device_id)
|
||||
self.trust_db = DeviceStore(os.path.join(session_path, trust_file_path))
|
||||
|
||||
def _create_session(self, sender, sender_key, message):
|
||||
W.prnt("", "matrix: Creating session for {}".format(sender))
|
||||
session = InboundSession(self.account, message, sender_key)
|
||||
W.prnt("", "matrix: Created session for {}".format(sender))
|
||||
self.account.remove_one_time_keys(session)
|
||||
self._update_acc_in_db()
|
||||
|
||||
return session
|
||||
|
||||
def verify_device(self, device):
|
||||
if self.trust_db.check(device):
|
||||
return False
|
||||
|
||||
self.trust_db.add(device)
|
||||
return True
|
||||
|
||||
def unverify_device(self, device):
|
||||
self.trust_db.remove(device)
|
||||
|
||||
def create_session(self, user_id, device_id, one_time_key):
|
||||
W.prnt("", "matrix: Creating session for {}".format(user_id))
|
||||
id_key = None
|
||||
|
||||
for user, keys in self.device_keys.items():
|
||||
if user != user_id:
|
||||
continue
|
||||
|
||||
for key in keys:
|
||||
if key.device_id == device_id:
|
||||
id_key = key.keys["curve25519"]
|
||||
break
|
||||
|
||||
if not id_key:
|
||||
W.prnt("", "ERRR not found ID key")
|
||||
W.prnt("", "Found id key {}".format(id_key))
|
||||
session = OutboundSession(self.account, id_key, one_time_key)
|
||||
self._update_acc_in_db()
|
||||
self.sessions[user_id][device_id].append(session)
|
||||
self._store_session(user_id, device_id, session)
|
||||
W.prnt("", "matrix: Created session for {}".format(user_id))
|
||||
|
||||
def create_group_session(self, room_id, session_id, session_key):
|
||||
W.prnt("", "matrix: Creating group session for {}".format(room_id))
|
||||
session = InboundGroupSession(session_key)
|
||||
self.inbound_group_sessions[room_id][session_id] = session
|
||||
self._store_inbound_group_session(room_id, session)
|
||||
|
||||
def create_outbound_group_session(self, room_id):
|
||||
session = OutboundGroupSession()
|
||||
self.outbound_group_sessions[room_id] = session
|
||||
self.create_group_session(room_id, session.id, session.session_key)
|
||||
|
||||
@encrypt_enabled
|
||||
def get_missing_sessions(self, users):
|
||||
# type: (List[str]) -> Dict[str, Dict[str, str]]
|
||||
missing = {}
|
||||
|
||||
for user in users:
|
||||
devices = []
|
||||
|
||||
for key in self.device_keys[user]:
|
||||
# we don't need a session for our own device, skip it
|
||||
if key.device_id == self.device_id:
|
||||
continue
|
||||
|
||||
if not self.sessions[user][key.device_id]:
|
||||
W.prnt("", "Missing session for device {}".format(key.device_id))
|
||||
devices.append(key.device_id)
|
||||
|
||||
if devices:
|
||||
missing[user] = {device: "signed_curve25519" for
|
||||
device in devices}
|
||||
|
||||
return missing
|
||||
|
||||
@encrypt_enabled
|
||||
def decrypt(self, sender, sender_key, message):
|
||||
plaintext = None
|
||||
|
||||
for device_id, session_list in self.sessions[sender].items():
|
||||
for session in session_list:
|
||||
W.prnt("", "Trying session for device {}".format(device_id))
|
||||
try:
|
||||
if isinstance(message, OlmPreKeyMessage):
|
||||
if not session.matches(message):
|
||||
continue
|
||||
|
||||
W.prnt("", "Decrypting using existing session")
|
||||
plaintext = session.decrypt(message)
|
||||
parsed_plaintext = json.loads(plaintext, encoding='utf-8')
|
||||
W.prnt("", "Decrypted using existing session")
|
||||
return parsed_plaintext
|
||||
except OlmSessionError:
|
||||
pass
|
||||
|
||||
try:
|
||||
session = self._create_session(sender, sender_key, message)
|
||||
except OlmSessionError:
|
||||
return None
|
||||
|
||||
try:
|
||||
plaintext = session.decrypt(message)
|
||||
parsed_plaintext = json.loads(plaintext, encoding='utf-8')
|
||||
|
||||
device_id = sanitize_id(parsed_plaintext["sender_device"])
|
||||
self.sessions[sender][device_id].append(session)
|
||||
self._store_session(sender, device_id, session)
|
||||
return parsed_plaintext
|
||||
except OlmSessionError:
|
||||
return None
|
||||
|
||||
def group_encrypt(self, room_id, plaintext_dict, own_id, users):
|
||||
# type: (str, Dict[str, str]) -> Dict[str, str], Optional[Dict[Any, Any]]
|
||||
plaintext_dict["room_id"] = room_id
|
||||
to_device_dict = None
|
||||
|
||||
if room_id not in self.outbound_group_sessions:
|
||||
self.create_outbound_group_session(room_id)
|
||||
|
||||
if self.outbound_group_sessions[room_id].id not in self.shared_sessions:
|
||||
to_device_dict = self.share_group_session(room_id, own_id, users)
|
||||
self.shared_sessions.append(
|
||||
self.outbound_group_sessions[room_id].id
|
||||
)
|
||||
|
||||
session = self.outbound_group_sessions[room_id]
|
||||
|
||||
ciphertext = session.encrypt(Olm._to_json(plaintext_dict))
|
||||
|
||||
payload_dict = {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"sender_key": self.account.identity_keys["curve25519"],
|
||||
"ciphertext": ciphertext,
|
||||
"session_id": session.id,
|
||||
"device_id": self.device_id
|
||||
}
|
||||
|
||||
return payload_dict, to_device_dict
|
||||
|
||||
@encrypt_enabled
|
||||
def group_decrypt(self, room_id, session_id, ciphertext):
|
||||
if session_id not in self.inbound_group_sessions[room_id]:
|
||||
return None, None
|
||||
|
||||
session = self.inbound_group_sessions[room_id][session_id]
|
||||
|
||||
try:
|
||||
return session.decrypt(ciphertext)
|
||||
except OlmGroupSessionError:
|
||||
pass
|
||||
|
||||
return None, None
|
||||
|
||||
def share_group_session(self, room_id, own_id, users):
|
||||
group_session = self.outbound_group_sessions[room_id]
|
||||
|
||||
key_content = {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"room_id": room_id,
|
||||
"session_id": group_session.id,
|
||||
"session_key": group_session.session_key,
|
||||
"chain_index": group_session.message_index
|
||||
}
|
||||
|
||||
payload_dict = {
|
||||
"type": "m.room_key",
|
||||
"content": key_content,
|
||||
# TODO we don't have the user_id in the Olm class
|
||||
"sender": own_id,
|
||||
"sender_device": self.device_id,
|
||||
"keys": {
|
||||
"ed25519": self.account.identity_keys["ed25519"]
|
||||
}
|
||||
}
|
||||
|
||||
to_device_dict = {
|
||||
"messages": {}
|
||||
}
|
||||
|
||||
for user in users:
|
||||
if user not in self.device_keys:
|
||||
continue
|
||||
|
||||
for key in self.device_keys[user]:
|
||||
if key.device_id == self.device_id:
|
||||
continue
|
||||
|
||||
if not self.sessions[user][key.device_id]:
|
||||
continue
|
||||
|
||||
if not self.trust_db.check(key):
|
||||
raise OlmTrustError
|
||||
|
||||
device_payload_dict = payload_dict.copy()
|
||||
# TODO sort the sessions
|
||||
session = self.sessions[user][key.device_id][0]
|
||||
device_payload_dict["recipient"] = user
|
||||
device_payload_dict["recipient_keys"] = {
|
||||
"ed25519": key.keys["ed25519"]
|
||||
}
|
||||
|
||||
olm_message = session.encrypt(
|
||||
Olm._to_json(device_payload_dict)
|
||||
)
|
||||
|
||||
olm_dict = {
|
||||
"algorithm": "m.olm.v1.curve25519-aes-sha2",
|
||||
"sender_key": self.account.identity_keys["curve25519"],
|
||||
"ciphertext": {
|
||||
key.keys["curve25519"]: {
|
||||
"type": (0 if isinstance(
|
||||
olm_message,
|
||||
OlmPreKeyMessage
|
||||
) else 1),
|
||||
"body": olm_message.ciphertext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user not in to_device_dict["messages"]:
|
||||
to_device_dict["messages"][user] = {}
|
||||
|
||||
to_device_dict["messages"][user][key.device_id] = olm_dict
|
||||
|
||||
# W.prnt("", pprint.pformat(to_device_dict))
|
||||
return to_device_dict
|
||||
|
||||
@classmethod
|
||||
@encrypt_enabled
|
||||
def from_session_dir(cls, user, device_id, session_path):
|
||||
# type: (Server) -> Olm
|
||||
db_file = "{}_{}.db".format(user, device_id)
|
||||
db_path = os.path.join(session_path, db_file)
|
||||
database = sqlite3.connect(db_path)
|
||||
Olm._check_db_tables(database)
|
||||
|
||||
cursor = database.cursor()
|
||||
|
||||
cursor.execute("select pickle from olmaccount where user = ?", (user,))
|
||||
row = cursor.fetchone()
|
||||
account_pickle = row[0]
|
||||
|
||||
cursor.execute("select user, device_id, pickle from olmsessions")
|
||||
db_sessions = cursor.fetchall()
|
||||
|
||||
cursor.execute("select room_id, pickle from inbound_group_sessions")
|
||||
db_inbound_group_sessions = cursor.fetchall()
|
||||
|
||||
cursor.close()
|
||||
|
||||
sessions = defaultdict(lambda: defaultdict(list))
|
||||
inbound_group_sessions = defaultdict(dict)
|
||||
|
||||
try:
|
||||
try:
|
||||
account_pickle = bytes(account_pickle, "utf-8")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
account = Account.from_pickle(account_pickle)
|
||||
|
||||
for db_session in db_sessions:
|
||||
session_pickle = db_session[2]
|
||||
try:
|
||||
session_pickle = bytes(session_pickle, "utf-8")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
sessions[db_session[0]][db_session[1]].append(
|
||||
Session.from_pickle(session_pickle))
|
||||
|
||||
for db_session in db_inbound_group_sessions:
|
||||
session_pickle = db_session[1]
|
||||
try:
|
||||
session_pickle = bytes(session_pickle, "utf-8")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
session = InboundGroupSession.from_pickle(session_pickle)
|
||||
inbound_group_sessions[db_session[0]][session.id] = session
|
||||
|
||||
return cls(user, device_id, session_path, database, account,
|
||||
sessions, inbound_group_sessions)
|
||||
except (OlmAccountError, OlmSessionError) as error:
|
||||
raise EncryptionError(error)
|
||||
|
||||
def _update_acc_in_db(self):
|
||||
cursor = self.database.cursor()
|
||||
cursor.execute("update olmaccount set pickle=? where user = ?",
|
||||
(self.account.pickle(), self.user))
|
||||
self.database.commit()
|
||||
cursor.close()
|
||||
|
||||
def _update_sessions_in_db(self):
|
||||
cursor = self.database.cursor()
|
||||
|
||||
for user, session_dict in self.sessions.items():
|
||||
for device_id, session_list in session_dict.items():
|
||||
for session in session_list:
|
||||
cursor.execute("""update olmsessions set pickle=?
|
||||
where user = ? and session_id = ? and
|
||||
device_id = ?""",
|
||||
(session.pickle(), user, session.id,
|
||||
device_id))
|
||||
self.database.commit()
|
||||
|
||||
cursor.close()
|
||||
|
||||
def _update_inbound_group_sessions(self):
|
||||
cursor = self.database.cursor()
|
||||
|
||||
for room_id, session_dict in self.inbound_group_sessions.items():
|
||||
for session in session_dict.values():
|
||||
cursor.execute("""update inbound_group_sessions set pickle=?
|
||||
where room_id = ? and session_id = ?""",
|
||||
(session.pickle(), room_id, session.id))
|
||||
self.database.commit()
|
||||
|
||||
cursor.close()
|
||||
|
||||
def _store_session(self, user, device_id, session):
|
||||
cursor = self.database.cursor()
|
||||
|
||||
cursor.execute("insert into olmsessions values(?,?,?,?)",
|
||||
(user, device_id, session.id, session.pickle()))
|
||||
|
||||
self.database.commit()
|
||||
|
||||
cursor.close()
|
||||
|
||||
def _store_inbound_group_session(self, room_id, session):
|
||||
cursor = self.database.cursor()
|
||||
|
||||
cursor.execute("insert into inbound_group_sessions values(?,?,?)",
|
||||
(room_id, session.id, session.pickle()))
|
||||
|
||||
self.database.commit()
|
||||
|
||||
cursor.close()
|
||||
|
||||
def _insert_acc_to_db(self):
|
||||
cursor = self.database.cursor()
|
||||
cursor.execute("insert into olmaccount values (?,?)",
|
||||
(self.user, self.account.pickle()))
|
||||
self.database.commit()
|
||||
cursor.close()
|
||||
|
||||
@staticmethod
|
||||
def _check_db_tables(database):
|
||||
cursor = database.cursor()
|
||||
cursor.execute("""select name from sqlite_master where type='table'
|
||||
and name='olmaccount'""")
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("create table olmaccount (user text, pickle text)")
|
||||
database.commit()
|
||||
|
||||
cursor.execute("""select name from sqlite_master where type='table'
|
||||
and name='olmsessions'""")
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("""create table olmsessions (user text,
|
||||
device_id text, session_id text, pickle text)""")
|
||||
database.commit()
|
||||
|
||||
cursor.execute("""select name from sqlite_master where type='table'
|
||||
and name='inbound_group_sessions'""")
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("""create table inbound_group_sessions
|
||||
(room_id text, session_id text, pickle text)""")
|
||||
database.commit()
|
||||
|
||||
cursor.close()
|
||||
|
||||
@encrypt_enabled
|
||||
def to_session_dir(self):
|
||||
# type: (Server) -> None
|
||||
try:
|
||||
self._update_acc_in_db()
|
||||
self._update_sessions_in_db()
|
||||
except OlmAccountError as error:
|
||||
raise EncryptionError(error)
|
||||
|
||||
def sign_json(self, json_dict):
|
||||
signature = self.account.sign(json.dumps(
|
||||
json_dict,
|
||||
ensure_ascii=False,
|
||||
separators=(',', ':'),
|
||||
sort_keys=True,
|
||||
))
|
||||
|
||||
return signature
|
||||
|
||||
@staticmethod
|
||||
def _to_json(json_dict):
|
||||
# type: (Dict[Any, Any]) -> str
|
||||
return json.dumps(
|
||||
json_dict,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True
|
||||
)
|
||||
|
||||
@encrypt_enabled
|
||||
def mark_keys_as_published(self):
|
||||
self.account.mark_keys_as_published()
|
386
matrix/events.py
386
matrix/events.py
|
@ -1,386 +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
|
||||
from builtins import str
|
||||
|
||||
import json
|
||||
import pprint
|
||||
|
||||
from collections import deque, defaultdict
|
||||
from functools import partial
|
||||
from operator import itemgetter
|
||||
|
||||
from matrix.globals import W
|
||||
from matrix.utils import (tags_for_message, sanitize_id, sanitize_token,
|
||||
sanitize_text, tags_from_line_data)
|
||||
|
||||
from matrix.encryption import OlmDeviceKey, OneTimeKey
|
||||
from .buffer import RoomUser, OwnMessage, OwnAction
|
||||
|
||||
try:
|
||||
from olm.session import OlmMessage, OlmPreKeyMessage
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class MatrixEvent():
|
||||
|
||||
def __init__(self, server):
|
||||
self.server = server
|
||||
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
|
||||
class MatrixErrorEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, error_message, fatal=False):
|
||||
self.error_message = error_message
|
||||
self.fatal = fatal
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, error_prefix, fatal, parsed_dict):
|
||||
try:
|
||||
message = "{prefix}: {error}".format(
|
||||
prefix=error_prefix, error=sanitize_text(parsed_dict["error"]))
|
||||
return cls(server, message, fatal=fatal)
|
||||
except KeyError:
|
||||
return cls(
|
||||
server, ("{prefix}: Invalid JSON response "
|
||||
"from server.").format(prefix=error_prefix),
|
||||
fatal=fatal)
|
||||
|
||||
|
||||
class MatrixKeyUploadEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, device_keys):
|
||||
self.device_keys = device_keys
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
def execute(self):
|
||||
self.server.olm.mark_keys_as_published()
|
||||
self.server.store_olm()
|
||||
|
||||
if not self.device_keys:
|
||||
return
|
||||
|
||||
message = "{prefix}matrix: Uploaded Olm device keys.".format(
|
||||
prefix=W.prefix("network"))
|
||||
|
||||
W.prnt(self.server.server_buffer, message)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, device_keys, parsed_dict):
|
||||
try:
|
||||
return cls(server, device_keys)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return MatrixErrorEvent.from_dict(server, "Error uploading device"
|
||||
"keys", False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixSendEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id, message):
|
||||
self.room_id = room_id
|
||||
self.message = message
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, message, parsed_dict):
|
||||
try:
|
||||
event_id = sanitize_id(parsed_dict["event_id"])
|
||||
sender = server.user_id
|
||||
age = 0
|
||||
formatted_message = message
|
||||
|
||||
message = OwnMessage(sender, age, event_id, formatted_message)
|
||||
|
||||
return cls(server, room_id, message)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return MatrixErrorEvent.from_dict(server, "Error sending message",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixEmoteEvent(MatrixSendEvent):
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, message, parsed_dict):
|
||||
try:
|
||||
event_id = sanitize_id(parsed_dict["event_id"])
|
||||
sender = server.user_id
|
||||
age = 0
|
||||
formatted_message = message
|
||||
|
||||
message = OwnAction(sender, age, event_id, formatted_message)
|
||||
|
||||
return cls(server, room_id, message)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return MatrixErrorEvent.from_dict(server, "Error sending message",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixTopicEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id, event_id, topic):
|
||||
self.room_id = room_id
|
||||
self.topic = topic
|
||||
self.event_id = event_id
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, topic, parsed_dict):
|
||||
try:
|
||||
return cls(server, room_id, sanitize_id(parsed_dict["event_id"]),
|
||||
topic)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return MatrixErrorEvent.from_dict(server, "Error setting topic",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixRedactEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id, event_id, reason):
|
||||
self.room_id = room_id
|
||||
self.topic = reason
|
||||
self.event_id = event_id
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, reason, parsed_dict):
|
||||
try:
|
||||
return cls(server, room_id, sanitize_id(parsed_dict["event_id"]),
|
||||
reason)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return MatrixErrorEvent.from_dict(server, "Error redacting message",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixJoinEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room, room_id):
|
||||
self.room = room
|
||||
self.room_id = room_id
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, parsed_dict):
|
||||
try:
|
||||
return cls(
|
||||
server,
|
||||
room_id,
|
||||
sanitize_id(parsed_dict["room_id"]),
|
||||
)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return MatrixErrorEvent.from_dict(server, "Error joining room",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixPartEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id):
|
||||
self.room_id = room_id
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, parsed_dict):
|
||||
try:
|
||||
if parsed_dict == {}:
|
||||
return cls(server, room_id)
|
||||
|
||||
raise KeyError
|
||||
except KeyError:
|
||||
return MatrixErrorEvent.from_dict(server, "Error leaving room",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixInviteEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id, user_id):
|
||||
self.room_id = room_id
|
||||
self.user_id = user_id
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, user_id, parsed_dict):
|
||||
try:
|
||||
if parsed_dict == {}:
|
||||
return cls(server, room_id, user_id)
|
||||
|
||||
raise KeyError
|
||||
except KeyError:
|
||||
return MatrixErrorEvent.from_dict(server, "Error inviting user",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixKickEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id, user_id, reason):
|
||||
self.room_id = room_id
|
||||
self.user_id = user_id
|
||||
self.reason = reason
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, user_id, reason, parsed_dict):
|
||||
try:
|
||||
if parsed_dict == {}:
|
||||
return cls(server, room_id, user_id, reason)
|
||||
|
||||
raise KeyError
|
||||
except KeyError:
|
||||
return MatrixErrorEvent.from_dict(server, "Error kicking user",
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixKeyQueryEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, keys):
|
||||
self.keys = keys
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@staticmethod
|
||||
def _get_keys(key_dict):
|
||||
keys = {}
|
||||
|
||||
for key_type, key in key_dict.items():
|
||||
key_type, _ = key_type.split(":")
|
||||
keys[key_type] = key
|
||||
|
||||
return keys
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, parsed_dict):
|
||||
keys = defaultdict(list)
|
||||
try:
|
||||
for user_id, device_dict in parsed_dict["device_keys"].items():
|
||||
for device_id, key_dict in device_dict.items():
|
||||
device_keys = MatrixKeyQueryEvent._get_keys(
|
||||
key_dict.pop("keys"))
|
||||
keys[user_id].append(OlmDeviceKey(user_id, device_id,
|
||||
device_keys))
|
||||
return cls(server, keys)
|
||||
except KeyError:
|
||||
# TODO error message
|
||||
return MatrixErrorEvent.from_dict(server, "Error kicking user",
|
||||
False, parsed_dict)
|
||||
|
||||
def execute(self):
|
||||
# TODO move this logic into an Olm method
|
||||
olm = self.server.olm
|
||||
|
||||
if olm.device_keys == self.keys:
|
||||
return
|
||||
|
||||
olm.device_keys = self.keys
|
||||
# TODO invalidate megolm sessions for rooms that got new devices
|
||||
|
||||
|
||||
class MatrixKeyClaimEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id, keys):
|
||||
self.keys = keys
|
||||
self.room_id = room_id
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, parsed_dict):
|
||||
W.prnt("", pprint.pformat(parsed_dict))
|
||||
keys = []
|
||||
try:
|
||||
for user_id, user_dict in parsed_dict["one_time_keys"].items():
|
||||
for device_id, device_dict in user_dict.items():
|
||||
for key_dict in device_dict.values():
|
||||
# TODO check the signature of the key
|
||||
key = OneTimeKey(user_id, device_id, key_dict["key"])
|
||||
keys.append(key)
|
||||
|
||||
return cls(server, room_id, keys)
|
||||
except KeyError:
|
||||
return MatrixErrorEvent.from_dict(
|
||||
server, ("Error claiming onetime keys."), False, parsed_dict)
|
||||
|
||||
def execute(self):
|
||||
server = self.server
|
||||
olm = server.olm
|
||||
|
||||
for key in self.keys:
|
||||
olm.create_session(key.user_id, key.device_id, key.key)
|
||||
|
||||
while server.encryption_queue[self.room_id]:
|
||||
formatted_message = server.encryption_queue[self.room_id].popleft()
|
||||
room, _ = server.find_room_from_id(self.room_id)
|
||||
server.send_room_message(room, formatted_message, True)
|
||||
|
||||
|
||||
class MatrixToDeviceEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server):
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, parsed_dict):
|
||||
try:
|
||||
if parsed_dict == {}:
|
||||
return cls(server)
|
||||
|
||||
raise KeyError
|
||||
except KeyError:
|
||||
return MatrixErrorEvent.from_dict(server, ("Error sending to "
|
||||
"device message"),
|
||||
False, parsed_dict)
|
||||
|
||||
|
||||
class MatrixBacklogEvent(MatrixEvent):
|
||||
|
||||
def __init__(self, server, room_id, end_token, events):
|
||||
self.room_id = room_id
|
||||
self.end_token = end_token
|
||||
self.events = events
|
||||
MatrixEvent.__init__(self, server)
|
||||
|
||||
@staticmethod
|
||||
def _room_event_from_dict(room_id, event_dict):
|
||||
if room_id != event_dict["room_id"]:
|
||||
raise ValueError
|
||||
|
||||
if "redacted_by" in event_dict["unsigned"]:
|
||||
return RoomRedactedMessageEvent.from_dict(event_dict)
|
||||
|
||||
return RoomMessageEvent.from_dict(event_dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, server, room_id, parsed_dict):
|
||||
try:
|
||||
end_token = sanitize_id(parsed_dict["end"])
|
||||
|
||||
if not parsed_dict["chunk"]:
|
||||
return cls(server, room_id, end_token, [])
|
||||
|
||||
event_func = partial(MatrixBacklogEvent._room_event_from_dict,
|
||||
room_id)
|
||||
|
||||
message_events = list(
|
||||
filter(lambda event: event["type"] == "m.room.message",
|
||||
parsed_dict["chunk"]))
|
||||
|
||||
events = [event_func(m) for m in message_events]
|
||||
|
||||
return cls(server, room_id, end_token, events)
|
||||
except (KeyError, ValueError, TypeError):
|
||||
return MatrixErrorEvent.from_dict(server, "Error fetching backlog",
|
||||
False, parsed_dict)
|
|
@ -1,93 +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
|
||||
from builtins import str
|
||||
|
||||
import json
|
||||
from enum import Enum, unique
|
||||
|
||||
|
||||
@unique
|
||||
class RequestType(Enum):
|
||||
GET = 0
|
||||
POST = 1
|
||||
PUT = 2
|
||||
|
||||
|
||||
class HttpResponse:
|
||||
|
||||
def __init__(self, status, headers, body):
|
||||
self.status = status # type: int
|
||||
self.headers = headers # type: Dict[str, str]
|
||||
self.body = body # type: bytes
|
||||
|
||||
|
||||
# yapf: disable
|
||||
class HttpRequest:
|
||||
def __init__(
|
||||
self,
|
||||
request_type, # type: RequestType
|
||||
host, # type: str
|
||||
location, # type: str
|
||||
data=None, # type: Dict[str, Any]
|
||||
user_agent='weechat-matrix/{version}'.format(
|
||||
version="0.1") # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
user_agent = 'User-Agent: {agent}'.format(agent=user_agent)
|
||||
host_header = 'Host: {host}'.format(host=host)
|
||||
keepalive = "Connection: keep-alive"
|
||||
request_list = [] # type: List[str]
|
||||
accept_header = 'Accept: */*' # type: str
|
||||
end_separator = '\r\n' # type: str
|
||||
payload = "" # type: str
|
||||
# yapf: enable
|
||||
|
||||
if request_type == RequestType.GET:
|
||||
get = 'GET {location} HTTP/1.1'.format(location=location)
|
||||
request_list = [
|
||||
get, host_header, user_agent, keepalive, accept_header,
|
||||
end_separator
|
||||
]
|
||||
|
||||
elif (request_type == RequestType.POST or
|
||||
request_type == RequestType.PUT):
|
||||
|
||||
json_data = json.dumps(data, separators=(',', ':'))
|
||||
|
||||
if request_type == RequestType.POST:
|
||||
method = "POST"
|
||||
else:
|
||||
method = "PUT"
|
||||
|
||||
request_line = '{method} {location} HTTP/1.1'.format(
|
||||
method=method, location=location)
|
||||
|
||||
type_header = 'Content-Type: application/x-www-form-urlencoded'
|
||||
length_header = 'Content-Length: {length}'.format(
|
||||
length=len(json_data))
|
||||
|
||||
request_list = [
|
||||
request_line, host_header, user_agent, keepalive,
|
||||
accept_header, length_header, type_header, end_separator
|
||||
]
|
||||
payload = json_data
|
||||
|
||||
request = '\r\n'.join(request_list)
|
||||
|
||||
self.request = request
|
||||
self.payload = payload
|
377
matrix/server.py
377
matrix/server.py
|
@ -15,55 +15,23 @@
|
|||
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from builtins import str, bytes
|
||||
|
||||
import os
|
||||
import ssl
|
||||
import socket
|
||||
import time
|
||||
import datetime
|
||||
import pprint
|
||||
import json
|
||||
|
||||
from collections import deque, defaultdict
|
||||
from http_parser.pyparser import HttpParser
|
||||
|
||||
from nio import Client, LoginResponse, SyncRepsponse
|
||||
from nio import HttpClient, LoginResponse, SyncRepsponse
|
||||
|
||||
from matrix.plugin_options import Option, DebugType
|
||||
from matrix.utils import (key_from_value, prnt_debug, server_buffer_prnt,
|
||||
create_server_buffer, tags_for_message,
|
||||
server_ts_to_weechat, shorten_sender)
|
||||
create_server_buffer)
|
||||
from matrix.utf import utf8_decode
|
||||
from matrix.globals import W, SERVERS, OPTIONS
|
||||
import matrix.api as API
|
||||
from matrix.globals import W, SERVERS
|
||||
from .buffer import RoomBuffer, OwnMessage, OwnAction
|
||||
from matrix.api import (
|
||||
MatrixClient,
|
||||
MatrixSyncMessage,
|
||||
MatrixLoginMessage,
|
||||
MatrixKeyUploadMessage,
|
||||
MatrixKeyQueryMessage,
|
||||
MatrixToDeviceMessage,
|
||||
MatrixSendMessage,
|
||||
MatrixEncryptedMessage,
|
||||
MatrixKeyClaimMessage
|
||||
)
|
||||
|
||||
from .events import (
|
||||
MatrixSendEvent,
|
||||
MatrixBacklogEvent,
|
||||
MatrixErrorEvent,
|
||||
MatrixEmoteEvent,
|
||||
MatrixJoinEvent
|
||||
)
|
||||
|
||||
from matrix.encryption import (
|
||||
Olm,
|
||||
EncryptionError,
|
||||
OlmTrustError,
|
||||
encrypt_enabled
|
||||
)
|
||||
|
||||
try:
|
||||
FileNotFoundError
|
||||
|
@ -108,7 +76,6 @@ class MatrixServer:
|
|||
self.ssl_context = ssl.create_default_context() # type: ssl.SSLContext
|
||||
|
||||
self.client = None
|
||||
self.nio_client = Client() # type: Option[Client]
|
||||
self.access_token = None # type: str
|
||||
self.next_batch = None # type: str
|
||||
self.transaction_id = 0 # type: int
|
||||
|
@ -117,18 +84,8 @@ class MatrixServer:
|
|||
|
||||
self.send_fd_hook = None # type: weechat.hook
|
||||
self.send_buffer = b"" # type: bytes
|
||||
self.current_message = None # type: MatrixMessage
|
||||
self.device_check_timestamp = None
|
||||
|
||||
self.http_parser = HttpParser() # type: HttpParser
|
||||
self.http_buffer = [] # type: List[bytes]
|
||||
|
||||
# Queue of messages we need to send off.
|
||||
self.send_queue = deque() # type: Deque[MatrixMessage]
|
||||
|
||||
# Queue of messages we send off and are waiting a response for
|
||||
self.receive_queue = deque() # type: Deque[MatrixMessage]
|
||||
|
||||
self.event_queue_timer = None
|
||||
self.event_queue = deque() # type: Deque[RoomInfo]
|
||||
|
||||
|
@ -166,48 +123,6 @@ class MatrixServer:
|
|||
with open(path, 'w') as f:
|
||||
f.write(self.device_id)
|
||||
|
||||
def _load_olm(self):
|
||||
try:
|
||||
self.olm = Olm.from_session_dir(
|
||||
self.user,
|
||||
self.device_id,
|
||||
self.get_session_path()
|
||||
)
|
||||
message = ("{prefix}matrix: Loaded Olm account for {user} (device:"
|
||||
"{device})").format(prefix=W.prefix("network"),
|
||||
user=self.user,
|
||||
device=self.device_id)
|
||||
W.prnt("", message)
|
||||
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except EncryptionError as error:
|
||||
message = ("{prefix}matrix: Error loading Olm"
|
||||
"account: {error}.").format(
|
||||
prefix=W.prefix("error"), error=error)
|
||||
W.prnt("", message)
|
||||
|
||||
@encrypt_enabled
|
||||
def create_olm(self):
|
||||
message = ("{prefix}matrix: Creating new Olm identity for "
|
||||
"{self_color}{user}{ncolor}"
|
||||
" on {server_color}{server}{ncolor} for device "
|
||||
"{device}.").format(
|
||||
prefix=W.prefix("network"),
|
||||
self_color=W.color("chat_nick_self"),
|
||||
ncolor=W.color("reset"),
|
||||
user=self.user_id,
|
||||
server_color=W.color("chat_server"),
|
||||
server=self.name,
|
||||
device=self.device_id)
|
||||
W.prnt(self.server_buffer, message)
|
||||
self.olm = Olm(self.user, self.device_id, self.get_session_path())
|
||||
|
||||
@encrypt_enabled
|
||||
def store_olm(self):
|
||||
if self.olm:
|
||||
self.olm.to_session_dir()
|
||||
|
||||
def _create_options(self, config_file):
|
||||
options = [
|
||||
Option('autoconnect', 'boolean', '', 0, 0, 'off',
|
||||
|
@ -243,15 +158,9 @@ class MatrixServer:
|
|||
option.max, option.value, option.value, 0, "", "",
|
||||
"matrix_config_server_change_cb", self.name, "", "")
|
||||
|
||||
def reset_parser(self):
|
||||
self.http_parser = HttpParser()
|
||||
self.http_buffer = []
|
||||
|
||||
def _change_client(self):
|
||||
host = ':'.join([self.address, str(self.port)])
|
||||
user_agent = 'weechat-matrix/{version}'.format(version="0.1")
|
||||
self.client = MatrixClient(host, user_agent=user_agent)
|
||||
# self.nio_client = Client()
|
||||
self.client = HttpClient(host, self.user)
|
||||
|
||||
def update_option(self, option, option_name):
|
||||
if option_name == "address":
|
||||
|
@ -280,13 +189,9 @@ class MatrixServer:
|
|||
value = W.config_string(option)
|
||||
self.user = value
|
||||
self.access_token = ""
|
||||
self.nio_client.user = value
|
||||
self.nio_client.access_token = ""
|
||||
|
||||
self._load_device_id()
|
||||
|
||||
# if self.device_id:
|
||||
# self._load_olm()
|
||||
if self.client:
|
||||
self.client.user = value
|
||||
|
||||
elif option_name == "password":
|
||||
value = W.config_string(option)
|
||||
|
@ -305,7 +210,6 @@ class MatrixServer:
|
|||
"Adding to queue").format(
|
||||
prefix=W.prefix("error"),
|
||||
t=message.__class__.__name__))
|
||||
self.send_queue.append(message)
|
||||
|
||||
def try_send(self, message):
|
||||
# type: (MatrixServer, bytes) -> bool
|
||||
|
@ -362,31 +266,16 @@ class MatrixServer:
|
|||
return True
|
||||
|
||||
def _abort_send(self):
|
||||
self.send_queue.appendleft(self.current_message)
|
||||
self.current_message = None
|
||||
self.send_buffer = ""
|
||||
|
||||
def _finalize_send(self):
|
||||
# type: (MatrixServer) -> None
|
||||
self.current_message.send_time = time.time()
|
||||
self.receive_queue.append(self.current_message)
|
||||
|
||||
self.send_buffer = b""
|
||||
self.current_message = None
|
||||
|
||||
def send(self, message):
|
||||
# type: (MatrixServer, MatrixMessage) -> bool
|
||||
if self.current_message:
|
||||
return False
|
||||
|
||||
self.current_message = message
|
||||
|
||||
request = message.request.request
|
||||
payload = message.request.payload
|
||||
|
||||
bytes_message = bytes(request, 'utf-8') + bytes(payload, 'utf-8')
|
||||
|
||||
self.try_send(bytes_message)
|
||||
def send(self, data):
|
||||
# type: (bytes) -> bool
|
||||
self.try_send(data)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -437,8 +326,6 @@ class MatrixServer:
|
|||
self.socket = None
|
||||
self.connected = False
|
||||
self.access_token = ""
|
||||
self.send_queue.clear()
|
||||
self.receive_queue.clear()
|
||||
|
||||
self.send_buffer = b""
|
||||
self.current_message = None
|
||||
|
@ -500,124 +387,13 @@ class MatrixServer:
|
|||
return True
|
||||
|
||||
def sync(self):
|
||||
limit = None if self.next_batch else OPTIONS.sync_limit
|
||||
message = MatrixSyncMessage(self.client, self.next_batch, limit)
|
||||
self.send_queue.append(message)
|
||||
|
||||
def _send_unencrypted_message(self, room_id, formatted_data):
|
||||
message = MatrixSendMessage(
|
||||
self.client, room_id=room_id, formatted_message=formatted_data)
|
||||
self.send_or_queue(message)
|
||||
|
||||
def send_room_message(
|
||||
self,
|
||||
room,
|
||||
formatted_data,
|
||||
already_claimed=False
|
||||
):
|
||||
# type: (str, Formatted) -> None
|
||||
if not room.encrypted:
|
||||
self._send_unencrypted_message(room.room_id, formatted_data)
|
||||
return
|
||||
|
||||
# TODO don't send messages unless all the devices are verified
|
||||
missing = self.olm.get_missing_sessions(room.users.keys())
|
||||
|
||||
if missing and not already_claimed:
|
||||
W.prnt("", "{prefix}matrix: Olm session missing for room, can't"
|
||||
" encrypt message.")
|
||||
W.prnt("", pprint.pformat(missing))
|
||||
self.encryption_queue[room.room_id].append(formatted_data)
|
||||
message = MatrixKeyClaimMessage(self.client, room.room_id, missing)
|
||||
self.send_or_queue(message)
|
||||
return
|
||||
|
||||
body = {"msgtype": "m.text", "body": formatted_data.to_plain()}
|
||||
|
||||
if formatted_data.is_formatted():
|
||||
body["format"] = "org.matrix.custom.html"
|
||||
body["formatted_body"] = formatted_data.to_html()
|
||||
|
||||
plaintext_dict = {
|
||||
"type": "m.room.message",
|
||||
"content": body
|
||||
}
|
||||
|
||||
W.prnt("", "matrix: Encrypting message")
|
||||
|
||||
try:
|
||||
payload_dict, to_device_dict = self.olm.group_encrypt(
|
||||
room.room_id,
|
||||
plaintext_dict,
|
||||
self.user_id,
|
||||
room.users.keys()
|
||||
)
|
||||
|
||||
if to_device_dict:
|
||||
W.prnt("", "matrix: Megolm session missing for room.")
|
||||
message = MatrixToDeviceMessage(self.client, to_device_dict)
|
||||
self.send_queue.append(message)
|
||||
|
||||
message = MatrixEncryptedMessage(
|
||||
self.client,
|
||||
room.room_id,
|
||||
formatted_data,
|
||||
payload_dict
|
||||
)
|
||||
|
||||
self.send_queue.append(message)
|
||||
|
||||
except OlmTrustError:
|
||||
m = ("{prefix}matrix: Untrusted devices found in room, "
|
||||
"verification is needed before sending a message").format(
|
||||
prefix=W.prefix("error"))
|
||||
W.prnt(self.server_buffer, m)
|
||||
return
|
||||
|
||||
@encrypt_enabled
|
||||
def upload_keys(self, device_keys=False, one_time_keys=False):
|
||||
keys = self.olm.account.identity_keys if device_keys else None
|
||||
|
||||
one_time_keys = (self.olm.account.one_time_keys["curve25519"] if
|
||||
one_time_keys else None)
|
||||
|
||||
message = MatrixKeyUploadMessage(self.client, self.user_id,
|
||||
self.device_id, self.olm,
|
||||
keys, one_time_keys)
|
||||
self.send_queue.append(message)
|
||||
|
||||
@encrypt_enabled
|
||||
def check_one_time_keys(self, key_count):
|
||||
max_keys = self.olm.account.max_one_time_keys
|
||||
|
||||
key_count = (max_keys / 2) - key_count
|
||||
|
||||
if key_count <= 0:
|
||||
return
|
||||
|
||||
self.olm.account.generate_one_time_keys(key_count)
|
||||
self.upload_keys(device_keys=False, one_time_keys=True)
|
||||
|
||||
@encrypt_enabled
|
||||
def query_keys(self):
|
||||
users = []
|
||||
|
||||
for room_buffer in self.room_buffers.values():
|
||||
if not room_buffer.room.encrypted:
|
||||
continue
|
||||
users += list(room_buffer.room.users)
|
||||
|
||||
if not users:
|
||||
return
|
||||
|
||||
message = MatrixKeyQueryMessage(self.client, users)
|
||||
self.send_queue.append(message)
|
||||
request = self.client.sync()
|
||||
self.send_or_queue(request)
|
||||
|
||||
def login(self):
|
||||
# type: (MatrixServer) -> None
|
||||
message = MatrixLoginMessage(self.client, self.user, self.password,
|
||||
self.device_name, self.device_id)
|
||||
self.send_or_queue(message)
|
||||
# type: () -> None
|
||||
request = self.client.login(self.password)
|
||||
self.send_or_queue(request)
|
||||
|
||||
msg = "{prefix}matrix: Logging in...".format(prefix=W.prefix("network"))
|
||||
|
||||
|
@ -698,92 +474,18 @@ class MatrixServer:
|
|||
# self.check_one_time_keys(response.one_time_key_count)
|
||||
# self.handle_events()
|
||||
|
||||
def handle_matrix_response(self, response):
|
||||
if isinstance(response, MatrixSendEvent):
|
||||
room_buffer = self.find_room_from_id(response.room_id)
|
||||
self.handle_own_messages(room_buffer, response.message)
|
||||
|
||||
elif isinstance(response, MatrixBacklogEvent):
|
||||
room_buffer = self.find_room_from_id(response.room_id)
|
||||
room_buffer.handle_backlog(response.events)
|
||||
W.bar_item_update("buffer_modes")
|
||||
|
||||
elif isinstance(response, MatrixErrorEvent):
|
||||
self._handle_erorr_response(response)
|
||||
|
||||
def nio_receive(self):
|
||||
response = self.nio_client.next_response()
|
||||
def handle_response(self, response):
|
||||
# type: (MatrixMessage) -> None
|
||||
|
||||
if isinstance(response, LoginResponse):
|
||||
self._handle_login(response)
|
||||
elif isinstance(response, SyncRepsponse):
|
||||
self._handle_sync(response)
|
||||
|
||||
def nio_parse_response(self, response):
|
||||
if isinstance(response, MatrixLoginMessage):
|
||||
self.nio_client.receive("login", response.response.body)
|
||||
elif isinstance(response, MatrixSyncMessage):
|
||||
self.nio_client.receive("sync", response.response.body)
|
||||
|
||||
self.nio_receive()
|
||||
|
||||
return
|
||||
|
||||
def handle_response(self, message):
|
||||
# type: (MatrixMessage) -> None
|
||||
|
||||
assert message.response
|
||||
|
||||
if ('content-type' in message.response.headers and
|
||||
message.response.headers['content-type'] == 'application/json'):
|
||||
|
||||
if isinstance(message, (MatrixLoginMessage, MatrixSyncMessage)):
|
||||
self.nio_parse_response(message)
|
||||
|
||||
else:
|
||||
ret, error = message.decode_body(self)
|
||||
|
||||
if not ret:
|
||||
message = ("{prefix}matrix: Error decoding json response"
|
||||
" from server: {error}").format(
|
||||
prefix=W.prefix("error"), error=error)
|
||||
W.prnt(self.server_buffer, message)
|
||||
return
|
||||
|
||||
event = message.event
|
||||
self.handle_matrix_response(event)
|
||||
else:
|
||||
status_code = message.response.status
|
||||
if status_code == 504:
|
||||
if isinstance(message, API.MatrixSyncMessage):
|
||||
self.sync()
|
||||
else:
|
||||
self._print_message_error(message)
|
||||
else:
|
||||
self._print_message_error(message)
|
||||
|
||||
creation_date = datetime.datetime.fromtimestamp(message.creation_time)
|
||||
done_time = time.time()
|
||||
info_message = (
|
||||
"Message of type {t} created at {c}."
|
||||
"\nMessage lifetime information:"
|
||||
"\n Send delay: {s} ms"
|
||||
"\n Receive delay: {r} ms"
|
||||
"\n Handling time: {h} ms"
|
||||
"\n Total time: {total} ms").format(
|
||||
t=message.__class__.__name__,
|
||||
c=creation_date,
|
||||
s=(message.send_time - message.creation_time) * 1000,
|
||||
r=(message.receive_time - message.send_time) * 1000,
|
||||
h=(done_time - message.receive_time) * 1000,
|
||||
total=(done_time - message.creation_time) * 1000,
|
||||
)
|
||||
prnt_debug(DebugType.TIMING, self, info_message)
|
||||
|
||||
return
|
||||
|
||||
def create_room_buffer(self, room_id):
|
||||
room = self.nio_client.rooms[room_id]
|
||||
room = self.client.rooms[room_id]
|
||||
buf = RoomBuffer(room, self.name)
|
||||
# TODO this should turned into a propper class
|
||||
self.room_buffers[room_id] = buf
|
||||
|
@ -867,31 +569,31 @@ def matrix_timer_cb(server_name, remaining_calls):
|
|||
if not server.connected:
|
||||
return W.WEECHAT_RC_OK
|
||||
|
||||
# check lag, disconnect if it's too big
|
||||
if server.receive_queue:
|
||||
message = server.receive_queue.popleft()
|
||||
server.lag = (current_time - message.send_time) * 1000
|
||||
server.receive_queue.appendleft(message)
|
||||
server.lag_done = False
|
||||
W.bar_item_update("lag")
|
||||
# # check lag, disconnect if it's too big
|
||||
# if server.receive_queue:
|
||||
# message = server.receive_queue.popleft()
|
||||
# server.lag = (current_time - message.send_time) * 1000
|
||||
# server.receive_queue.appendleft(message)
|
||||
# server.lag_done = False
|
||||
# W.bar_item_update("lag")
|
||||
|
||||
# TODO print out message, make timeout configurable
|
||||
if server.lag > 300000:
|
||||
server.disconnect()
|
||||
return W.WEECHAT_RC_OK
|
||||
# # TODO print out message, make timeout configurable
|
||||
# if server.lag > 300000:
|
||||
# server.disconnect()
|
||||
# return W.WEECHAT_RC_OK
|
||||
|
||||
while server.send_queue:
|
||||
message = server.send_queue.popleft()
|
||||
prnt_debug(
|
||||
DebugType.MESSAGING,
|
||||
server, ("Timer hook found message of type {t} in queue. Sending "
|
||||
"out.".format(t=message.__class__.__name__)))
|
||||
# while server.send_queue:
|
||||
# message = server.send_queue.popleft()
|
||||
# prnt_debug(
|
||||
# DebugType.MESSAGING,
|
||||
# server, ("Timer hook found message of type {t} in queue. Sending "
|
||||
# "out.".format(t=message.__class__.__name__)))
|
||||
|
||||
if not server.send(message):
|
||||
# We got an error while sending the last message return the message
|
||||
# to the queue and exit the loop
|
||||
server.send_queue.appendleft(message)
|
||||
break
|
||||
# if not server.send(message):
|
||||
# # We got an error while sending the last message return the message
|
||||
# # to the queue and exit the loop
|
||||
# server.send_queue.appendleft(message)
|
||||
# break
|
||||
|
||||
if not server.next_batch:
|
||||
return W.WEECHAT_RC_OK
|
||||
|
@ -904,7 +606,6 @@ def matrix_timer_cb(server_name, remaining_calls):
|
|||
"{prefix}matrix: Querying user devices.".format(
|
||||
prefix=W.prefix("networ")))
|
||||
|
||||
server.query_keys()
|
||||
server.device_check_timestamp = current_time
|
||||
|
||||
return W.WEECHAT_RC_OK
|
||||
|
|
Loading…
Reference in a new issue