Make Vona a Python module
This commit is contained in:
485
vona/federation/__init__.py
Normal file
485
vona/federation/__init__.py
Normal file
@@ -0,0 +1,485 @@
|
||||
from flask import jsonify, Response, request, send_file, abort, Blueprint
|
||||
from vona.config import *
|
||||
import vona.globals as globals
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
|
||||
server = Blueprint("federation", __name__)
|
||||
|
||||
|
||||
def send_join(request, roomId) -> dict:
|
||||
event_chain = []
|
||||
event_hashes = []
|
||||
|
||||
event_ids = [
|
||||
globals.make_event_id(seed=f"1_{roomId}"),
|
||||
globals.make_event_id(seed=f"2_{roomId}"),
|
||||
globals.make_event_id(seed=f"3_{roomId}"),
|
||||
globals.make_event_id(seed=f"4_{roomId}"),
|
||||
globals.make_event_id(seed=f"5_{roomId}"),
|
||||
globals.make_event_id(seed=f"6_{roomId}"),
|
||||
]
|
||||
|
||||
create_event = {
|
||||
"content": {
|
||||
"m.federate": True,
|
||||
"creator": f"@vona:{server_name}",
|
||||
"room_version": "2"
|
||||
},
|
||||
"event_id": event_ids[0],
|
||||
"origin_server_ts": 1,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"depth": 1,
|
||||
"type": "m.room.create",
|
||||
"auth_events": [],
|
||||
"prev_events": []
|
||||
}
|
||||
|
||||
screate_event = globals.hash_and_sign_event(create_event)
|
||||
event_chain.append(screate_event)
|
||||
|
||||
our_join = {
|
||||
"content": {
|
||||
"displayname": "Vona",
|
||||
"avatar_url": f"mxc://{server_name}/cat",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin_server_ts": 2,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": f"@vona:{server_name}",
|
||||
"type": "m.room.member",
|
||||
"event_id": event_ids[1],
|
||||
"room_id": roomId,
|
||||
"depth": 2,
|
||||
"auth_events": [[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
]],
|
||||
"prev_events": [[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
sour_join = globals.hash_and_sign_event(our_join)
|
||||
event_chain.append(sour_join)
|
||||
|
||||
pls = {
|
||||
"content": {
|
||||
"users": {
|
||||
f"@vona:{server_name}": "100"
|
||||
}
|
||||
},
|
||||
"origin_server_ts": 3,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
"event_id": event_ids[2],
|
||||
"depth": 3,
|
||||
"user_id": f"@vona:{server_name}",
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
spls = globals.hash_and_sign_event(pls)
|
||||
event_chain.append(spls)
|
||||
|
||||
join_rule = {
|
||||
"content": {
|
||||
"join_rule": "public"
|
||||
},
|
||||
"origin_server_ts": 4,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules",
|
||||
"event_id": event_ids[3],
|
||||
"room_id": roomId,
|
||||
"depth": 4,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
sjoin_rule = globals.hash_and_sign_event(join_rule)
|
||||
event_chain.append(sjoin_rule)
|
||||
|
||||
guest_access = {
|
||||
"content": {
|
||||
"guest_access": "forbidden"
|
||||
},
|
||||
"origin_server_ts": 5,
|
||||
"depth": 5,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.guest_access",
|
||||
"event_id": event_ids[4],
|
||||
"room_id": roomId,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
sguest_access = globals.hash_and_sign_event(guest_access)
|
||||
event_chain.append(sguest_access)
|
||||
|
||||
history = {
|
||||
"content": {
|
||||
"history_visibility": "shared"
|
||||
},
|
||||
"type": "m.room.history_visibility",
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"origin_server_ts": 6,
|
||||
"depth": 6,
|
||||
"event_id": event_ids[5],
|
||||
"room_id": roomId,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
sguest_access["event_id"],
|
||||
sguest_access["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
shistory = globals.hash_and_sign_event(history)
|
||||
event_chain.append(shistory)
|
||||
|
||||
remote_join = request.get_json()
|
||||
|
||||
response = {
|
||||
"auth_chain": event_chain,
|
||||
"event": remote_join,
|
||||
"members_omitted": False,
|
||||
"servers_in_room": [server_name],
|
||||
"state": event_chain
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/version")
|
||||
async def version():
|
||||
return jsonify({
|
||||
"server": {
|
||||
"version": globals.vona_version,
|
||||
"name": "Vona"
|
||||
}
|
||||
})
|
||||
|
||||
@server.route("/_matrix/key/v2/server")
|
||||
async 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")
|
||||
async def room_query():
|
||||
return jsonify({
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"servers": [server_name]
|
||||
})
|
||||
|
||||
@server.route("/_matrix/federation/v1/media/download/<media_id>")
|
||||
async 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>")
|
||||
async def thumbnail_media(media_id):
|
||||
return jsonify({
|
||||
"errcode": "M_TOO_CUTE",
|
||||
"error": "Cat is too cute to thumbnail"
|
||||
}), 418
|
||||
|
||||
@server.route("/_matrix/federation/v1/send_join/<roomId>/<eventId>", methods=["PUT"])
|
||||
async def send_join_v1(roomId, eventId):
|
||||
return jsonify([200, send_join(request, roomId)])
|
||||
|
||||
@server.route("/_matrix/federation/v2/send_join/<roomId>/<eventId>", methods=["PUT"])
|
||||
async def send_join_v2(roomId, eventId):
|
||||
return jsonify(send_join(request, roomId))
|
||||
|
||||
@server.route("/_matrix/federation/v1/make_join/<roomId>/<userId>")
|
||||
async def make_join(roomId, userId):
|
||||
def not_invited():
|
||||
return jsonify({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "You are not invited to this room."
|
||||
}), 403
|
||||
|
||||
try:
|
||||
if roomId.split(":")[1] != server_name:
|
||||
return not_invited()
|
||||
except:
|
||||
return not_invited()
|
||||
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
state = send_join(
|
||||
request=bullshit,
|
||||
roomId=roomId
|
||||
)["state"]
|
||||
|
||||
join = {
|
||||
"content": {
|
||||
"join_authorised_via_users_server": f"@vona:{server_name}",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin": server_name,
|
||||
"origin_server_ts": 7,
|
||||
"room_id": roomId,
|
||||
"sender": userId,
|
||||
"state_key": userId,
|
||||
"type": "m.room.member",
|
||||
"depth": 7
|
||||
}
|
||||
|
||||
join["auth_events"] = [
|
||||
[
|
||||
state[0]["event_id"],
|
||||
state[0]["hashes"]
|
||||
],
|
||||
[
|
||||
state[2]["event_id"],
|
||||
state[2]["hashes"]
|
||||
],
|
||||
[
|
||||
state[3]["event_id"],
|
||||
state[3]["hashes"]
|
||||
]
|
||||
]
|
||||
|
||||
join["prev_events"] = [[
|
||||
state[5]["event_id"],
|
||||
state[5]["hashes"]
|
||||
]]
|
||||
|
||||
return jsonify({
|
||||
"event": globals.hash_and_sign_event(join),
|
||||
"room_version": "2"
|
||||
})
|
||||
|
||||
@server.route("/_matrix/federation/v1/publicRooms", methods=["POST", "GET"])
|
||||
async def room_directory():
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/server-server-api/#transactions
|
||||
@server.route("/_matrix/federation/v1/send/<txnId>", methods=["PUT"])
|
||||
async def receive_txn(txnId):
|
||||
# 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.
|
||||
|
||||
# These events are:
|
||||
# - m.room.create
|
||||
# - m.room.member
|
||||
# - m.room.join_rules
|
||||
# - m.room.power_levels
|
||||
# - m.room.third_party_invite
|
||||
#
|
||||
# As per https://spec.matrix.org/latest/rooms/v2/#authorization-rules
|
||||
|
||||
|
||||
data = request.data.decode("utf-8")
|
||||
parsed_data = json.loads(data)
|
||||
response = {"pdus": {}}
|
||||
|
||||
if "pdus" in parsed_data:
|
||||
for pdu in parsed_data["pdus"]:
|
||||
if "event_id" in pdu:
|
||||
response["pdus"][pdu["event_id"]] = {}
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/query/profile")
|
||||
async 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"})
|
||||
|
||||
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"})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/server-server-api/#device-management
|
||||
@server.route(f"/_matrix/federation/v1/user/devices/@/:{server_name}")
|
||||
@server.route("/_matrix/federation/v1/user/devices/<user>")
|
||||
async 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"])
|
||||
async def user_keys():
|
||||
try:
|
||||
users = request.json["device_keys"]
|
||||
except:
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "User does not exist"
|
||||
}), 404
|
||||
|
||||
return jsonify({"device_keys": users})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v2/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v2(room, txnId):
|
||||
return invite_user(request.data)
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v1(room, txnId):
|
||||
return [200, invite_user(request.data)]
|
||||
|
||||
|
||||
def invite_user(data):
|
||||
try:
|
||||
invite_data = json.loads(data)
|
||||
except:
|
||||
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}),
|
||||
|
||||
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
|
||||
|
||||
event = invite_data.get("event", {})
|
||||
content = event.get("content", {})
|
||||
|
||||
# NOTE to crispycat: I know you loooooove this syntax
|
||||
if (
|
||||
"content" in event
|
||||
and "membership" in content
|
||||
and "state_key" in event
|
||||
and "room_id" in event
|
||||
and content["membership"] == "invite"
|
||||
and event["state_key"] == f"@vona:{server_name}"
|
||||
):
|
||||
return jsonify({"event": globals.sign_json_without_discard(invite_data["event"]), "room_version": "2"})
|
||||
|
||||
|
||||
return jsonify({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid invitation PDU"
|
||||
}), 403
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/hierarchy/<roomId>")
|
||||
async def space_hierachy(roomId):
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Room does not exist."
|
||||
})
|
||||
Reference in New Issue
Block a user