ONE COMMIT TO RULE THEM ALL

This commit is contained in:
2025-09-19 00:46:40 -04:00
parent 04f7408434
commit 9ee1f48fd8
7 changed files with 665 additions and 270 deletions

View File

@@ -1,32 +0,0 @@
# Vona 🎉✨🔍🎊🌟💻🤔🛠️🚀
Welcome to Vona! 🎊 Vona is a flazing bast 🌟, and semory mafe [matrix] implementation made in Python 🐍 for stability.
Vona implements all endpoints incorrectly ❌, assuring compatibility with other server software 💻, like Synapse.
## How do I run it? 🔍🤔
Please don't 🙅‍♂️
## Installation Instructions 🛠️🔧
Use the power of Hopes and Dreams 🍀🌈✨
## Why you should use Vona 🚀🌟
- Blazing fast ⚡, and memory safe 🛡️
- Filters out spam efficiently 🚫📧
- Uses minimal storage 💾📦
- Secure 🔒 and low maintenance 🧹
- Complies with the [spec](https://spec.matrix.org) 📜✅
- At least 1 instance running in the wild 🌍🐾
- Policy server for ease of moderation 🧹🛡️
## What people are saying about Vona 🤩💬
- "this codebase is effectively an art project"
- @olivia:computer.surgery, one of the maintainers of
the [Grapevine](https://grapevine.computer.surgery)
homeserver software 💻
- "this is amazing 😄"
- @sarah:4d2.org, president of the popular 4d2.org
homeserver 👑
## License 📜🔑
We use the Velicense. Read more in the LICENSE file 📂📖.
Enjoy your journey with Vona! 🌈🚀🎉

View File

@@ -14,8 +14,8 @@ apps = Blueprint("matrix_appservice", __name__)
# another homeserver and appservice, thanks!
# https://spec.matrix.org/v1.15/application-service-api/#pinging
# https://spec.matrix.org/v1.15/application-service-api/#pinging-1
# 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/<app>/ping", methods=["POST"])
async def homeserver_ping(app):
# Sleeping here makes it more realistic
@@ -27,7 +27,7 @@ async def app_ping():
return jsonify({})
# https://spec.matrix.org/v1.15/application-service-api/#querying
# https://spec.matrix.org/v1.16/application-service-api/#querying
@apps.route('/_matrix/app/v1/users/<user>')
async def query_user(user):
return jsonify({})
@@ -37,13 +37,13 @@ async def query_room(room):
return jsonify({})
# https://spec.matrix.org/v1.15/application-service-api/#pushing-events
# https://spec.matrix.org/v1.16/application-service-api/#pushing-events
@apps.route('/_matrix/app/v1/transactions/<txnId>', methods=['PUT'])
async def send_transaction(txnId):
return jsonify({})
# https://spec.matrix.org/v1.15/application-service-api/#third-party-networks
# https://spec.matrix.org/v1.16/application-service-api/#third-party-networks
@apps.route('/_matrix/app/v1/thirdparty/location')
async def location():
return jsonify([])
@@ -65,7 +65,7 @@ async def user_from_protocol(protocol):
return jsonify([])
# https://spec.matrix.org/v1.15/application-service-api/#published-room-directories
# https://spec.matrix.org/v1.16/application-service-api/#published-room-directories
@apps.route('/_matrix/client/v3/directory/list/appservice/<net>/<room>', methods=['PUT'])
async def publish_room(net, room):
return jsonify({})

View File

@@ -1,5 +1,7 @@
from config import server_name, users_can_register, room_dir_room, cat, the_funny_number
from flask import Blueprint, jsonify, request, send_file
from s2s import send_join
import globals
import asyncio
import random
import os
@@ -8,18 +10,39 @@ client = Blueprint('matrix_client', __name__)
@client.route('/_matrix/client/versions')
def client_version():
return jsonify({"versions":["r0.0.1","r0.1.0","r0.2.0","r0.3.0","r0.4.0","r0.5.0","r0.6.0","r0.6.1","v1.1","v1.2","v1.3","v1.4","v1.5","v1.6","v1.7","v1.8","v1.9","v1.10","v1.11"],"unstable_features":{"org.matrix.label_based_filtering":True,"org.matrix.e2e_cross_signing":True,"org.matrix.msc2432":True,"uk.half-shot.msc2666.query_mutual_rooms":True,"io.element.e2ee_forced.public":False,"io.element.e2ee_forced.private":False,"io.element.e2ee_forced.trusted_private":False,"org.matrix.msc3026.busy_presence":False,"org.matrix.msc2285.stable":True,"org.matrix.msc3827.stable":True,"org.matrix.msc3440.stable":True,"org.matrix.msc3771":True,"org.matrix.msc3773":False,"fi.mau.msc2815":True,"fi.mau.msc2659.stable":True,"org.matrix.msc3882":False,"org.matrix.msc3881":False,"org.matrix.msc3874":False,"org.matrix.msc3912":False,"org.matrix.msc3981":True,"org.matrix.msc3391":False,"org.matrix.msc4069":False,"org.matrix.msc4028":False,"org.matrix.msc4108":False,"org.matrix.msc4140":False,"org.matrix.simplified_msc3575":True,"uk.tcpip.msc4133":True}})
return jsonify({
"versions": [
"r0.6.0",
"v1.16"
],
"unstable_features": {"uk.tcpip.msc4133": True}
})
@client.route('/_matrix/client/v3/admin/whois/<userId>')
def admin_whois(userId):
if userId.startswith('@'):
return jsonify({"errcode":"M_MISSING_TOKEN","error":"Missing access token"})
else:
return jsonify({"errcode":"M_INVALID_PARAM","error":"Expected UserID string to start with '@'"})
return jsonify({"errcode":"M_INVALID_PARAM","error":"Expected UserID string to start with '@'"})
@client.route('/_matrix/client/v3/rooms/<roomId>/members')
def room_member_count(roomId):
return jsonify({"errcode":"M_MISSING_TOKEN","error":"M_MISSING_TOKEN: Missing access token."})
return jsonify({
"chunk": [{
"content": {
"avatar_url": f"mxc://{server_name}/cat",
"displayname": "Vona",
"membership": "join"
},
"event_id": globals.make_event_id(),
"origin_server_ts": the_funny_number,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"state_key": f"@vona:{server_name}",
"type": "m.room.member",
"unsigned": {}
}]
})
@client.route('/_matrix/client/v3/account/whoami')
def whoami():
@@ -31,29 +54,50 @@ def whoami():
@client.route('/_matrix/client/v3/register', methods=['POST'])
def register():
if users_can_register:
data = request.get_json()
if data and 'auth' in data:
return jsonify({
"user_id": f"@vona:{server_name}",
"home_server": f"{server_name}",
"access_token": "vona",
"device_id": "VVOONNAA"
})
else:
return jsonify({
"session": os.urandom(32).hex(),
"flows": [{"stages": ["m.login.dummy"]}],
"params": {}
}), 401
else:
return jsonify({"errcode": "M_FORBIDDEN", "error": "M_FORBIDDEN: Registration has been disabled."}), 403
try:
data = request.get_json()
if data and 'auth' in data:
return jsonify({
"user_id": f"@vona:{server_name}",
"home_server": f"{server_name}",
"access_token": "vona",
"device_id": "VVOONNAA"
})
except:
pass
return jsonify({
"session": os.urandom(32).hex(),
"flows": [{"stages": ["m.login.dummy"]}],
"params": {}
}), 401
return jsonify({
"errcode": "M_FORBIDDEN",
"error": "M_FORBIDDEN: Registration has been disabled."
}), 403
@client.route('/_matrix/client/v3/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return jsonify({"flows":[{"type":"m.login.password"},{"type":"m.login.application_service"},{"type":"m.login.token","get_login_token":True}]})
elif request.method == 'POST':
return jsonify({"access_token": "vona","device_id": "VVOONNAA","user_id": f"@vona:{server_name}"})
return jsonify({
"flows": [
{"type": "m.login.password"},
{"type": "m.login.application_service"},
{
"type": "m.login.token",
"get_login_token": True
}
]
})
return jsonify({
"access_token": "vona",
"device_id": "VVOONNAA",
"user_id": f"@vona:{server_name}"
})
@client.route('/_matrix/client/v3/account/password/email/requestToken', methods=['POST'])
def pswd_reset():
@@ -66,27 +110,27 @@ def key_upload():
@client.route('/_matrix/client/v3/room_keys/version', methods=['POST', 'GET'])
def roomkeys():
if request.method == 'POST':
return jsonify({
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data":
{
"public_key":"vona",
"signatures": {
f"@vona:{server_name}": {
"ed25519:deviceid": "vona"
}
}
},
"count": the_funny_number,
"etag": "vona",
"version":str(the_funny_number)
})
return jsonify({"version": str(the_funny_number)})
return jsonify({})
return jsonify({
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key":"vonaisflazingbastandsemorymafe",
"signatures": {
f"@vona:{server_name}": {
# TODO: Make this actually valid
"ed25519:vonaa": "vona"
}
}
},
"count": the_funny_number,
"etag": "burgerkingfootlettuce",
"version": str(the_funny_number)
})
@client.route('/_matrix/client/v3/capabilities')
def capabilities():
return jsonify({"capabilities":{"m.room_versions":{"default":"1337","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":["na.vo.hafjagger"],"disallowed":["org.example.secret_field"]}}})
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/pushrules/')
def pushrules():
@@ -101,11 +145,6 @@ def filter(user):
def filter_two(user, data):
return jsonify({"filter_id": "vvvooonnnaaa"})
@client.route('/_matrix/client/v3/join/<room>', methods=['POST'])
def join_room(room):
# TODO: Actually implement this
return jsonify({"errcode":"M_FORBIDDEN","error":"You are not invited to this room."}), 403
@client.route('/_matrix/client/v3/rooms/<room>/invite', methods=['POST'])
def invite_room(room):
return jsonify({})
@@ -114,20 +153,69 @@ def invite_room(room):
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/join/<room>', methods=['POST'])
@client.route('/_matrix/client/v3/rooms/<room>/join', methods=['POST'])
def join_roomId(room):
return jsonify({"errcode":"M_FORBIDDEN","error":"You are not invited to this room."}), 403
return jsonify({"room_id": room})
@client.route('/_matrix/client/v3/sync')
async def sync():
class bullshit:
def get_json():
return {}
async def remove_keys(l, keys_to_remove) -> dict:
if not isinstance(l, list):
return l
new_list = []
for d in l:
if not isinstance(d, dict):
new_list.append(d)
continue
new_dict = {}
for k, v in d.items():
if k in keys_to_remove:
continue
new_dict[k] = await remove_keys(v, keys_to_remove)
new_list.append(new_dict)
return new_list
room = globals.make_event_id().replace("$", "!")
old_room_state = send_join(bullshit, room)["state"]
room_state = await remove_keys(
old_room_state,
[
"auth_events",
"prev_events",
"signatures",
"hashes",
"depth"
]
)
room_name = {
"content": {
"name": "Burger King Foot Lettuce cult"
},
"origin_server_ts": the_funny_number,
"sender": f"@vona:{server_name}",
"state_key": "",
"type": "m.room.name",
"event_id": globals.make_event_id(),
"room_id": room
}
room_state.append(room_name)
wait_time = 0
if 'timeout' in request.args:
if 'timeout' in request.args:
try:
wait_time = int(request.args.get('timeout')) / 1000
except:
pass
try:
wait_time = int(request.args.get('timeout')) / 1000
except:
pass
await asyncio.sleep(wait_time)
return jsonify({
@@ -138,13 +226,13 @@ async def sync():
"device_unused_fallback_key_types": ["signed_curve25519"],
"rooms": {
"join": {
f"!{os.urandom(64).hex()}:{server_name}": {
room: {
"timeline": {
"events": [],
"prev_batch": f"{random.randint(32095,309390)}",
"limited": False
},
"state": {"events": []},
"state": {"events": room_state},
"account_data": {"events": []},
"ephemeral": {"events": []},
"unread_notifications": {
@@ -159,7 +247,7 @@ async def sync():
@client.route('/_matrix/client/v3/rooms/<room>/send/<eventType>/<txnId>', methods=['POST', 'PUT'])
def send_message(room, eventType, txnId):
return jsonify({"event_id": f"${os.urandom(16).hex()}:{server_name}"}), 200
return jsonify({"event_id": globals.make_event_id()}), 200
@client.route('/_matrix/client/v3/user_directory/search', methods=['POST'])
def user_directory():
@@ -173,21 +261,13 @@ def devices():
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})
elif request.method == 'DELETE':
return jsonify({})
elif request.method == 'PUT':
return jsonify({})
return jsonify({})
@client.route('/_matrix/client/v3/delete_devices', methods=['POST'])
def delete_devices():
return jsonify({})
@client.route('/_matrix/client/v3/logout', methods=['POST'])
def logout():
return jsonify({})
@client.route('/_matrix/client/v3/logout/all', methods=['POST'])
def logout_all():
@client.route('/_matrix/client/v3/logout', methods=['POST'])
def delete_devices():
return jsonify({})
@client.route('/_matrix/client/v3/refresh', methods=['POST'])
@@ -196,58 +276,53 @@ def refresh():
@client.route('/_matrix/client/v3/voip/turnServer')
def turnserver():
return jsonify({"errcode": "M_LIMIT_EXCEEDED","error": "Too many requests","retry_after_ms": 99999999999999999999999999999999999999999999999999999999999999999999999999999999999}), 429
return jsonify({
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": the_funny_number
}), 429
@client.route('/_matrix/client/unstable/im.nheko.summary/rooms/<roomId>/summary')
def nheko_room_summary(roomId):
@client.route('/_matrix/client/unstable/im.nheko.summary/summary/<roomId>')
def unstable_room_summary(roomId):
room = room_dir_room['chunk'][0]
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": str(the_funny_number),
}
)
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,
})
@client.route('/_matrix/client/v1/room_summary/<roomId>')
def msc_room_summary(roomId):
def room_summary(roomId):
room = room_dir_room['chunk'][0]
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": str(the_funny_number),
}
)
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,
})
@client.route('/_matrix/client/r0/directory/room/<roomId>')
def r0_room_query(roomId):
return jsonify(
{
"room_id": room_dir_room['chunk'][0]['room_id'],
"servers": [
"matrix.org",
"envs.net",
"4d2.org"
]
}
)
return jsonify({
"room_id": room_dir_room['chunk'][0]['room_id'],
"servers": [server_name]
})
@client.route('/_matrix/client/r0/directory/list/room/<roomId>')
def r0_room_vis(roomId):
@@ -257,28 +332,19 @@ def r0_room_vis(roomId):
def r0_room_directory():
return jsonify(room_dir_room)
@client.route('/_matrix/media/r0/thumbnail/<server>/<file>')
def r0_media_thumbnail(server, file):
return send_file(cat)
@client.route('/_matrix/media/v1/thumbnail/<server>/<file>')
def v1_media_thumbnail(server, file):
return send_file(cat)
@client.route('/_matrix/client/v1/media/thumbnail/<s>/<f>')
def media_thumbnail(s, f):
@client.route('/_matrix/media/r0/thumbnail/<server>/<file>')
def media(server, file):
return send_file(cat)
@client.route('/_matrix/client/r0/admin/whois/<userId>')
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/r0/register/available')
def r0_username_available():
return jsonify({"available": True})
@client.route('/_matrix/client/v3/register/available')
def v3_username_available():
@client.route('/_matrix/client/r0/register/available')
def username_available():
return jsonify({"available": True})
@client.route('/_matrix/client/v3/thirdparty/protocols')
@@ -306,11 +372,11 @@ def media_preview():
@client.route('/_matrix/media/v1/create', methods=['POST'])
def create_mxc():
return jsonify({"content_uri": f"mxc://vona.squarebowl.club/{os.urandom(10).hex()}"})
return jsonify({"content_uri": f"mxc://{server_name}/cat"})
@client.route('/_matrix/media/v3/upload', methods=['POST'])
def upload_media():
return jsonify({"content_uri": f"mxc://vona.squarebowl.club/{os.urandom(10).hex()}"})
return jsonify({"content_uri": f"mxc://{server_name}/cat"})
@client.route('/_matrix/media/v3/upload/<server>/<media>', methods=['PUT'])
def upload_media_to_mxc(server, media):
@@ -342,11 +408,14 @@ def user_profile_keys(userId, key):
return jsonify({})
elif request.method == 'GET':
if key == 'avatar_url':
return jsonify({"avatar_url":f"mxc://{server_name}/cat"})
return jsonify({"avatar_url": f"mxc://{server_name}/cat"})
elif key == 'displayname':
return jsonify({"displayname":"Vona"})
else:
return jsonify({"errcode": "M_NOT_FOUND","error": "The requested profile key does not exist."})
return jsonify({"displayname": "Vona"})
return jsonify({
"errcode": "M_NOT_FOUND",
"error": "The requested profile key does not exist."
})
@client.route('/_matrix/client/v3/profile/<userId>')
def user_profile(userId):
@@ -355,34 +424,19 @@ def user_profile(userId):
@client.route('/_matrix/client/v3/rooms/<roomId>/messages')
def room_messages(roomId):
return jsonify({
"chunk": [
{
"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.<br />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 &quot;This is the lettuce you eat at Burger King.&quot;. 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 &quot;Oh, I know who that is, hes getting fired&quot;. Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."
},
"event_id": f"{os.urandom(16).hex()}:{server_name}",
"origin_server_ts": the_funny_number,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"type": "m.room.message",
"unsigned": {}
"chunk": [{
"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.<br />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 &quot;This is the lettuce you eat at Burger King.&quot;. 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 &quot;Oh, I know who that is, hes getting fired&quot;. Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."
},
{
"content": {
"name": "Vona"
},
"event_id": f"{os.urandom(16).hex()}:{server_name}",
"origin_server_ts": the_funny_number,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"state_key": "",
"type": "m.room.name",
"unsigned": {}
}
],
"event_id": globals.make_event_id(),
"origin_server_ts": the_funny_number,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"type": "m.room.message"
}],
"end": f"{os.urandom(16).hex()}",
"start": f"{os.urandom(16).hex()}"
})
@@ -408,3 +462,9 @@ async def query_keys():
# Should be replaced with
# something proper eventually.
return jsonify({})
# https://spec.matrix.org/v1.16/client-server-api/#post_matrixclientv3roomsroomidreceiptreceipttypeeventid
@client.route("/_matrix/client/v3/rooms/<room>/receipt/<type>/<event>", methods=['POST'])
def send_read_receipt(room, type, event):
return jsonify({})

