diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 2eca8ba..0000000 --- a/TODO.md +++ /dev/null @@ -1,18 +0,0 @@ -- add the entirety of Synapse Admin (higher priority to lower priority) - - [X] [Users](https://element-hq.github.io/synapse/latest/admin_api/user_admin_api.html) - - [X] [Server Version](https://element-hq.github.io/synapse/latest/admin_api/version_api.html) - - [X] [Manipulate room membership](https://element-hq.github.io/synapse/latest/admin_api/room_membership.html) - - [X] [Account validity](https://element-hq.github.io/synapse/latest/admin_api/account_validity.html) - - [X] [Server notices](https://element-hq.github.io/synapse/latest/admin_api/server_notices.html) - - [X] [Experimental features](https://element-hq.github.io/synapse/latest/admin_api/experimental_features.html) - - [X] [Register users](https://element-hq.github.io/synapse/latest/admin_api/register_api.html) - - [X] [Purge history](https://element-hq.github.io/synapse/latest/admin_api/purge_history_api.html) - - [X] [Media](https://element-hq.github.io/synapse/latest/admin_api/media_admin_api.html) - - [X] [Statistics](https://element-hq.github.io/synapse/latest/admin_api/statistics.html) - - [X] [Background updates](https://element-hq.github.io/synapse/latest/usage/administration/admin_api/background_updates.html) - - [X] [Reported events](https://element-hq.github.io/synapse/latest/admin_api/event_reports.html) - - [ ] [Federation](https://element-hq.github.io/synapse/latest/usage/administration/admin_api/federation.html) - - [ ] [Registration tokens](https://element-hq.github.io/synapse/latest/usage/administration/admin_api/registration_tokens.html) - - [ ] [Rooms](https://element-hq.github.io/synapse/latest/admin_api/rooms.html) - -- implement `/_matrix/client/v1/auth_metadata` diff --git a/src/appservice.py b/src/appservice.py index d745b88..0cdc1e0 100644 --- a/src/appservice.py +++ b/src/appservice.py @@ -2,7 +2,7 @@ from flask import Blueprint, jsonify from config import the_funny_number import asyncio -apps = Blueprint("matrix_appservice", __name__) +apps = Blueprint("appservice", __name__) # This implements both being a homeserver and # being an appservice. Why? Maximum carnage. @@ -10,59 +10,26 @@ apps = Blueprint("matrix_appservice", __name__) # Endpoints invoked by the homeserver are put # lower than ones invoked by the appservice. - -# https://spec.matrix.org/v1.16/application-service-api/#pinging -# https://spec.matrix.org/v1.16/application-service-api/#pinging-1 @apps.route("/_matrix/client/v1/appservice//ping", methods=["POST"]) async def homeserver_ping(app): # Sleeping here makes it more realistic await asyncio.sleep(the_funny_number / 1000) return jsonify({"duration_ms": the_funny_number}) + +@apps.route("/_matrix/client/v3/directory/list/appservice//", methods=["PUT"]) @apps.route("/_matrix/app/v1/ping", methods=["POST"]) -async def app_ping(): +@apps.route("/_matrix/app/v1/transactions/", methods=["PUT"]) +@apps.route("/_matrix/app/v1/thirdparty/protocol/") +@apps.route("/_matrix/app/v1/rooms/") +@apps.route("/_matrix/app/v1/users/") +async def empty_dict(**kwargs): return jsonify({}) -# https://spec.matrix.org/v1.16/application-service-api/#querying -@apps.route('/_matrix/app/v1/users/') -async def query_user(user): - return jsonify({}) - -@apps.route('/_matrix/app/v1/rooms/') -async def query_room(room): - return jsonify({}) - - -# https://spec.matrix.org/v1.16/application-service-api/#pushing-events -@apps.route('/_matrix/app/v1/transactions/', methods=['PUT']) -async def send_transaction(txnId): - return jsonify({}) - - -# https://spec.matrix.org/v1.16/application-service-api/#third-party-networks -@apps.route('/_matrix/app/v1/thirdparty/location') -async def location(): +@apps.route("/_matrix/app/v1/thirdparty/location") +@apps.route("/_matrix/app/v1/thirdparty/location/") +@apps.route("/_matrix/app/v1/thirdparty/user") +@apps.route("/_matrix/app/v1/thirdparty/user/") +async def empty_array(**kwargs): return jsonify([]) - -@apps.route('/_matrix/app/v1/thirdparty/location/') -async def location_from_protocol(protocol): - return jsonify([]) - -@apps.route('/_matrix/app/v1/thirdparty/protocol/') -async def protocol_info(protocol): - return jsonify({}) - -@apps.route('/_matrix/app/v1/thirdparty/user') -async def thirdparty_user(): - return jsonify([]) - -@apps.route('/_matrix/app/v1/thirdparty/user/') -async def user_from_protocol(protocol): - return jsonify([]) - - -# https://spec.matrix.org/v1.16/application-service-api/#published-room-directories -@apps.route('/_matrix/client/v3/directory/list/appservice//', methods=['PUT']) -async def publish_room(net, room): - return jsonify({}) diff --git a/src/c2s.py b/src/c2s.py index 35ecc3d..d14c77e 100644 --- a/src/c2s.py +++ b/src/c2s.py @@ -6,27 +6,100 @@ import asyncio import random import os -client = Blueprint('matrix_client', __name__) +client = Blueprint("c2s", __name__) + + +@client.route("/_matrix/client/v3/account/password", methods=["POST"]) +@client.route('/_matrix/client/v3/user//account_data/', methods=['GET', 'PUT']) +@client.route('/_matrix/client/r0/user//account_data/', methods=['GET', 'PUT']) +@client.route('/_matrix/client/v3/sendToDevice//', methods=["PUT"]) +@client.route('/_matrix/media/v3/upload//', methods=["PUT"]) +@client.route('/_matrix/client/v3/thirdparty/protocols') +@client.route('/_matrix/client/r0/thirdparty/protocols') +@client.route('/_matrix/client/v3/delete_devices', methods=['POST']) +@client.route('/_matrix/client/r0/delete_devices', methods=['POST']) +@client.route('/_matrix/client/v3/logout/all', methods=['POST']) +@client.route('/_matrix/client/v3/logout', methods=['POST']) +@client.route('/_matrix/client/v3/rooms//invite', methods=['POST']) +@client.route('/_matrix/client/v3/rooms//leave', methods=['POST']) +@client.route('/_matrix/client/r0/rooms//leave', methods=['POST']) +@client.route('/_matrix/client/v3/rooms//read_markers', methods=['POST']) +@client.route('/_matrix/client/r0/rooms//read_markers', methods=['POST']) +@client.route('/_matrix/client/v3/keys/device_signing/upload', methods=['POST']) +@client.route("/_matrix/client/v3/rooms//receipt//", methods=['POST']) +@client.route("/_matrix/client/v3/users//report", methods=["POST"]) +@client.route('/_matrix/client/v3/voip/turnServer') +@client.route('/_matrix/client/r0/voip/turnServer') +@client.route("/_matrix/client/v3/rooms//report/") +@client.route("/_matrix/client/v3/rooms//report") +@client.route("/_matrix/client/v3/users//report") +async def empty_response(**kwargs): + return jsonify({}) + @client.route('/_matrix/client/versions') -def client_version(): +async def spec_versions(): return jsonify({ - "versions": [ - "r0.6.0", - "v1.16" - ], - "unstable_features": {"uk.tcpip.msc4133": True} + "versions": ( + ["r0.0.0"] + [f"r0.{i}.0" for i in range(1, 7)] + + ["r0.6.1"] + [f"v1.{i}" for i in range(1, 17)] + ), + "unstable_features": { + "uk.half-shot.msc2666": True, + "uk.timedout.msc4323": True + } }) -@client.route('/_matrix/client/v3/admin/whois/') -def admin_whois(userId): - if userId.startswith('@'): - return jsonify({"errcode":"M_MISSING_TOKEN","error":"Missing access token"}) - return jsonify({"errcode":"M_INVALID_PARAM","error":"Expected UserID string to start with '@'"}) +@client.route("/_matrix/client/v3/admin/whois/") +@client.route("/_matrix/client/r0/admin/whois/") +async def whois(user): + if userId.startswith("@"): + return jsonify({ + "devices": { + "": { + "sessions": [{ + "connections": [{ + "ip": "127.0.0.1", + "last_seen": the_funny_number, + "user_agent": f"Vona/{globals.vona_version}" + }] + }] + } + }, + "user_id": user + }) + + return jsonify({ + "errcode": "M_INVALID_PARAM", + "error": "Expected UserID string to start with '@'" + }) + +@client.route("/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/", methods=["GET", "PUT"]) +@client.route("/_matrix/client/v1/admin/suspend/", methods=["GET", "PUT"]) +async def suspend(user): + if request.method == "PUT": + req = request.get_json() + if req and "suspended" in req: + return jsonify({"suspended": req["suspended"]}) + + return jsonify({"suspended": True}) + + +@client.route("/_matrix/client/unstable/uk.timedout.msc4323/admin/lock/", methods=["GET", "PUT"]) +@client.route("/_matrix/client/v1/admin/lock/", methods=["GET", "PUT"]) +async def lock(user): + if request.method == "PUT": + req = request.get_json() + if req and "locked" in req: + return jsonify({"locked": req["locked"]}) + + return jsonify({"locked": True}) + @client.route('/_matrix/client/v3/rooms//members') -def room_member_count(roomId): +@client.route('/_matrix/client/r0/rooms//members') +async def room_member_count(roomId): return jsonify({ "chunk": [{ "content": { @@ -44,15 +117,19 @@ def room_member_count(roomId): }] }) -@client.route('/_matrix/client/v3/account/whoami') -def whoami(): + +@client.route("/_matrix/client/v3/account/whoami") +async def whoami(): return jsonify({ "device_id": "VVOONNAA", "user_id": f"@vona:{server_name}" }) + @client.route('/_matrix/client/v3/register', methods=['POST']) -def register(): +@client.route('/_matrix/client/v1/register', methods=['POST']) +@client.route('/_matrix/client/r0/register', methods=['POST']) +async def register(): if users_can_register: try: data = request.get_json() @@ -79,8 +156,10 @@ def register(): "error": "M_FORBIDDEN: Registration has been disabled." }), 403 + +@client.route('/_matrix/client/r0/login', methods=['GET', 'POST']) @client.route('/_matrix/client/v3/login', methods=['GET', 'POST']) -def login(): +async def login(): if request.method == 'GET': return jsonify({ "flows": [ @@ -100,15 +179,17 @@ def login(): }) @client.route('/_matrix/client/v3/account/password/email/requestToken', methods=['POST']) -def pswd_reset(): +async def pswd_reset(): return jsonify({"errcode":"M_THREEPID_NOT_FOUND","error":"Email not found"}), 400 @client.route('/_matrix/client/v3/keys/upload', methods=['POST']) -def key_upload(): +async def key_upload(): return jsonify({"one_time_key_counts":{"signed_curve25519":50}}) -@client.route('/_matrix/client/v3/room_keys/version', methods=['POST', 'GET']) -def roomkeys(): + +@client.route("/_matrix/client/v3/room_keys/version", methods=['POST', 'GET']) +@client.route("/_matrix/client/unstable/room_keys/version", methods=['POST', 'GET']) +async def room_keys(): if request.method == 'POST': return jsonify({"version": str(the_funny_number)}) @@ -128,37 +209,82 @@ def roomkeys(): "version": str(the_funny_number) }) -@client.route('/_matrix/client/v3/capabilities') -def capabilities(): - return jsonify({"capabilities":{"m.room_versions":{"default":"2","available":{"1":"stable","2":"stable","3":"stable","4":"stable","5":"stable","6":"stable","7":"stable","8":"stable","1337":"stable","9":"stable","10":"stable","11":"stable","org.matrix.msc3757.10":"stable","org.matrix.msc3757.11":"stable"},"org.matrix.msc3244.room_capabilities":{"knock":{"preferred":"7","support":["7","8","9","10","11","org.matrix.msc3757.10","org.matrix.msc3757.11"]},"restricted":{"preferred":"9","support":["8","9","10","11","org.matrix.msc3757.10","org.matrix.msc3757.11"]}}},"m.change_password":{"enabled":True},"m.3pid_changes":{"enabled":True},"m.get_login_token":{"enabled":False},"m.profile_fields":{"enabled":True,"allowed":["*"]}}}) +@client.route("/_matrix/client/v3/capabilities") +@client.route("/_matrix/client/r0/capabilities") +async def capabilities(): + return jsonify({ + "capabilities": { + "m.room_versions": { + "default": "2", + "available": { + "1": "stable", + "2": "stable", + "3": "stable", + "4": "stable", + "5": "stable", + "6": "stable", + "7": "stable", + "8": "stable", + "9": "stable", + "10": "stable", + "11": "stable", + "12": "stable" + }, + "org.matrix.msc3244.room_capabilities": { + "knock": { + "preferred":"7", + "support": [ + "7","8","9","10","11","12" + ] + }, + "restricted": { + "preferred": "9", + "support": ["8","9","10","11","12"] + } + } + }, + "m.change_password": {"enabled": True}, + "m.3pid_changes": {"enabled": True}, + "m.get_login_token": {"enabled": False}, + "m.profile_fields": { + "enabled": True, + "allowed":["*"] + }, + "account_moderation": { + "suspend": True, + "lock": True + } + } + }) + + +@client.route('/_matrix/client/r0/pushrules/') @client.route('/_matrix/client/v3/pushrules/') -def pushrules(): +async def pushrules(): # TODO: Actually implement this - return jsonify({"errcode":"M_MISSING_TOKEN","error":"Missing access token"}) - -@client.route('/_matrix/client/v3/user//filter', methods=['POST']) -def filter(user): - return jsonify({"filter_id": "vvvooonnnaaa"}) - -@client.route('/_matrix/client/v3/user//filter/') -def filter_two(user, data): - return jsonify({"filter_id": "vvvooonnnaaa"}) - -@client.route('/_matrix/client/v3/rooms//invite', methods=['POST']) -def invite_room(room): return jsonify({}) -@client.route('/_matrix/client/v3/knock/', methods=['POST']) -def knock_room(room): - return jsonify({"errcode":"M_FORBIDDEN","error":"You are not allowed to knock on this room."}), 403 + +@client.route('/_matrix/client/v3/user//filter/') +@client.route('/_matrix/client/r0/user//filter/') +@client.route('/_matrix/client/v3/user//filter', methods=['POST']) +@client.route('/_matrix/client/r0/user//filter', methods=['POST']) +async def filter(**kwargs): + return jsonify({"filter_id": "vvvooonnnaaa"}) + @client.route('/_matrix/client/v3/join/', methods=['POST']) +@client.route('/_matrix/client/r0/join/', methods=['POST']) @client.route('/_matrix/client/v3/rooms//join', methods=['POST']) -def join_roomId(room): +@client.route('/_matrix/client/v3/knock/', methods=['POST']) +async def join(room): return jsonify({"room_id": room}) -@client.route('/_matrix/client/v3/sync') + +@client.route("/_matrix/client/v3/initialSync") +@client.route("/_matrix/client/v3/sync") +@client.route("/_matrix/client/r0/sync") async def sync(): class bullshit: def get_json(): @@ -183,7 +309,10 @@ async def sync(): return new_list room = globals.make_event_id().replace("$", "!") - old_room_state = send_join(bullshit, room)["state"] + old_room_state = send_join( + bullshit, + room + )["state"] room_state = await remove_keys( old_room_state, @@ -245,45 +374,64 @@ async def sync(): } }) + @client.route('/_matrix/client/v3/rooms//send//', methods=['POST', 'PUT']) -def send_message(room, eventType, txnId): +@client.route('/_matrix/client/r0/rooms//send//', methods=['POST', 'PUT']) +async def send_message(room, eventType, txnId): return jsonify({"event_id": globals.make_event_id()}), 200 + @client.route('/_matrix/client/v3/user_directory/search', methods=['POST']) -def user_directory(): - return jsonify({"limited":False,"results":[{"avatar_url":f"mxc://{server_name}/cat","display_name":"Vona","user_id":f"@vona:{server_name}"}]}) +async def user_directory(): + return jsonify({ + "limited": False, + "results": [{ + "avatar_url": f"mxc://{server_name}/cat", + "display_name": "Vona", + "user_id": f"@vona:{server_name}" + }] + }) + @client.route('/_matrix/client/v3/devices') -def devices(): - return jsonify({"devices":[{"device_id":"VVOONNAA","display_name":"Vona","last_seen_ip":"127.0.0.1","last_seen_ts":the_funny_number}]}) - -@client.route('/_matrix/client/v3/devices/', methods=['GET', 'PUT', 'DELETE']) -def get_device(): - if request.method == 'GET': - return jsonify({"device_id": "VVOONNAA","display_name": "Vona","last_seen_ip": "127.0.0.1","last_seen_ts": the_funny_number}) - - return jsonify({}) - -@client.route('/_matrix/client/v3/delete_devices', methods=['POST']) -@client.route('/_matrix/client/v3/logout/all', methods=['POST']) -@client.route('/_matrix/client/v3/logout', methods=['POST']) -def delete_devices(): - return jsonify({}) - -@client.route('/_matrix/client/v3/refresh', methods=['POST']) -def refresh(): - return jsonify({"errcode": "M_UNKNOWN_TOKEN","error": "Soft logged out","soft_logout": True}) - -@client.route('/_matrix/client/v3/voip/turnServer') -def turnserver(): +@client.route('/_matrix/client/r0/devices') +async def devices(): return jsonify({ - "errcode": "M_LIMIT_EXCEEDED", - "error": "Too many requests", - "retry_after_ms": the_funny_number - }), 429 + "devices": [{ + "device_id": "VVOONNAA", + "display_name": "Vona", + "last_seen_ip": "127.0.0.1", + "last_seen_ts": the_funny_number + }] + }) + + +@client.route('/_matrix/client/v3/devices/', methods=['GET', 'PUT', 'DELETE']) +@client.route('/_matrix/client/r0/devices/', methods=['GET', 'PUT', 'DELETE']) +async def get_device(device): + if request.method == "GET": + return jsonify({ + "device_id": device, + "display_name": "Vona", + "last_seen_ip": "127.0.0.1", + "last_seen_ts": the_funny_number + }) + + return jsonify({}) + + +@client.route("/_matrix/client/v3/refresh", methods=['POST']) +async def refresh(): + return jsonify({ + "access_token": "vona", + "expires_in_ms": the_funny_number * 1000, + "refresh_token": "vona" + }) + @client.route('/_matrix/client/unstable/im.nheko.summary/rooms//summary') @client.route('/_matrix/client/unstable/im.nheko.summary/summary/') +@client.route('/_matrix/client/v1/room_summary/') def unstable_room_summary(roomId): room = room_dir_room['chunk'][0] return jsonify({ @@ -300,59 +448,93 @@ def unstable_room_summary(roomId): "room_version": 2, }) -@client.route('/_matrix/client/v1/room_summary/') -def room_summary(roomId): - room = room_dir_room['chunk'][0] + +@client.route("/_matrix/client/v3/directory/room/", methods=["GET", "PUT", "DELETE"]) +@client.route("/_matrix/client/r0/directory/room/") +async def room_query(room): + if request.method == "GET": + return jsonify({ + "room_id": room_dir_room['chunk'][0]['room_id'], + "servers": [server_name] + }) + + return jsonify({}) + +@client.route("/_matrix/client/v3/rooms//aliases") +async def room_aliases(room): return jsonify({ - "room_id": room['room_id'], - "avatar_url": room['avatar_url'], - "guest_can_join": room['guest_can_join'], - "name": room['name'], - "num_joined_members": room['num_joined_members'], - "topic": room['topic'], - "world_readable": room['world_readable'], - "join_rule": room['join_rule'], - "room_type": room['room_type'], - "membership": "join", - "room_version": 2, + "aliases": [ + f"#vona:{server_name}" + ] }) -@client.route('/_matrix/client/r0/directory/room/') -def r0_room_query(roomId): - return jsonify({ - "room_id": room_dir_room['chunk'][0]['room_id'], - "servers": [server_name] - }) -@client.route('/_matrix/client/r0/directory/list/room/') -def r0_room_vis(roomId): +@client.route("/_matrix/client/v3/directory/list/room/", methods=["GET", "PUT"]) +@client.route('/_matrix/client/r0/directory/list/room/', methods=["GET", "PUT"]) +async def room_visibility(room): return jsonify({"visibility": "public"}) -@client.route('/_matrix/client/r0/publicRooms') -def r0_room_directory(): - return jsonify(room_dir_room) +@client.route("/_matrix/client/v3/search", methods=["POST"]) +async def search(): + room = globals.make_event_id().replace("$", "!") + event = globals.make_event_id() + + return jsonify({ + "search_categories": { + "room_events": { + "count": 1, + "groups": { + "room_id": { + room: { + "next_batch": "vona", + "order": 1, + "results": [event] + } + } + }, + "highlights": [], + "next_batch": "vona", + "results": [{ + "rank": the_funny_number, + "result": { + "content": { + "msgtype": "m.text", + "body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.", + "format": "org.matrix.custom.html", + "formatted_body": "Number 15: Burger King Foot Lettuce.
The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement "This is the lettuce you eat at Burger King.". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said "Oh, I know who that is, hes getting fired". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace." + }, + "event_id": event, + "origin_server_ts": the_funny_number, + "room_id": room, + "sender": f"@vona:{server_name}", + "type": "m.room.message" + } + }] + } + } + }) + @client.route('/_matrix/media/v1/thumbnail//') @client.route('/_matrix/client/v1/media/thumbnail//') @client.route('/_matrix/media/r0/thumbnail//') -def media(server, file): +@client.route('/_matrix/media/v3/thumbnail//') +@client.route('/_matrix/media/v3/download///') +@client.route('/_matrix/client/v1/media/download//') +@client.route('/_matrix/media/v3/download//') +@client.route('/_matrix/media/r0/download//') +async def media(**kwargs): return send_file(cat) -@client.route('/_matrix/client/r0/admin/whois/') -def whois(userId): - return jsonify({"user_id":f"@vona:{server_name}","devices":{"":{"sessions":[{"connections":[{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{the_funny_number}.0"},{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{the_funny_number}.0"}]}]}}}) @client.route('/_matrix/client/v3/register/available') @client.route('/_matrix/client/r0/register/available') -def username_available(): +async def username_available(): return jsonify({"available": True}) -@client.route('/_matrix/client/v3/thirdparty/protocols') -def thirdparty_protocols(): - return jsonify({}) @client.route('/_matrix/media/v3/preview_url') -def url_preview(): +async def url_preview(): return jsonify({ "matrix:image:size": 102400, "og:description": "look at this cool cat", @@ -364,49 +546,28 @@ def url_preview(): }) @client.route('/_matrix/client/v1/media/preview_url') -def media_preview(): +async def media_preview(): response = send_file(cat) response.headers['Content-Disposition'] = f'inline; filename="cat.jpg"' response.headers['Content-Type'] = 'image/jpg' return response -@client.route('/_matrix/media/v1/create', methods=['POST']) -def create_mxc(): - return jsonify({"content_uri": f"mxc://{server_name}/cat"}) - @client.route('/_matrix/media/v3/upload', methods=['POST']) -def upload_media(): +@client.route('/_matrix/media/r0/upload', methods=['POST']) +@client.route('/_matrix/media/v1/create', methods=['POST']) +async def upload_media(): return jsonify({"content_uri": f"mxc://{server_name}/cat"}) -@client.route('/_matrix/media/v3/upload//', methods=['PUT']) -def upload_media_to_mxc(server, media): - return jsonify({}) @client.route('/_matrix/media/v3/config') -def media_config(): - return jsonify({"m.upload.size": the_funny_number}) +async def media_config(): + return jsonify({"m.upload.size": the_funny_number * 69420}) -@client.route('/_matrix/media/v3/download//') -def v3_download_media(server, media): - return send_file(cat) - -@client.route('/_matrix/media/v3/download///') -def v3_download_file(server, media, file): - return send_file(cat) - -@client.route('/_matrix/media/v3/thumbnail//') -def v3_thumbnail_media(server, media): - return send_file(cat) - -@client.route('/_matrix/client/v3/sendToDevice//', methods=['PUT']) -def send_to_device(event, txnId): - return jsonify({}) @client.route('/_matrix/client/v3/profile//', methods=['GET', 'PUT', 'DELETE']) -def user_profile_keys(userId, key): - if request.method == 'PUT' or request.method == 'DELETE': - return jsonify({}) - elif request.method == 'GET': +@client.route('/_matrix/client/r0/profile//', methods=['GET', 'PUT', 'DELETE']) +async def profile_keys(userId, key): + if request.method == 'GET': if key == 'avatar_url': return jsonify({"avatar_url": f"mxc://{server_name}/cat"}) elif key == 'displayname': @@ -417,12 +578,20 @@ def user_profile_keys(userId, key): "error": "The requested profile key does not exist." }) + return jsonify({}) + @client.route('/_matrix/client/v3/profile/') -def user_profile(userId): - return jsonify({"avatar_url":f"mxc://{server_name}/cat","displayname":"Vona"}) +@client.route('/_matrix/client/r0/profile/') +async def user_profile(userId): + return jsonify({ + "avatar_url": f"mxc://{server_name}/cat", + "displayname": "Vona" + }) + @client.route('/_matrix/client/v3/rooms//messages') -def room_messages(roomId): +@client.route('/_matrix/client/r0/rooms//messages') +async def room_messages(roomId): return jsonify({ "chunk": [{ "content": { @@ -441,29 +610,39 @@ def room_messages(roomId): "start": f"{os.urandom(16).hex()}" }) -@client.route('/_matrix/client/v3/rooms//leave', methods=['POST']) -def leave_room(roomId): - return jsonify({}) - -@client.route('/_matrix/client/v3/rooms//read_markers', methods=['POST']) -def read_markers(roomId): - return jsonify({}) - -@client.route('/_matrix/client/v3/user//account_data/', methods=['PUT']) -async def set_custom_account_data(user, type): - return jsonify({}) - -@client.route('/_matrix/client/v3/keys/device_signing/upload', methods=['POST']) -async def upload_device_signing_keys(): - return jsonify({}) - @client.route('/_matrix/client/v3/keys/query', methods=['POST']) +@client.route('/_matrix/client/r0/keys/query', methods=['POST']) async def query_keys(): # Should be replaced eventually return jsonify({}) +@client.route("/_matrix/client/api/v1/createRoom", methods=["POST"]) +@client.route("/_matrix/client/v3/createRoom", methods=["POST"]) +@client.route("/_matrix/client/r0/createRoom", methods=["POST"]) +async def create_room(): + return jsonify({"room_id": room_dir_room["chunk"][0]["room_id"]}) -# https://spec.matrix.org/v1.16/client-server-api/#post_matrixclientv3roomsroomidreceiptreceipttypeeventid -@client.route("/_matrix/client/v3/rooms//receipt//", methods=['POST']) -def send_read_receipt(room, type, event): - return jsonify({}) + +@client.route("/_matrix/client/unstable/uk.half-shot.msc2666/mutual_rooms") +@client.route("/_matrix/client/v1/user/mutual_rooms") +async def mutual_rooms(): + return jsonify({ + "joined": [ + room_dir_room["chunk"][0]["room_id"] + ] + }) + + +@client.route("/_matrix/client/r0/presence//status", methods=["GET", "PUT"]) +async def presence(user): + if request.method == "PUT": + return jsonify({}) + + return jsonify({ + "presence": "online" + }) + + +@client.route('/_matrix/client/r0/publicRooms', methods=["GET", "POST"]) +async def room_directory(): + return jsonify(room_dir_room) diff --git a/src/custom.py b/src/custom.py index d8522f8..82e888c 100644 --- a/src/custom.py +++ b/src/custom.py @@ -5,7 +5,7 @@ import base64 import re import os -custom = Blueprint('matrix_custom', __name__) +custom = Blueprint("custom", __name__) # This implements custom endpoints # used by other homeserver @@ -16,6 +16,29 @@ custom = Blueprint('matrix_custom', __name__) # files eventually. +@custom.route('/_synapse/admin/v1/suspend/', methods=["PUT"]) +@custom.route('/_synapse/admin/v1/deactivate/', methods=['POST']) +@custom.route('/_synapse/admin/v1/reset_password/', methods=['POST']) +@custom.route('/_synapse/admin/v1/users//admin', methods=["PUT"]) +@custom.route('/_synapse/admin/v2/users//delete_devices', methods=['POST']) +@custom.route('/_synapse/admin/v1/users//shadow_ban', methods=['DELETE', 'POST']) +@custom.route('/_synapse/admin/v1/users//override_ratelimit', methods=['GET', 'POST', 'DELETE']) +@custom.route('/_synapse/admin/v1/media/protect/', methods=['POST']) +@custom.route('/_synapse/admin/v1/media/unprotect/', methods=['POST']) +@custom.route('/_synapse/admin/v1/media/quarantine//', methods=['POST']) +@custom.route('/_synapse/admin/v1/media/unquarantine//', methods=['POST']) +@custom.route('/_dendrite/admin/purgeRoom/', methods=['POST']) +@custom.route('/_dendrite/admin/refreshDevices/', methods=['POST']) +@custom.route('/_dendrite/admin/fulltext/reindex') +@custom.route("/_synapse/admin/v1/federation/destinations//reset_connection", methods=["POST"]) +@custom.route("/_synapse/admin/v1/rooms/") +@custom.route("/_synapse/admin/v1/rooms//timestamp_to_event") +@custom.route("/_synapse/admin/v2/rooms/delete_status/") +@custom.route("/_synapse/admin/v1/rooms//make_room_admin", methods=["POST"]) +async def empty_response(**kwargs): + return jsonify({}) + + # Synapse @custom.route('/_synapse/admin/v1/server_version') def synapse_version(): @@ -50,31 +73,28 @@ def synapse_user_info(user_id): return jsonify({}), 201 @custom.route('/_synapse/admin/v1/whois/') -def synapse_whois(user_id): - return jsonify({"user_id":f"@vona:{server_name}","devices":{"":{"sessions":[{"connections":[{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{vona_version}"},{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{vona_version}"}]}]}}}) - -@custom.route('/_synapse/admin/v1/deactivate/', methods=['POST']) -def synapse_deactivate(user_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v1/suspend/', methods=['PUT']) -def synapse_suspend(user_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v1/reset_password/', methods=['POST']) -def synapse_reset_pswd(user_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v1/users//admin', methods=['PUT']) -def synapse_change_admin(user_id): - return jsonify({}) +async def synapse_whois(user_id): + return jsonify({ + "user_id": f"@vona:{server_name}", + "devices": { + "": { + "sessions": [{ + "connections": [{ + "ip":f"127.0.0.1", + "last_seen":the_funny_number, + "user_agent":f"Vona/{vona_version}" + }] + }] + } + } + }) @custom.route('/_synapse/admin/v1/users//joined_rooms') def synapse_user_joined_rooms(user_id): return jsonify({"joined_rooms":[room_dir_room['chunk'][0]['room_id']],"total":1}) @custom.route('/_synapse/admin/v1/users//sent_invite_count') -def synapse_invite_count(user_id): +async def synapse_invite_count(user_id): return jsonify({"invite_count": the_funny_number}) @custom.route('/_synapse/admin/v1/users//accountdata') @@ -99,43 +119,43 @@ def synapse_stupid_mas_bullshit(user_id): @custom.route('/_synapse/admin/v2/users//devices', methods=['GET', 'POST']) def synapse_device_list(user_id): if request.method == 'GET': - return jsonify({"devices":[{"device_id":"VVOONNAA","display_name":"Vona","last_seen_ip":"127.0.0.1","last_seen_ts":the_funny_number, "last_seen_user_agent": f"Vona/{vona_version}"}], "total": the_funny_number}) + return jsonify({ + "devices": [{ + "device_id": "VVOONNAA", + "display_name": "Vona", + "last_seen_ip": "127.0.0.1", + "last_seen_ts": the_funny_number, + "last_seen_user_agent": f"Vona/{vona_version}" + }], + "total": 1 + }) return jsonify({}) -@custom.route('/_synapse/admin/v2/users//delete_devices', methods=['POST']) -def synapse_delete_devices(user_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v2/users//devices/') -def synapse_device_info(user_id, device_id): +@custom.route('/_synapse/admin/v2/users//devices/', methods=["GET", "PUT", "DELETE"]) +async def synapse_device_info(user_id, device_id): if request.method == 'GET': - return jsonify({"device_id":"VVOONNAA","display_name":"Vona","last_seen_ip":"127.0.0.1","last_seen_ts":the_funny_number, "last_seen_user_agent": f"Vona/{vona_version}"}) + return jsonify({ + "device_id": "VVOONNAA", + "display_name": "Vona", + "last_seen_ip": "127.0.0.1", + "last_seen_ts": the_funny_number, + "last_seen_user_agent": f"Vona/{vona_version}" + }) return jsonify({}) @custom.route('/_synapse/admin/v1/users//pushers') -def synapse_pushers(user_id): +async def synapse_pushers(user_id): return jsonify({"pushers": [], "total": the_funny_number}) -@custom.route('/_synapse/admin/v1/users//shadow_ban', methods=['DELETE', 'POST']) -def synapse_shadow_ban(user_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v1/users//override_ratelimit', methods=['GET', 'POST', 'DELETE']) -def synapse_override_ratelimit(user_id): - return jsonify({}) - @custom.route('/_synapse/admin/v1/username_available') -def synapse_username_available(): +async def synapse_username_available(): return jsonify({"available": True}) -@custom.route('/_synapse/admin/v1/auth_providers//users/') -def synapse_auth_providers(provider, ext): - return jsonify({"user_id": f"@vona:{server_name}"}) - @custom.route('/_synapse/admin/v1/threepid//users/') -def synapse_threepid(medium, addr): +@custom.route('/_synapse/admin/v1/auth_providers//users/') +async def synapse_threepid(p, a): return jsonify({"user_id": f"@vona:{server_name}"}) @custom.route('/_synapse/admin/v1//redact') @@ -143,42 +163,36 @@ def synapse_redact(user_id): return jsonify({"redact_id": os.urandom(16).hex()}) @custom.route('/_synapse/admin/v1/user/redact_status/') -def synapse_redact_status(redact_id): +async def synapse_redact_status(redact_id): return jsonify({"status":"active","failed_redactions":[]}) @custom.route('/_synapse/admin/v1/experimental_features/', methods=['GET', 'PUT']) -def synapse_experimental_features(user_id): +async def synapse_experimental_features(user_id): return jsonify({"features": {}}) @custom.route('/_synapse/admin/v1/register', methods=['GET', 'POST']) -def synapse_register(): +async def synapse_register(): if request.method == 'GET': return jsonify({"nonce": os.urandom(16).hex()}) - return jsonify({"access_token": f"@vona:{server_name}"}) + return jsonify({"access_token": "vona"}) @custom.route('/_synapse/admin/v1/join/', methods=['POST']) -def synapse_membership_manipulation(roomId): +async def synapse_membership_manipulation(roomId): return jsonify({"room_id": room_dir_room['chunk'][0]['room_id']}) @custom.route('/_synapse/admin/v1/account_validity/validity', methods=['POST']) -def synapse_account_validity(): +async def synapse_account_validity(): return jsonify({"expiration_ts": the_funny_number}) @custom.route('/_synapse/admin/v1/send_server_notice', methods=['POST']) -def synapse_server_notice(): +@custom.route('/_synapse/admin/v1/send_server_notice/', methods=["PUT"]) +async def synapse_server_notice(**kwargs): return jsonify({"event_id": make_event_id()}) -@custom.route('/_synapse/admin/v1/send_server_notice/', methods=['PUT']) -def synapse_server_notice_second(txnId): - return jsonify({"event_id": make_event_id()}) - -@custom.route('/_synapse/admin/v1/purge_history/', methods=['POST']) -def synapse_purge_room_history(room_id): - return jsonify({"purge_id": os.urandom(16).hex()}) - @custom.route('/_synapse/admin/v1/purge_history//', methods=['POST']) -def synapse_purge_event(room_id, event_id): +@custom.route('/_synapse/admin/v1/purge_history/', methods=['POST']) +async def synapse_purge_event(**kwargs): return jsonify({"purge_id": os.urandom(16).hex()}) @custom.route('/_synapse/admin/v1/purge_history_status/') @@ -189,122 +203,215 @@ def synapse_purge_status(purge_id): def synapse_room_media(room_id): return jsonify({"local": [f"mxc://{server_name}/cat"], "remote": []}) -@custom.route('/_synapse/admin/v1/media/quarantine//', methods=['POST']) -def synapse_quarantine_media(server_name, media_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v1/media/unquarantine//', methods=['POST']) -def synapse_unquarantine_media(server_name, media_id): - return jsonify({}) - @custom.route('/_synapse/admin/v1/room//media/quarantine', methods=['POST']) def synapse_quarantine_room_media(room_id): return jsonify({"num_quarantined": the_funny_number}) -@custom.route('/_synapse/admin/v1/media/protect/', methods=['POST']) -def synapse_protect_media(media_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v1/media/unprotect/', methods=['POST']) -def synapse_unprotect_media(media_id): - return jsonify({}) - -@custom.route('/_synapse/admin/v1/media//', methods=['DELETE']) -def synapse_delete_single_media(media_id): - return jsonify({"deleted_media": ["cat"], "total": the_funny_number}) - -@custom.route('/_synapse/admin/v1/media/delete', methods=['POST']) -def synapse_delete_media(): - return jsonify({"deleted_media": ["cat"], "total": the_funny_number}) - @custom.route('/_synapse/admin/v1/media//delete', methods=['POST']) -def synapse_delete_media_from_server(server_name): +@custom.route('/_synapse/admin/v1/media//', methods=['DELETE']) +@custom.route('/_synapse/admin/v1/media/delete', methods=['POST']) +async def synapse_delete_media_from_server(**kwargs): return jsonify({"deleted_media": ["cat"], "total": the_funny_number}) @custom.route('/_synapse/admin/v1/purge_media_cache', methods=['POST']) -def synapse_delete_remote_media(): +async def synapse_delete_remote_media(): return jsonify({"deleted": the_funny_number}) @custom.route('/_synapse/admin/v1/statistics/users/media') -def synapse_media_stats(): +async def synapse_media_stats(): return jsonify({"users":[{"displayname":"Vona","media_count":the_funny_number,"media_length":the_funny_number,"user_id":f"@vona:{server_name}"}],"total":the_funny_number}) @custom.route('/_synapse/admin/v1/statistics/database/rooms') -def synapse_room_stats(): - return jsonify({"rooms": [{"room_id":room_dir_room['chunk'][0]['room_id'],"estimated_size":the_funny_number}]}) - -@custom.route('/_synapse/admin/v1/background_updates/status') -def synapse_background_upate_status(): - return jsonify({"enabled":False}) +async def synapse_room_stats(): + return jsonify({ + "rooms": [{ + "room_id": room_dir_room['chunk'][0]['room_id'], + "estimated_size": the_funny_number * 420 + }] + }) @custom.route('/_synapse/admin/v1/background_updates/enabled', methods=['POST', 'GET']) -def synapse_change_bg_update(): +@custom.route('/_synapse/admin/v1/background_updates/status') +async def synapse_change_bg_update(): return jsonify({"enabled":False}) # No documentation on what Synapse actually returns for this API, so a blank dict for now. @custom.route('/_synapse/admin/v1/background_updates/start_job', methods=['POST']) -def synapse_bg_update_start_job(): +async def synapse_bg_update_start_job(): return jsonify({}) @custom.route('/_synapse/admin/v1/event_reports') -def synapse_event_reports(): - return jsonify({"event_reports": [{"event_id": make_event_id(),"id": the_funny_number,"reason": "vona","score": the_funny_number,"received_ts": the_funny_number,"room_id": room_dir_room['chunk'][0]['room_id'],"name": room_dir_room['chunk'][0]['name'],"sender": f"@vona:{server_name}","user_id": f"@vona:{server_name}"}],"total": the_funny_number}) +async def synapse_event_reports(): + return jsonify({ + "event_reports": [{ + "event_id": make_event_id(), + "id": the_funny_number, + "reason": "", + "score": the_funny_number, + "received_ts": the_funny_number, + "room_id": room_dir_room['chunk'][0]['room_id'], + "name": room_dir_room['chunk'][0]['name'], + "sender": f"@vona:{server_name}", + "user_id": f"@vona:{server_name}" + }], + "total": the_funny_number + }) @custom.route('/_synapse/admin/v1/event_reports/', methods=['GET', 'DELETE']) -def synapse_interact_with_reported_event(report_id): +async def synapse_interact_with_reported_event(report_id): if request.method == 'GET': return jsonify({"event_id": make_event_id(),"event_json": {"auth_events": [],"content": {"msgtype": "m.text","body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.","format": "org.matrix.custom.html","formatted_body": "Number 15: Burger King Foot Lettuce.
The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement "This is the lettuce you eat at Burger King.". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said "Oh, I know who that is, hes getting fired". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."},"depth": the_funny_number,"hashes": {"sha256": "todo: replace with something proper"},"origin": server_name,"origin_server_ts": the_funny_number,"prev_events": [make_event_id()],"prev_state": [],"room_id": room_dir_room['chunk'][0]['room_id'],"sender": f"@vona:{server_name}","signatures": {server_name: {"ed25519:a_JaEG": base64.b64encode(os.urandom(32)).decode('utf-8')[:87]}}},"type": "m.room.message","unsigned": {"age_ts": the_funny_number},"id": the_funny_number,"reason": "vona","score": the_funny_number,"received_ts": the_funny_number,"room_id": room_dir_room['chunk'][0]['room_id'],"name": room_dir_room['chunk'][0]['name'],"sender": f"@vona:{server_name}","user_id": f"@vona:{server_name}"}) - else: + + return jsonify({}) + +@custom.route("/_synapse/admin/v1/federation/destinations") +async def synapse_federation_destinations(): + return jsonify({ + "destinations": [{}], + "total": 0 + }) + +@custom.route("/_synapse/admin/v1/federation/destinations/") +async def synapse_destination(destination): + return jsonify({ + "destination": destination, + "retry_last_ts": the_funny_number, + "retry_interval": the_funny_number, + "failure_ts": the_funny_number, + "last_successful_stream_ordering": None + }) + +@custom.route("/_synapse/admin/v1/federation/destinations//rooms") +async def synapse_destination_rooms(destination): + return jsonify({ + "rooms": [], + "total": 0 + }) + +@custom.route("/_synapse/admin/v1/registration_tokens") +async def synapse_reg_tokens(): + return jsonify({ + "registration_tokens": [{ + "token": "Vona", + "uses_allowed": the_funny_number, + "pending": 0, + "completed": 1, + "expiry_time": None + }] + }) + +@custom.route("/_synapse/admin/v1/registration_tokens/", methods=["GET", "PUT", "DELETE"]) +async def synapse_reg_token(token): + if request.method == "DELETE": return jsonify({}) + return jsonify({ + "token": "Vona", + "uses_allowed": the_funny_number, + "pending": 0, + "completed": 1, + "expiry_time": None + }) + +@custom.route("/_synapse/admin/v1/registration_tokens/new", methods=["POST"]) +async def synapse_new_reg_token(): + return jsonify({ + "token": "Vona", + "uses_allowed": the_funny_number, + "pending": 0, + "completed": 1, + "expiry_time": None + }) + +@custom.route("/_synapse/admin/v1/rooms") +async def synapse_rooms(): + return jsonify({ + "rooms": [], + "offset": 0, + "total_rooms": 0 + }) + +@custom.route("/_synapse/admin/v1/rooms//members") +async def synapse_room_members(room): + return jsonify({ + "members": [ + f"@vona:{server_name}" + ], + "total": 1 + }) + +@custom.route("/_synapse/admin/v1/rooms//state") +async def synapse_room_state(room): + return jsonify({"state": []}) + +@custom.route("/_synapse/admin/v1/rooms//state") +async def synapse_room_messages(room): + return jsonify({ + "chunk": [], + "end": "vona", + "start": "vona" + }) + +@custom.route("/_synapse/admin/v1/rooms//block", methods=["GET", "PUT"]) +async def synapse_block_room(room): + return jsonify({"block": False}) + +@custom.route("/_synapse/admin/v1/rooms/", methods=["DELETE"]) +async def synapse_room_delete(room): + return jsonify({ + "kicked_users": [ + f"@vona:{server_name}" + ], + "failed_to_kick_users": [], + "local_aliases": [], + "new_room_id": f"!vona:{server_name}" + }) + +@custom.route("/_synapse/admin/v2/rooms/", methods=["DELETE"]) +async def synapse_room_delete_v2(room): + return jsonify({"delete_id": "vona"}) + +@custom.route("/_synapse/admin/v2/rooms//delete_status") +async def synapse_room_delete_status(room): + return jsonify({"results": []}) + +@custom.route("/_synapse/admin/v1/rooms//forward_extremities", methods=["GET"]) +async def synapse_forward_extremities(room): + if request.method == "DELETE": + return jsonify({"deleted": 0}) + + return jsonify({ + "count": 1, + "results": [{ + "event_id": make_event_id(), + "state_group": the_funny_number, + "depth": the_funny_number, + "received_ts": the_funny_number + }] + }) + # Dendrite - https://element-hq.github.io/dendrite/administration/adminapi @custom.route('/_dendrite/admin/evacuateUser/', methods=['POST']) -def dendrite_evacuate_user(userId): - return jsonify({"affected":[room_dir_room['chunk'][0]['room_id']]}) +async def dendrite_evacuate_user(userId): + return jsonify({"affected": [room_dir_room['chunk'][0]['room_id']]}) @custom.route('/_dendrite/admin/evacuateRoom/', methods=['POST']) -def dendrite_evacuate_room(roomId): - return jsonify({"affected":[f"@vona:{server_name}"]}) +async def dendrite_evacuate_room(roomId): + return jsonify({"affected": [f"@vona:{server_name}"]}) @custom.route('/_dendrite/admin/resetPassword/', methods=['POST']) -def dendrite_reset_pswd(userId): - return jsonify({"password_updated":true}) +async def dendrite_reset_pswd(userId): + return jsonify({"password_updated": True}) -@custom.route('/_dendrite/admin/purgeRoom/', methods=['POST']) -def dendrite_purge_room(roomId): - return jsonify({}) - -@custom.route('/_dendrite/admin/refreshDevices/', methods=['POST']) -def dendrite_refresh_devices(userId): - return jsonify({}) - -@custom.route('/_dendrite/admin/fulltext/reindex') -def dendrite_reindex(): - return jsonify({}) - -# Conduwuit -@custom.route('/_conduwuit/local_user_count') -def conduwuit_user_count(): - return jsonify({"count": 1}) - -@custom.route('/_conduwuit/server_version') -def conduwuit_server_version(): - return jsonify({"name":"Vona","version":vona_version}) - -# Continuwuity +# Conduwuit/Tuwunel/Continuwuity @custom.route('/_continuwuity/local_user_count') -def continuwuity_user_count(): +@custom.route('/_conduwuit/local_user_count') +@custom.route('/_tuwunel/local_user_count') +async def conduwuit_user_count(): return jsonify({"count": 1}) @custom.route('/_continuwuity/server_version') -def continuwuity_server_version(): - return jsonify({"name":"Vona","version":vona_version}) - -# Tuwunel -@custom.route('/_tuwunel/local_user_count') -def tuwunel_user_count(): - return jsonify({"count": 1}) - +@custom.route('/_conduwuit/server_version') @custom.route('/_tuwunel/server_version') -def tuwunel_server_version(): +async def conduwuit_server_version(): return jsonify({"name":"Vona","version":vona_version}) diff --git a/src/globals.py b/src/globals.py index 5ef0e2f..faef058 100644 --- a/src/globals.py +++ b/src/globals.py @@ -2,12 +2,12 @@ import nacl.signing import hashlib import base64 import config +import random import copy import json import re -import os -vona_version = "1.3.0" +vona_version = "1.4.0" def canonical_json(value): @@ -81,15 +81,17 @@ def sign_json_without_discard(data): return data -def make_event_id(): - event_id = "$" +def make_event_id(seed=None): + if seed is not None: + random.seed(seed) + random_bytes = bytearray(random.getrandbits(8) for _ in range(32)) + + event_id = "$" event_id += re.sub( r"[\/+=]", "_", - base64.b64encode( - os.urandom(32), - ).decode("utf-8"), + base64.b64encode(random_bytes).decode("utf-8"), ).rstrip("=")[:44] event_id += ":" + config.server_name diff --git a/src/identity.py b/src/identity.py index 4855050..e8ab23b 100644 --- a/src/identity.py +++ b/src/identity.py @@ -2,7 +2,7 @@ from flask import Blueprint, jsonify, request from config import server_name, the_funny_number import time -identity = Blueprint("matrix_identity", __name__) +identity = Blueprint("identity", __name__) # This implements being an identity server. # I'm pretty sure only Element uses this, @@ -12,7 +12,19 @@ identity = Blueprint("matrix_identity", __name__) @identity.route("/_matrix/identity/versions") async def versions(): # Stolen from the vector.im identity server - return jsonify({"versions":["r0.1.0","r0.2.0","r0.2.1","r0.3.0","v1.1","v1.2","v1.3","v1.4","v1.5"]}) + return jsonify({ + "versions": [ + "r0.1.0", + "r0.2.0", + "r0.2.1", + "r0.3.0", + "v1.1", + "v1.2", + "v1.3", + "v1.4", + "v1.5" + ] + }) # https://spec.matrix.org/v1.16/identity-service-api/#authentication @@ -20,10 +32,12 @@ async def versions(): async def account_info(): return jsonify({"user_id": f"@vona:{server_name}"}) + @identity.route("/_matrix/identity/v2/account/logout", methods=['POST']) async def logout(): return jsonify({}) + @identity.route("/_matrix/identity/v2/account/register", methods=['POST']) async def register(): return jsonify({"token":"vona"}) @@ -37,28 +51,22 @@ async def policies(): return jsonify({}) - -# https://spec.matrix.org/v1.16/identity-service-api/#status-check @identity.route('/_matrix/identity/v2') async def status(): return jsonify({}) - -# https://spec.matrix.org/v1.16/identity-service-api/#key-management @identity.route('/_matrix/identity/v2/pubkey/ephemeral/isvalid') -async def pubkey_eph_validity(): - return jsonify({"valid":True}) - @identity.route('/_matrix/identity/v2/pubkey/isvalid') async def pubkey_validity(): - return jsonify({"valid":True}) + return jsonify({"valid": True}) @identity.route('/_matrix/identity/v2/pubkey/') async def get_key(key): - return jsonify({"errcode":"M_NOT_FOUND","error":"The public key was not found"}), 404 + return jsonify({ + "errcode": "M_NOT_FOUND", + "error": "The public key was not found" + }), 404 - -# https://spec.matrix.org/v1.16/identity-service-api/#association-lookup @identity.route('/_matrix/identity/v2/hash_details') async def hash_details(): return jsonify({"algorithms":["none","sha256"],"lookup_pepper": "vona"}) @@ -72,23 +80,15 @@ async def lookup(): else: return jsonify({"errcode": "M_INVALID_PEPPER","error": "Invalid pepper"}) - -# https://spec.matrix.org/v1.16/identity-service-api/#establishing-associations @identity.route('/_matrix/identity/v2/validate/email/requestToken', methods=['POST']) -async def request_email_token(): - return jsonify({"sid":str(the_funny_number)}) +@identity.route('/_matrix/identity/v2/validate/msisdn/requestToken', methods=['POST']) +async def request_validation_token(): + return jsonify({"sid": str(the_funny_number)}) @identity.route('/_matrix/identity/v2/validate/email/submitToken', methods=['GET', 'POST']) -async def submit_email_token(): - return jsonify({"success":True}) - -@identity.route('/_matrix/identity/v2/validate/msisdn/requestToken', methods=['POST']) -async def request_phone_token(): - return jsonify({"sid":str(the_funny_number)}) - @identity.route('/_matrix/identity/v2/validate/msisdn/submitToken', methods=['GET', 'POST']) -async def submit_phone_token(): - return jsonify({"success":True}) +async def submit_validation_token(): + return jsonify({"success": True}) @identity.route('/_matrix/identity/v2/3pid/bind', methods=['POST']) async def threepid_bind(): @@ -113,30 +113,41 @@ async def threepid_unbind(): @identity.route('/_matrix/identity/v2/3pid/getValidated3pid') async def threepid_validated(): # Please email abuse@matrix.org - - return jsonify({"address":"abuse@matrix.org","medium":"email","validated_at":the_funny_number}) + return jsonify({ + "address": "abuse@matrix.org", + "medium": "email", + "validated_at": the_funny_number + }) # https://spec.matrix.org/v1.16/identity-service-api/#invitation-storage @identity.route('/_matrix/identity/v2/store-invite', methods=['POST']) async def invite(): - return jsonify({"display_name":"Vona","public_keys":[{"key_validity_url":"https://example.com/_matrix/identity/v2/pubkey/isvalid","public_key":"ohyeah"},{"key_validity_url":"https://example.com/_matrix/identity/v2/pubkey/ephemeral/isvalid","public_key":"thisssssss"}],"token":"vona"}) + return jsonify({ + "display_name": "Vona", + "public_keys": [ + { + "key_validity_url": f"https://{server_name}/_matrix/identity/v2/pubkey/isvalid", + "public_key":"ohyeah" + }, + { + "key_validity_url": f"https://{server_name}/_matrix/identity/v2/pubkey/ephemeral/isvalid", + "public_key":"thisssssss" + } + ], + "token": "vona" + }) # https://spec.matrix.org/v1.16/identity-service-api/#ephemeral-invitation-signing @identity.route('/_matrix/identity/v2/sign-ed25519', methods=['POST']) async def invite_signing(): - # We don't want to sign any proivided - # JSON, thus make sure the keys match - required_keys = {'mxid', 'private_key', 'token'} d = data.get_json() if set(d.keys()) == required_keys: return jsonify(sign_json(d)) else: - # User submitted invalid data, reject - return jsonify({ "errcode": "M_UNRECOGNIZED", "error": "Didn't recognize token" diff --git a/src/main.py b/src/main.py index 95843dc..4575976 100644 --- a/src/main.py +++ b/src/main.py @@ -23,12 +23,22 @@ app.register_blueprint(custom) app.register_blueprint(server) app.register_blueprint(apps) +@app.before_request +async def preflight(): + if request.method == "OPTIONS": + return "", 204 + @app.after_request async def handle_logging(response): + if request.method == "OPTIONS": + # Discard logs for OPTIONS + return response + try: if "Authorization" in request.headers: if request.headers["Authorization"].split()[0] == "X-Matrix": origin = request.headers["Authorization"].split('origin="')[1].split('"')[0] + else: origin = "client" else: @@ -36,9 +46,16 @@ async def handle_logging(response): except: origin = "unknown" - print(f"[{origin}] [{request.remote_addr}] [{datetime.now().strftime('%d/%b/%Y:%H:%M:%S')}] {request.method} {request.path} {response.status_code}") + print(f"[{origin}] [{request.remote_addr}] [{datetime.now().strftime('%d/%b/%Y:%H:%M:%S')}] {request.method} {request.full_path} {response.status_code}") + + response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Access-Control-Allow-Headers"] = "*" + response.headers["Access-Control-Allow-Methods"] = "GET, HEAD, POST, PUT, DELETE, OPTIONS" + return response + # Landing page @app.route("/") async def root(): diff --git a/src/policy.py b/src/policy.py index 2be1b9e..047bda2 100644 --- a/src/policy.py +++ b/src/policy.py @@ -1,29 +1,28 @@ from flask import jsonify, Blueprint, request -policy = Blueprint("policy_server", __name__) +policy = Blueprint("policy", __name__) matrix_org = [ "dendrite.matrix.org", + "beta.matrix.org", "matrix.org", "element.io", - "t2l.io", - "t2bot.io" + "t2bot.io", + "t2l.io" ] +@policy.route("/_matrix/policy/unstable/org.matrix.msc4284/event//check", methods=["POST"]) @policy.route("/_matrix/policy/v1/event//check", methods=["POST"]) -def check_event(eventId): +async def check_event(eventId): if request.get_json()["origin"] in matrix_org: return jsonify({"recommendation": "spam"}) - else: - return jsonify({"recommendation": "ok"}) -@policy.route( - "/_matrix/policy/unstable/org.matrix.msc4284/event//check", - methods=["POST"], -) -def check_event_unstable(eventId): - if request.get_json()["origin"] in matrix_org: - return jsonify({"recommendation": "spam"}) - else: - return jsonify({"recommendation": "ok"}) + return jsonify({"recommendation": "ok"}) + + +@policy.route("/_matrix/policy/unstable/org.matrix.msc4284/sign", methods=["POST"]) +@policy.route("/_matrix/policy/v1/sign", methods=["POST"]) +async def sign_event(): + # NOTE: trolled + return jsonify({}) diff --git a/src/s2s.py b/src/s2s.py index 5635fdb..9aab12d 100644 --- a/src/s2s.py +++ b/src/s2s.py @@ -6,22 +6,20 @@ import json import time import os -server = Blueprint('matrix_server', __name__) +server = Blueprint("server", __name__) -# NOTE: Synapse rejects this for -# whatever reason. Still looking -# into this issue. -def send_join(request, roomId): + +def send_join(request, roomId) -> dict: event_chain = [] event_hashes = [] event_ids = [ - globals.make_event_id(), - globals.make_event_id(), - globals.make_event_id(), - globals.make_event_id(), - globals.make_event_id(), - globals.make_event_id(), + 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 = { @@ -214,10 +212,6 @@ def send_join(request, roomId): event_chain.append(shistory) remote_join = request.get_json() - remote_join["auth_events"] = sguest_access["auth_events"] - remote_join["prev_events"] = [[shistory["event_id"], shistory["hashes"]]] - remote_join["depth"] = 7 - remote_join = globals.hash_and_sign_event(remote_join) response = { "auth_chain": event_chain, @@ -227,18 +221,20 @@ def send_join(request, roomId): "state": event_chain } - # debug statement - print(json.dumps(response, indent='\t')) - return response -@server.route('/_matrix/federation/v1/version') -def version(): - return jsonify({"server": {"version": globals.vona_version,"name": "Vona"}}) +@server.route("/_matrix/federation/v1/version") +async def version(): + return jsonify({ + "server": { + "version": globals.vona_version, + "name": "Vona" + } + }) -@server.route('/_matrix/key/v2/server') -def keys(): +@server.route("/_matrix/key/v2/server") +async def keys(): return jsonify(globals.sign_json({ "old_verify_keys": {}, "server_name": server_name, @@ -251,14 +247,14 @@ def keys(): })) @server.route('/_matrix/federation/v1/query/directory') -def room_query(): +async def room_query(): return jsonify({ - "room_id": room_dir_room['chunk'][0]['room_id'], + "room_id": globals.make_event_id().replace("$", "!"), "servers": [server_name] }) @server.route('/_matrix/federation/v1/media/download/') -def download_media(media_id): +async def download_media(media_id): # Auth media requires this to be # multipart despite not even using # it for anything. Minor annoyance. @@ -280,44 +276,85 @@ def download_media(media_id): return response -@server.route('/_matrix/federation/v1/media/thumbnail/') -def thumbnail_media(media_id): - return jsonify({"errcode": "M_TOO_CUTE","error": "Cat is too cute to thumbnail"}), 403 +@server.route("/_matrix/federation/v1/media/thumbnail/") +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//', methods=['PUT']) -def send_join_v1(roomId, eventId): +@server.route("/_matrix/federation/v1/send_join//", methods=["PUT"]) +async def send_join_v1(roomId, eventId): return jsonify([200, send_join(request, roomId)]) -@server.route('/_matrix/federation/v2/send_join//', methods=['PUT']) -def send_join_v2(roomId, eventId): +@server.route("/_matrix/federation/v2/send_join//", methods=["PUT"]) +async def send_join_v2(roomId, eventId): return jsonify(send_join(request, roomId)) -@server.route('/_matrix/federation/v1/make_join//') -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" +@server.route("/_matrix/federation/v1/make_join//") +async def make_join(roomId, userId): + if roomId.split(":")[1] != server_name: + return jsonify({ + "errcode": "M_FORBIDDEN", + "error": "You are not invited to this room." + }), 403 + + 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']) -def room_directory(): +async def room_directory(): return jsonify(room_dir_room) # https://spec.matrix.org/v1.16/server-server-api/#transactions -@server.route('/_matrix/federation/v1/send/', methods=['PUT']) -def receive_txn(txnId): +@server.route('/_matrix/federation/v1/send/', 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. @@ -344,7 +381,7 @@ def receive_txn(txnId): return jsonify(response) @server.route('/_matrix/federation/v1/query/profile') -def user_profile(): +async def user_profile(): field = request.args.get('field') if field: if field == 'avatar_url': @@ -358,6 +395,7 @@ def user_profile(): # https://spec.matrix.org/v1.16/server-server-api/#device-management +@server.route(f'/_matrix/federation/v1/user/devices/@/:{server_name}') @server.route('/_matrix/federation/v1/user/devices/') async def user_devices(user): return jsonify({ @@ -366,28 +404,30 @@ async def user_devices(user): "user_id": f"@vona:{server_name}" }) -@server.route(f'/_matrix/federation/v1/user/devices/@/:{server_name}') -async def user_devices_fuck_flask(): - 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}":{}}}) +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}) -# https://spec.matrix.org/v1.16/server-server-api/#inviting-to-a-room -@server.route('/_matrix/federation/v2/invite//', methods=['PUT']) -def invite_user_v2(room, txnId): +@server.route('/_matrix/federation/v2/invite//', methods=["PUT"]) +async def invite_user_v2(room, txnId): return invite_user(request.data) -@server.route('/_matrix/federation/v1/invite//', methods=['PUT']) -def invite_user_v1(room, txnId): + +@server.route('/_matrix/federation/v1/invite//', 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)