223 lines
6.7 KiB
Python
223 lines
6.7 KiB
Python
from flask import Flask, jsonify, Response, request, send_file, abort, Blueprint
|
|
from config import *
|
|
import globals
|
|
import json
|
|
import time
|
|
import os
|
|
|
|
server = Blueprint('matrix_server', __name__)
|
|
|
|
@server.route('/_matrix/federation/v1/version')
|
|
def version():
|
|
return jsonify({"server": {"version": globals.vona_version,"name": "Vona"}})
|
|
|
|
@server.route('/_matrix/key/v2/server')
|
|
def keys():
|
|
return jsonify(globals.sign_json({
|
|
"old_verify_keys": {},
|
|
"server_name": server_name,
|
|
"valid_until_ts": int(time.time() * 1000 + 604800000),
|
|
"verify_keys": {
|
|
f"ed25519:{signing_key.split()[1]}": {
|
|
"key": globals.pubkey()
|
|
}
|
|
}
|
|
}))
|
|
|
|
@server.route('/_matrix/federation/v1/query/directory')
|
|
def room_query():
|
|
return jsonify({
|
|
"room_id": room_dir_room['chunk'][0]['room_id'],
|
|
"servers": [server_name]
|
|
})
|
|
|
|
@server.route('/_matrix/federation/v1/media/download/<media_id>')
|
|
def download_media(media_id):
|
|
# Auth media requires this to be
|
|
# multipart despite not even using
|
|
# it for anything. Minor annoyance.
|
|
|
|
with open(cat, 'rb') as img_file:
|
|
image_data = img_file.read()
|
|
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
|
|
response_body = (
|
|
f'--{boundary}\r\n'
|
|
f'Content-Type: application/json\r\n\r\n'
|
|
f'{{}}\r\n'
|
|
f'--{boundary}\r\n'
|
|
f'Content-Type: image/jpeg\r\n'
|
|
f'Content-Disposition: attachment; filename="cat.jpg"\r\n\r\n'
|
|
).encode() + image_data + f'\r\n--{boundary}--\r\n'.encode()
|
|
|
|
response = Response(response_body, content_type=f'multipart/mixed; boundary={boundary}')
|
|
response.status_code = 200
|
|
|
|
return response
|
|
|
|
@server.route('/_matrix/federation/v1/media/thumbnail/<media_id>')
|
|
def thumbnail_media(media_id):
|
|
return jsonify({"errcode": "M_TOO_CUTE","error": "Cat is too cute to thumbnail"}), 403
|
|
|
|
@server.route('/_matrix/federation/v1/send_join/<roomId>/<eventId>', methods=['PUT'])
|
|
def send_join_v1(roomId, eventId):
|
|
abort(500)
|
|
|
|
@server.route('/_matrix/federation/v2/send_join/<roomId>/<eventId>', methods=['PUT'])
|
|
def send_join_v2(roomId, eventId):
|
|
abort(500)
|
|
|
|
@server.route('/_matrix/federation/v1/make_join/<roomId>/<userId>')
|
|
def make_join(roomId, userId):
|
|
return jsonify({
|
|
"event": {
|
|
"content": {
|
|
"join_authorised_via_users_server": f"@vona:{server_name}",
|
|
"membership": "join"
|
|
},
|
|
"origin": server_name,
|
|
"origin_server_ts": str(the_funny_number),
|
|
"room_id": roomId,
|
|
"sender": userId,
|
|
"state_key": userId,
|
|
"type": "m.room.member"
|
|
},
|
|
"room_version": "2"
|
|
})
|
|
|
|
@server.route('/_matrix/federation/v1/publicRooms', methods=['POST', 'GET'])
|
|
def room_directory():
|
|
return jsonify(room_dir_room)
|
|
|
|
|
|
# https://spec.matrix.org/v1.15/server-server-api/#transactions
|
|
@server.route('/_matrix/federation/v1/send/<txnId>', methods=['PUT'])
|
|
def federation_send(txnId):
|
|
# This only works for v2 rooms, but anything
|
|
# bigger than v2 isn't real, so not a problem
|
|
|
|
# We will need to implement a way to store every
|
|
# event we need if we want to send events in the
|
|
# future. We don't send events currently, however.
|
|
|
|
data = request.data.decode('utf-8')
|
|
parsed_data = json.loads(data)
|
|
response = {"pdus": {}}
|
|
|
|
if 'pdus' in parsed_data and parsed_data['pdus']:
|
|
for pdu in parsed_data['pdus']:
|
|
if 'hashes' in pdu and 'sha256' in pdu['hashes']:
|
|
pdu_hash = pdu['hashes']['sha256']
|
|
response_key = f"${pdu_hash}:{parsed_data['origin']}"
|
|
response["pdus"][response_key] = {}
|
|
|
|
return jsonify(response)
|
|
|
|
@server.route('/_matrix/federation/v1/query/profile')
|
|
def user_profile():
|
|
field = request.args.get('field')
|
|
if field:
|
|
if field == 'avatar_url':
|
|
return jsonify({"avatar_url":f"mxc://{server_name}/cat"})
|
|
elif field == 'displayname':
|
|
return jsonify({"displayname":"Vona"})
|
|
else:
|
|
return jsonify({"errcode": "M_NOT_FOUND","error": "The requested profile key does not exist."}), 404
|
|
|
|
return jsonify({"avatar_url": f"mxc://{server_name}/cat","displayname": "Vona"})
|
|
|
|
@server.route('/_matrix/federation/v1/user/devices/<user>')
|
|
def user_devices(user):
|
|
return jsonify({
|
|
"devices": [],
|
|
"stream_id": the_funny_number,
|
|
"user_id": f"@vona:{server_name}"
|
|
})
|
|
|
|
@server.route('/_matrix/federation/v1/user/keys/query', methods=['POST'])
|
|
def user_keys():
|
|
return jsonify({"device_keys":{f"@vona:{server_name}":{}}})
|
|
|
|
|
|
# https://spec.matrix.org/v1.15/server-server-api/#inviting-to-a-room
|
|
@server.route('/_matrix/federation/v2/invite/<room>/<txnId>', methods=['PUT'])
|
|
def invite_user_v2(room, txnId):
|
|
return invite_user(request.data.decode('utf-8'), 2)
|
|
|
|
@server.route('/_matrix/federation/v1/invite/<room>/<txnId>', methods=['PUT'])
|
|
def invite_user_v1(room, txnId):
|
|
return invite_user(request.data.decode('utf-8'), 1)
|
|
|
|
def invite_user(data, endpVer):
|
|
try:
|
|
invite_data = json.loads(data)
|
|
except:
|
|
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}), 400
|
|
|
|
if "event" in invite_data:
|
|
if "room_version" in invite_data:
|
|
if invite_data["room_version"] != "2":
|
|
return jsonify({
|
|
"errcode": "M_INCOMPATIBLE_ROOM_VERSION",
|
|
"error": "Vona only supports room version 2.",
|
|
"room_version": invite_data["room_version"]
|
|
}), 400
|
|
else:
|
|
if "content" in invite_data["event"]:
|
|
if "membership" in invite_data["event"]["content"] and invite_data["event"]["content"]["membership"] == "invite":
|
|
if endpVer == 1:
|
|
return jsonify([
|
|
200,
|
|
{"event": globals.sign_json(invite_data["event"])}
|
|
])
|
|
else:
|
|
return jsonify({"event": globals.sign_json(invite_data["event"]), "room_version": "2"})
|
|
|
|
return jsonify({
|
|
"errcode": "M_FORBIDDEN",
|
|
"error": "Invalid invitation PDU"
|
|
}), 403
|
|
|
|
@server.route('/_matrix/federation/v1/hierarchy/<roomId>')
|
|
def space_hierachy(roomId):
|
|
room = room_dir_room['chunk'][0]
|
|
return jsonify({
|
|
"children": [{
|
|
"avatar_url": room['avatar_url'],
|
|
"children_state": [{
|
|
"content": {"via": [server_name]},
|
|
"origin_server_ts": the_funny_number,
|
|
"sender": f"@vona:{server_name}",
|
|
"state_key": room['room_id'],
|
|
"type": "m.space.child"
|
|
}],
|
|
"guest_can_join": room['guest_can_join'],
|
|
"join_rule": room['join_rule'],
|
|
"name": room['name'],
|
|
"num_joined_members": room['num_joined_members'],
|
|
"room_id": room['room_id'],
|
|
"room_type": room['room_type'],
|
|
"topic": room['topic'],
|
|
"world_readable": room['world_readable']
|
|
}],
|
|
"inaccessible_children": [],
|
|
"room": {
|
|
"allowed_room_ids": [],
|
|
"avatar_url": room['avatar_url'],
|
|
"children_state": [{
|
|
"content": {"via": [server_name]},
|
|
"origin_server_ts": the_funny_number,
|
|
"sender": f"@vona:{server_name}",
|
|
"state_key": room['room_id'],
|
|
"type": "m.space.child"
|
|
}],
|
|
"guest_can_join": room['guest_can_join'],
|
|
"join_rule": room['join_rule'],
|
|
"name": room['name'],
|
|
"num_joined_members": room['num_joined_members'],
|
|
"room_id": room['room_id'],
|
|
"room_type": room['room_type'],
|
|
"topic": room['topic'],
|
|
"world_readable": room['world_readable']
|
|
}
|
|
})
|