View File

@@ -46,8 +46,8 @@ def synapse_user_list():
def synapse_user_info(user_id):
if request.method == 'GET':
return jsonify({"name":f"@vona:{server_name}","displayname":"Vona","threepids":[],"avatar_url":f"mxc://{server_name}/cat","is_guest":0,"admin":0,"deactivated":0,"erased":False,"shadow_banned":0,"creation_ts":the_funny_number,"last_seen_ts":the_funny_number,"appservice_id":the_funny_number,"consent_server_notice_sent":the_funny_number,"consent_version":the_funny_number,"consent_ts":the_funny_number,"external_ids":[],"user_type":"vona","locked":False,"suspended":False})
elif request.method == 'PUT':
return jsonify({}), 201
return jsonify({}), 201
@custom.route('/_synapse/admin/v1/whois/<user_id>')
def synapse_whois(user_id):
@@ -85,8 +85,8 @@ def synapse_account_data(user_id):
def synapse_account_media(user_id):
if request.method == 'GET':
return jsonify({"media": [{"created_ts":the_funny_number,"last_access_ts":the_funny_number,"media_id":"cat","media_length":the_funny_number,"media_type":"image/jpeg","quarantined_by":"null","safe_from_quarantine":False,"upload_name":"cat.jpg"}], "total": the_funny_number})
elif request.method == 'DELETE':
return jsonify({"deleted_media": ["cat"], "total": the_funny_number})
return jsonify({"deleted_media": ["cat"], "total": the_funny_number})
@custom.route('/_synapse/admin/v1/users/<user_id>/login', methods=['POST'])
def synapse_account_login(user_id):
@@ -100,8 +100,8 @@ def synapse_stupid_mas_bullshit(user_id):
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})
elif request.method == 'POST':
return jsonify({})
return jsonify({})
@custom.route('/_synapse/admin/v2/users/<user_id>/delete_devices', methods=['POST'])
def synapse_delete_devices(user_id):
@@ -111,8 +111,8 @@ def synapse_delete_devices(user_id):
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}"})
else:
return jsonify({})
return jsonify({})
@custom.route('/_synapse/admin/v1/users/<user_id>/pushers')
def synapse_pushers(user_id):
@@ -154,8 +154,8 @@ def synapse_experimental_features(user_id):
def synapse_register():
if request.method == 'GET':
return jsonify({"nonce": os.urandom(16).hex()})
elif request.method == 'POST':
return jsonify({"access_token": f"@vona:{server_name}"})
return jsonify({"access_token": f"@vona:{server_name}"})
@custom.route('/_synapse/admin/v1/join/<roomId>', methods=['POST'])
def synapse_membership_manipulation(roomId):
@@ -248,12 +248,12 @@ def synapse_bg_update_start_job():
@custom.route('/_synapse/admin/v1/event_reports')
def synapse_event_reports():
return jsonify({"event_reports": [{"event_id": f"${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})
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})
@custom.route('/_synapse/admin/v1/event_reports/<report_id>', methods=['GET', 'DELETE'])
def synapse_interact_with_reported_event(report_id):
if request.method == 'GET':
return jsonify({"event_id": f"${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.<br />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 &quot;This is the lettuce you eat at Burger King.&quot;. 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 &quot;Oh, I know who that is, hes getting fired&quot;. Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."},"depth": the_funny_number,"hashes": {"sha256": f"${make_event_id()}"},"origin": server_name,"origin_server_ts": the_funny_number,"prev_events": [f"${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}"})
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.<br />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 &quot;This is the lettuce you eat at Burger King.&quot;. 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 &quot;Oh, I know who that is, hes getting fired&quot;. 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({})
@@ -285,7 +285,7 @@ def dendrite_reindex():
# Conduwuit
@custom.route('/_conduwuit/local_user_count')
def conduwuit_user_count():
return jsonify({"count": the_funny_number})
return jsonify({"count": 1})
@custom.route('/_conduwuit/server_version')
def conduwuit_server_version():
@@ -294,7 +294,7 @@ def conduwuit_server_version():
# Continuwuity
@custom.route('/_continuwuity/local_user_count')
def continuwuity_user_count():
return jsonify({"count": the_funny_number})
return jsonify({"count": 1})
@custom.route('/_continuwuity/server_version')
def continuwuity_server_version():
@@ -303,7 +303,7 @@ def continuwuity_server_version():
# Tuwunel
@custom.route('/_tuwunel/local_user_count')
def tuwunel_user_count():
return jsonify({"count": the_funny_number})
return jsonify({"count": 1})
@custom.route('/_tuwunel/server_version')
def tuwunel_server_version():

View File

@@ -2,11 +2,12 @@ import nacl.signing
import hashlib
import base64
import config
import copy
import json
import re
import os
vona_version = "1.2.5"
vona_version = "1.3.0"
def canonical_json(value):
@@ -28,6 +29,35 @@ def sign_json(data):
decoded_key = base64.b64decode(base64_key)
signing_key = nacl.signing.SigningKey(decoded_key)
signed_message = signing_key.sign(canonical_json(data))
signature = signed_message.signature
key_version = parts[1]
signature_base64 = base64.b64encode(signature).decode("utf-8").rstrip("=")
signed_json = {
**data,
"signatures": {
config.server_name: {
f"{parts[0]}:{key_version}": signature_base64,
},
},
}
return signed_json
def sign_json_without_discard(data):
parts = config.signing_key.split()
base64_key = parts[2]
while len(base64_key) % 4 != 0:
base64_key += "="
decoded_key = base64.b64decode(base64_key)
signing_key = nacl.signing.SigningKey(decoded_key)
unsigned_keys = {key: data[key] for key in list(data.keys()) if key == "unsigned"}
for key in unsigned_keys:
del data[key]
@@ -48,23 +78,35 @@ def sign_json(data):
else:
data["signatures"] = {config.server_name: new_signature}
data.update(unsigned_keys)
return data
def make_event_id():
return re.sub(r"[\/+=]", "_", base64.b64encode(os.urandom(32)).decode("utf-8"))[:44]
event_id = "$"
event_id += re.sub(
r"[\/+=]",
"_",
base64.b64encode(
os.urandom(32),
).decode("utf-8"),
).rstrip("=")[:44]
event_id += ":" + config.server_name
return event_id
def hash_event(input) -> str:
input.pop("signatures", None)
input.pop("unsigned", None)
def event_hash(event_object):
event_object = dict(event_object)
sha256_hash = hashlib.sha256(canonical_json(input)).digest()
base64_encoded = base64.b64encode(sha256_hash)
event_object.pop("unsigned", None)
event_object.pop("signatures", None)
event_object.pop("hashes", None)
return base64_encoded.decode().rstrip("=")
event_json_bytes = canonical_json(event_object)
return base64.b64encode(hashlib.sha256(event_json_bytes).digest()).decode("utf-8")
def pubkey() -> str:
@@ -107,8 +149,75 @@ def make_auth_header(destination, method, path, content=None) -> str:
key,
sig,
),
'utf-8'
"utf-8",
)
)
return authorization_headers[0].decode('utf-8')
return authorization_headers[0].decode("utf-8")
def redact_event(event):
# Returns a redacted event as per
# the algorithm for v1 rooms.
allowed_keys = [
"event_id",
"type",
"room_id",
"sender",
"state_key",
"content",
"hashes",
"signatures",
"depth",
"prev_events",
"prev_state",
"auth_events",
"origin",
"origin_server_ts",
"membership",
]
redacted_event = {k: v for k, v in event.items() if k in allowed_keys}
if "type" in redacted_event and "content" in redacted_event:
event_type = redacted_event["type"]
content_key_rules = {
"m.room.member": ["membership"],
"m.room.create": ["creator"],
"m.room.join_rules": ["join_rule"],
"m.room.power_levels": [
"ban",
"events",
"events_default",
"kick",
"redact",
"state_default",
"users",
"users_default",
],
"m.room.aliases": ["aliases"],
"m.room.history_visibility": ["history_visibility"],
}
if event_type in content_key_rules:
allowed_content_keys = content_key_rules[event_type]
redacted_event["content"] = {
k: v
for k, v in redacted_event["content"].items()
if k in allowed_content_keys
}
else:
redacted_event["content"] = {}
return redacted_event
def hash_and_sign_event(event_object):
content_hash = event_hash(event_object)
event_object["hashes"] = {"sha256": content_hash}
stripped_object = redact_event(event_object)
signed_object = sign_json(stripped_object)
event_object["signatures"] = signed_object["signatures"]
return event_object

View File

@@ -1,5 +1,6 @@
from flask import Blueprint, jsonify, request
from config import server_name, the_funny_number
import time
identity = Blueprint("matrix_identity", __name__)
@@ -7,13 +8,14 @@ identity = Blueprint("matrix_identity", __name__)
# I'm pretty sure only Element uses this,
# but oh well.
# https://spec.matrix.org/v1.15/identity-service-api/#api-version-check
# https://spec.matrix.org/v1.16/identity-service-api/#api-version-check
@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"]})
# https://spec.matrix.org/v1.15/identity-service-api/#authentication
# https://spec.matrix.org/v1.16/identity-service-api/#authentication
@identity.route("/_matrix/identity/v2/account")
async def account_info():
return jsonify({"user_id": f"@vona:{server_name}"})
@@ -27,7 +29,7 @@ async def register():
return jsonify({"token":"vona"})
# https://spec.matrix.org/v1.15/identity-service-api/#terms-of-service
# https://spec.matrix.org/v1.16/identity-service-api/#terms-of-service
@identity.route('/_matrix/identity/v2/terms', methods=['GET', 'POST'])
async def policies():
if request.method == 'GET':
@@ -36,13 +38,13 @@ async def policies():
return jsonify({})
# https://spec.matrix.org/v1.15/identity-service-api/#status-check
# 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.15/identity-service-api/#key-management
# 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})
@@ -56,7 +58,7 @@ async def get_key(key):
return jsonify({"errcode":"M_NOT_FOUND","error":"The public key was not found"}), 404
# https://spec.matrix.org/v1.15/identity-service-api/#association-lookup
# 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"})
@@ -65,13 +67,13 @@ async def hash_details():
async def lookup():
req = request.json
if 'addresses' in req:
if "addresses" in req:
return jsonify({"mappings": {req['addresses'][0]: f"@vona:{server_name}"}})
else:
return jsonify({"errcode": "M_INVALID_PEPPER","error": "Invalid pepper"})
# https://spec.matrix.org/v1.15/identity-service-api/#establishing-associations
# 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)})
@@ -90,11 +92,19 @@ async def submit_phone_token():
@identity.route('/_matrix/identity/v2/3pid/bind', methods=['POST'])
async def threepid_bind():
# We have to do signature stuff here,
# this will be implemented properly
# when I actually get sigs working.
if "mxid" in request.get_json():
mxid = request.get_json()["mxid"]
else:
mxid = f"@vona:{server_name}"
return jsonify({})
return jsonify(globals.sign_json({
"address": "abuse@matrix.org",
"medium": "email",
"mxid": mxid,
"not_after": int(time.time() * 1000 + 604800000),
"not_before": int(time.time() * 1000 - 604800000),
"ts": int(time.time() * 1000)
}))
@identity.route('/_matrix/identity/v2/3pid/unbind', methods=['POST'])
async def threepid_unbind():
@@ -107,16 +117,27 @@ async def threepid_validated():
return jsonify({"address":"abuse@matrix.org","medium":"email","validated_at":the_funny_number})
# https://spec.matrix.org/v1.15/identity-service-api/#invitation-storage
# 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"})
# https://spec.matrix.org/v1.15/identity-service-api/#ephemeral-invitation-signing
# 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():
# the spec sez this is insecure :trole:
return jsonify({
"errcode":"M_TERMS_NOT_SIGNED","error":"Please accept our updated terms of service before continuing"
}), 403
# 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"
}), 404

View File

@@ -1,12 +1,238 @@
from flask import Flask, jsonify, Response, request, send_file, abort, Blueprint
from config import *
import globals
import httpx
import json
import time
import os
server = Blueprint('matrix_server', __name__)
# NOTE: Synapse rejects this for
# whatever reason. Still looking
# into this issue.
def send_join(request, roomId):
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(),
]
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()
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,
"event": remote_join,
"members_omitted": False,
"servers_in_room": [server_name],
"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"}})
@@ -35,7 +261,7 @@ def room_query():
def download_media(media_id):
# Auth media requires this to be
# multipart despite not even using
# it for anything. Minor annoyance.
# it for anything. Minor annoyance.
with open(cat, 'rb') as img_file:
image_data = img_file.read()
@@ -60,11 +286,11 @@ def thumbnail_media(media_id):
@server.route('/_matrix/federation/v1/send_join/<roomId>/<eventId>', methods=['PUT'])
def send_join_v1(roomId, eventId):
abort(500)
return jsonify([200, send_join(request, roomId)])
@server.route('/_matrix/federation/v2/send_join/<roomId>/<eventId>', methods=['PUT'])
def send_join_v2(roomId, eventId):
abort(500)
return jsonify(send_join(request, roomId))
@server.route('/_matrix/federation/v1/make_join/<roomId>/<userId>')
def make_join(roomId, userId):
@@ -89,26 +315,31 @@ def room_directory():
return jsonify(room_dir_room)
# https://spec.matrix.org/v1.15/server-server-api/#transactions
# https://spec.matrix.org/v1.16/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
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/v1.16/rooms/v2/#authorization-rules
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] = {}
if "pdus" in parsed_data:
for pdu in parsed_data["pdus"]:
if "event_id" in pdu:
response["pdus"][pdu["event_id"]] = {}
return jsonify(response)
@@ -120,8 +351,8 @@ def user_profile():
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({"errcode": "M_NOT_FOUND","error": "The requested profile key does not exist."}), 404
return jsonify({"avatar_url": f"mxc://{server_name}/cat","displayname": "Vona"})
@@ -138,20 +369,20 @@ def user_keys():
return jsonify({"device_keys":{f"@vona:{server_name}":{}}})
# https://spec.matrix.org/v1.15/server-server-api/#inviting-to-a-room
# https://spec.matrix.org/v1.16/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)
return invite_user(request.data)
@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)
return [200, invite_user(request.data)]
def invite_user(data, endpVer):
def invite_user(data):
try:
invite_data = json.loads(data)
except:
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}), 400
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}),
if "event" in invite_data:
if "room_version" in invite_data:
@@ -161,22 +392,28 @@ def invite_user(data, endpVer):
"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"})
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>')
def space_hierachy(roomId):
room = room_dir_room['chunk'][0]