Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a5b34fb4a8
|
|||
|
2212453a96
|
|||
|
d9583f3472
|
|||
|
3d9ff622fd
|
|||
|
945f92e25f
|
|||
|
29ecfc0387
|
|||
|
4bdeca6139
|
|||
|
b8f8a1a7b1
|
|||
|
35896ed1ee
|
|||
|
da82f492e8
|
|||
|
17d5a6458e
|
|||
|
ad4c5cc5d2
|
|||
|
8999a5e414
|
|||
|
2fa647f7e7
|
|||
|
1c87e8b5a5
|
|||
|
9d68082764
|
|||
|
32cdb239ff
|
|||
|
626c865461
|
|||
|
fe5c0e23b5
|
|||
|
09cb708594
|
|||
|
ad72886fb3
|
|||
|
9b6988a029
|
|||
|
2f891508ab
|
|||
|
f23a74de5c
|
|||
|
b71096663c
|
|||
|
1fce4d5ba1
|
|||
|
c1a4de06ce
|
|||
|
1e6c7adb41
|
|||
|
6cc08fcb02
|
|||
|
458d69da84
|
|||
|
8c513ac738
|
|||
|
3ec0f2ab4f
|
|||
|
b5e74af72f
|
|||
|
d2e61a3b07
|
|||
|
24133be98c
|
|||
|
647916b749
|
|||
|
6056268a2e
|
|||
|
b4d2f8e728
|
|||
|
2cb6fd005a
|
|||
|
fe43b77611
|
|||
|
492ffc62e9
|
|||
|
9ca4c913f3
|
|||
|
6dd4fb04e3
|
|||
|
f771130a16
|
|||
|
9aa2e062e5
|
|||
|
c8d6f57e5b
|
|||
|
753625ad5c
|
|||
|
74a36e0b34
|
|||
|
b178011ad9
|
|||
|
76f89beb29
|
|||
|
7e8eb6de7f
|
|||
|
4e28507dea
|
|||
|
357d3b7429
|
|||
|
8a2bae706e
|
|||
|
4603ed86ac
|
|||
|
4a6e65226e
|
|||
|
fc9787b8ec
|
|||
|
2dbec63ff7
|
|||
|
2021fc027b
|
|||
|
e0115fe8db
|
18
.pre-commit-config.yaml
Normal file
18
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
default_install_hook_types:
|
||||
- pre-commit
|
||||
- commit-msg
|
||||
default_stages:
|
||||
- pre-commit
|
||||
- manual
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.2
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [ "--fix" ]
|
||||
|
||||
- repo: https://foundry.fsky.io/vel/ty-pre-commit
|
||||
rev: 0.0.1-alpha.25
|
||||
hooks:
|
||||
- id: ty-check
|
||||
44
MSCs.md
Normal file
44
MSCs.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# MSCs
|
||||
List of MSCs implemented by Vona.
|
||||
|
||||
Merged MSCs:
|
||||
|
||||
* [MSC971: Groups](https://github.com/matrix-org/matrix-spec-proposals/issues/971)
|
||||
* [MSC1708: .well-known for server name resolution](https://github.com/velikopter/matrix-spec-proposals/blob/edutypes/proposals/1708-well-known-for-federation.md)
|
||||
* [MSC1753: C2S Capabilities API](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1753-capabilities.md)
|
||||
* [MSC1794: Federation v2 Invite API](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1794-federation-v2-invites.md)
|
||||
* [MSC1802: Better format for send_join and send_leave](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1802-standardised-federation-response-format.md)
|
||||
* [MSC1812: Federation Make Membership Room Version](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1812-federation-make-membership.md)
|
||||
* [MSC1929: Homeserver Admin Contact and Support page](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1929-admin-contact.md)
|
||||
* [MSC2320: Versions information for identity servers](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2320-identity-versions.md)
|
||||
* [MSC2659: Application service ping endpoint](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2659-appservice-ping.md)
|
||||
* [MSC2713: Remove deprecated Identity Service endpoints](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2713-remove-deprecated-identity-endpoints.md)
|
||||
* [MSC3266: Room Summary API](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3266-room-summary.md)
|
||||
* [MSC3383: Include destination in X-Matrix Auth Header](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3383-fed-auth-destination.md)
|
||||
* [MSC4133: Extended User Profile API](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md)
|
||||
* [MSC4151: Reporting rooms](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/4151-report-room.md)
|
||||
* [MSC4260: Reporting users](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/4260-report-user.md)
|
||||
|
||||
|
||||
Non-merged MSCs:
|
||||
|
||||
* [MSC2666: Get rooms in common with another user](https://github.com/matrix-org/matrix-spec-proposals/pull/2666)
|
||||
* [MSC4358: Out-of-room server discovery](https://github.com/matrix-org/matrix-spec-proposals/pull/4358)
|
||||
* [MSC4367: via routes in the published room directory](https://github.com/matrix-org/matrix-spec-proposals/pull/4367)
|
||||
* [MSC4370: Federation endpoint for retrieving current extremities](https://github.com/matrix-org/matrix-spec-proposals/pull/4370)
|
||||
* [MSC4373: Server opt-out of specific EDU types](https://github.com/matrix-org/matrix-spec-proposals/pull/4373)
|
||||
* [MSC4375: Admin Room Management](https://github.com/matrix-org/matrix-spec-proposals/pull/4375)
|
||||
|
||||
|
||||
Room version MSCs:
|
||||
|
||||
* [MSC1759: Room V2](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1759-rooms-v2.md)
|
||||
* [MSC1659: Room V3](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1659-event-id-as-hashes.md)
|
||||
* [MSC2002: Room V4](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2002-rooms-v4.md)
|
||||
* [MSC2077: Room V5](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2077-rooms-v5.md)
|
||||
* [MSC2240: Room V6](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2240-rooms-v6.md)
|
||||
* [MSC2998: Room V7](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2998-rooms-v7.md)
|
||||
* [MSC3289: Room V8](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3289-rooms-v8.md)
|
||||
* [MSC3375: Room V9](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3375-room-v9.md)
|
||||
* [MSC3604: Room V10](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3604-rooms-v10.md)
|
||||
* [MSC3820: Room V11](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/3820-rooms-v11.md)
|
||||
3
SECURITY.md
Normal file
3
SECURITY.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Security
|
||||
If you believe you have found a vulnerability in Vona, please email vel@riseup.net or DM `@vel:faelix.im` on Matrix.
|
||||
Do not disclose the details of the vulnerability until a public fix has been made.
|
||||
2
TODO.md
Normal file
2
TODO.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- use `resolvematrix` async instead of sync
|
||||
- implement groups over federation
|
||||
@@ -2,12 +2,13 @@
|
||||
name = "vona"
|
||||
description = "Flazing bast Matrix homeserver"
|
||||
license = {text = "Velicense"}
|
||||
requires-python = ">=3.12"
|
||||
requires-python = "<4,>=3.12"
|
||||
|
||||
dependencies = [
|
||||
"httpx (>=0.28.1,<0.29.0)",
|
||||
"pynacl (>=1.6.0,<2.0.0)",
|
||||
"flask[async] (>=3.1.2,<4.0.0)",
|
||||
"resolvematrix @ git+https://codeberg.org/timedout/resolvematrix.git",
|
||||
]
|
||||
|
||||
authors = [
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# read __main__.py
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
from flask import Flask, jsonify, request, redirect
|
||||
import vona.globals as globals
|
||||
from datetime import datetime
|
||||
import vona.config as config
|
||||
import threading
|
||||
import logging
|
||||
import os
|
||||
|
||||
from flask import (
|
||||
Flask,
|
||||
jsonify,
|
||||
request,
|
||||
redirect,
|
||||
abort,
|
||||
)
|
||||
|
||||
from vona.federation import server
|
||||
from vona.custom import custom
|
||||
@@ -24,9 +33,20 @@ app.register_blueprint(server)
|
||||
app.register_blueprint(apps)
|
||||
|
||||
@app.before_request
|
||||
async def preflight():
|
||||
async def validate_json():
|
||||
if request.method == "OPTIONS":
|
||||
return "", 204
|
||||
return "", 200
|
||||
|
||||
elif request.method in ["PUT", "POST", "PATCH"]:
|
||||
if "media" in request.path:
|
||||
# Don't check media uploads
|
||||
return
|
||||
|
||||
try:
|
||||
request.get_json(force=True)
|
||||
except Exception:
|
||||
return jsonify({"error": "Content not JSON.", "errcode": "M_NOT_JSON"}), 400
|
||||
|
||||
|
||||
@app.after_request
|
||||
async def handle_logging(response):
|
||||
@@ -36,7 +56,6 @@ async def handle_logging(response):
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, HEAD, POST, PUT, DELETE, OPTIONS"
|
||||
|
||||
if request.method == "OPTIONS":
|
||||
# Discard logs for OPTIONS
|
||||
return response
|
||||
|
||||
origin = "unknown"
|
||||
@@ -44,13 +63,34 @@ async def handle_logging(response):
|
||||
try:
|
||||
if "Authorization" in request.headers:
|
||||
if request.headers["Authorization"].split()[0] == "X-Matrix":
|
||||
origin = request.headers["Authorization"].split('origin="')[1].split('"')[0]
|
||||
origin = (
|
||||
request.headers["Authorization"]
|
||||
.split("origin=")[1]
|
||||
.split(",")[0]
|
||||
)
|
||||
|
||||
while '"' in origin:
|
||||
origin = origin.replace('"', "")
|
||||
|
||||
if origin == config.server_name:
|
||||
return response
|
||||
|
||||
else:
|
||||
origin = "client"
|
||||
except:
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f'[{origin}] [{request.remote_addr}] [{datetime.now().strftime("%d/%b/%Y:%H:%M:%S")}] {request.method} {request.full_path} {response.status_code}')
|
||||
if request.path.startswith("/.well-known/matrix/"):
|
||||
return response
|
||||
|
||||
print(
|
||||
f"[{origin}] " +
|
||||
f'[{datetime.now().strftime("%d/%b/%Y:%H:%M:%S")}] ' +
|
||||
request.method + " " +
|
||||
request.full_path.rstrip("?") + " " +
|
||||
str(response.status_code)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -100,4 +140,23 @@ async def client():
|
||||
})
|
||||
|
||||
|
||||
def federation_self_test():
|
||||
try:
|
||||
resp = globals.http_client().get(
|
||||
path="/",
|
||||
destination=config.server_name,
|
||||
)
|
||||
|
||||
if str(resp.status_code).startswith("5") or str(resp.status_code).startswith("4"):
|
||||
print(f"[FATL] Federation self-test failed: status code is not acceptable ({str(resp.status_code)})")
|
||||
os._exit(1)
|
||||
|
||||
print("[INFO] Federation self-test OK")
|
||||
except Exception as e:
|
||||
print(f"[FATL] Federation self-test failed: {e}")
|
||||
os._exit(1)
|
||||
|
||||
threading.Thread(target=federation_self_test).start()
|
||||
|
||||
|
||||
app.run(host=config.addr, port=config.port)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from flask import Blueprint, jsonify
|
||||
from vona.config import the_funny_number
|
||||
import asyncio
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
apps = Blueprint("appservice", __name__)
|
||||
|
||||
# This implements both being a homeserver and
|
||||
@@ -10,6 +14,7 @@ apps = Blueprint("appservice", __name__)
|
||||
# Endpoints invoked by the homeserver are put
|
||||
# lower than ones invoked by the appservice.
|
||||
|
||||
|
||||
@apps.route("/_matrix/client/v1/appservice/<app>/ping", methods=["POST"])
|
||||
async def homeserver_ping(app):
|
||||
# Sleeping here makes it more realistic
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
from flask import Blueprint, jsonify, request, send_file
|
||||
from vona.federation import send_join
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import os
|
||||
|
||||
client = Blueprint("c2s", __name__)
|
||||
from .groups import groups
|
||||
from .admin import admin
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
request,
|
||||
send_file,
|
||||
)
|
||||
|
||||
client = Blueprint("client", __name__)
|
||||
|
||||
client.register_blueprint(groups)
|
||||
client.register_blueprint(admin)
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/account/password", methods=["POST"])
|
||||
@@ -22,16 +35,18 @@ client = Blueprint("c2s", __name__)
|
||||
@client.route("/_matrix/client/v3/logout", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/logout", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/invite", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<roomId>/leave", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/leave", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<roomId>/read_markers", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/read_markers", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/leave", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/rooms/<room>/leave", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/read_markers", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/rooms/<room>/read_markers", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/typing/<user>", methods=["PUT"])
|
||||
@client.route("/_matrix/client/api/v1/rooms/<room>/typing/<user>", methods=["PUT"])
|
||||
@client.route("/_matrix/client/v3/keys/device_signing/upload", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/receipt/<type>/<event>", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/users/<user>/report", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/voip/turnServer")
|
||||
@client.route("/_matrix/client/r0/voip/turnServer")
|
||||
@client.route("/_matrix/client/api/v1/voip/turnServer")
|
||||
@client.route("/_matrix/client/v3/rooms/<r>/report/<e>")
|
||||
@client.route("/_matrix/client/v3/rooms/<r>/report")
|
||||
@client.route("/_matrix/client/v3/users/<u>/report")
|
||||
@@ -47,61 +62,19 @@ async def spec_versions():
|
||||
["r0.6.1"] + [f"v1.{i}" for i in range(1, 17)]
|
||||
),
|
||||
"unstable_features": {
|
||||
"uk.half-shot.msc2666.query_mutual_rooms": True,
|
||||
"uk.half-shot.msc2666.mutual_rooms": True,
|
||||
"uk.half-shot.msc2666": True,
|
||||
"uk.timedout.msc4323": True
|
||||
"uk.timedout.msc4323": True,
|
||||
"uk.timedout.msc4375": True,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/admin/whois/<user>")
|
||||
@client.route("/_matrix/client/r0/admin/whois/<user>")
|
||||
async def whois(user):
|
||||
if userId.startswith("@"):
|
||||
return jsonify({
|
||||
"devices": {
|
||||
"": {
|
||||
"sessions": [{
|
||||
"connections": [{
|
||||
"ip": "127.0.0.1",
|
||||
"last_seen": config.the_funny_number,
|
||||
"user_agent": f"Vona/{globals.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/<user>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/v1/admin/suspend/<user>", 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/<user>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/v1/admin/lock/<user>", 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/<roomId>/members")
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/members")
|
||||
async def room_member_count(roomId):
|
||||
@client.route("/_matrix/client/api/v1/rooms/<room>/members")
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/members")
|
||||
@client.route("/_matrix/client/r0/rooms/<room>/members")
|
||||
async def room_members(room):
|
||||
return jsonify({
|
||||
"chunk": [{
|
||||
"content": {
|
||||
@@ -109,9 +82,9 @@ async def room_member_count(roomId):
|
||||
"displayname": "Vona",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": globals.make_event_id(),
|
||||
"event_id": globals.make_event_id(seed=room),
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"room_id": roomId,
|
||||
"room_id": room,
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": f"@vona:{config.server_name}",
|
||||
"type": "m.room.member",
|
||||
@@ -128,6 +101,7 @@ async def whoami():
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v2_alpha/register", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/register", methods=["POST"])
|
||||
@client.route("/_matrix/client/v1/register", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/register", methods=["POST"])
|
||||
@@ -144,7 +118,7 @@ async def register():
|
||||
"device_id": "VVOONNAA"
|
||||
})
|
||||
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return jsonify({
|
||||
@@ -161,6 +135,7 @@ async def register():
|
||||
|
||||
@client.route("/_matrix/client/r0/login", methods=["GET", "POST"])
|
||||
@client.route("/_matrix/client/v3/login", methods=["GET", "POST"])
|
||||
@client.route("/_matrix/client/api/v1/login", methods=["POST"])
|
||||
async def login():
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
@@ -231,7 +206,13 @@ async def capabilities():
|
||||
"9": "stable",
|
||||
"10": "stable",
|
||||
"11": "stable",
|
||||
"12": "stable"
|
||||
"12": "stable",
|
||||
"org.matrix.msc3757.10": "stable",
|
||||
"org.matrix.msc3757.11": "stable",
|
||||
"org.matrix.hydra.11": "stable",
|
||||
"org.matrix.msc3667": "stable",
|
||||
"org.matrix.msc3787": "stable",
|
||||
"org.matrix.msc4014": "unstable",
|
||||
},
|
||||
"org.matrix.msc3244.room_capabilities": {
|
||||
"knock": {
|
||||
@@ -276,6 +257,7 @@ async def filter(**kwargs):
|
||||
return jsonify({"filter_id": "vvvooonnnaaa"})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/join/<room>", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/join/<room>", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/join/<room>", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/join", methods=["POST"])
|
||||
@@ -284,6 +266,7 @@ async def join(room):
|
||||
return jsonify({"room_id": room})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/initialSync")
|
||||
@client.route("/_matrix/client/v3/initialSync")
|
||||
@client.route("/_matrix/client/v3/sync")
|
||||
@client.route("/_matrix/client/r0/sync")
|
||||
@@ -292,39 +275,12 @@ async def sync():
|
||||
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(
|
||||
room_state = globals.strip_state(
|
||||
send_join(
|
||||
bullshit,
|
||||
room
|
||||
)["state"]
|
||||
|
||||
room_state = await remove_keys(
|
||||
old_room_state,
|
||||
[
|
||||
"auth_events",
|
||||
"prev_events",
|
||||
"signatures",
|
||||
"hashes",
|
||||
"depth"
|
||||
]
|
||||
)
|
||||
|
||||
room_name = {
|
||||
@@ -345,7 +301,7 @@ async def sync():
|
||||
if "timeout" in request.args:
|
||||
try:
|
||||
wait_time = int(request.args.get("timeout")) / 1000
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
@@ -377,6 +333,78 @@ async def sync():
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/rooms/<room>/initialSync")
|
||||
async def room_initsync(room):
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
room_state = globals.strip_state(
|
||||
send_join(
|
||||
bullshit,
|
||||
room
|
||||
)["state"]
|
||||
)
|
||||
|
||||
for evt in room_state:
|
||||
evt["age"] = config.the_funny_number
|
||||
|
||||
return jsonify({
|
||||
"membership": "join",
|
||||
"room_id": room,
|
||||
"messages": {
|
||||
"chunk": [
|
||||
{
|
||||
"age": config.the_funny_number,
|
||||
"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 "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": globals.make_event_id(),
|
||||
"origin_server_ts": 1432804485886,
|
||||
"room_id": globals.make_event_id(),
|
||||
"type": "m.room.message",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"state": room_state
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/events")
|
||||
async def events():
|
||||
if "timeout" in request.args:
|
||||
try:
|
||||
await asyncio.sleep(int(request.args["timeout"]) / 1000)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return jsonify({
|
||||
"start": os.urandom(32).hex(),
|
||||
"end": os.urandom(32).hex(),
|
||||
"chunk": [
|
||||
{
|
||||
"age": config.the_funny_number,
|
||||
"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 "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": globals.make_event_id(),
|
||||
"origin_server_ts": 1432804485886,
|
||||
"room_id": globals.make_event_id(),
|
||||
"type": "m.room.message",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/rooms/<room>/send/<eventType>/<txnId>", methods=["POST", "PUT"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/send/<eventType>/<txnId>", methods=["POST", "PUT"])
|
||||
@client.route("/_matrix/client/r0/rooms/<room>/send/<eventType>/<txnId>", methods=["POST", "PUT"])
|
||||
async def send_message(room, eventType, txnId):
|
||||
@@ -431,10 +459,10 @@ async def refresh():
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/unstable/im.nheko.summary/rooms/<roomId>/summary")
|
||||
@client.route("/_matrix/client/unstable/im.nheko.summary/summary/<roomId>")
|
||||
@client.route("/_matrix/client/v1/room_summary/<roomId>")
|
||||
async def room_summary(roomId):
|
||||
@client.route("/_matrix/client/unstable/im.nheko.summary/rooms/<room>/summary")
|
||||
@client.route("/_matrix/client/unstable/im.nheko.summary/summary/<room>")
|
||||
@client.route("/_matrix/client/v1/room_summary/<room>")
|
||||
async def room_summary(room):
|
||||
return jsonify({
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
@@ -446,7 +474,7 @@ async def room_summary(roomId):
|
||||
"join_rule": "public",
|
||||
"room_type": "m.room",
|
||||
"membership": "join",
|
||||
"room_version": globals.room_version_from_id(roomId)
|
||||
"room_version": globals.room_version_from_id(room)
|
||||
})
|
||||
|
||||
|
||||
@@ -461,6 +489,7 @@ async def room_query(room):
|
||||
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/aliases")
|
||||
async def room_aliases(room):
|
||||
return jsonify({
|
||||
@@ -475,6 +504,7 @@ async def room_aliases(room):
|
||||
async def room_visibility(room):
|
||||
return jsonify({"visibility": "public"})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/search", methods=["POST"])
|
||||
async def search():
|
||||
room = globals.make_event_id().replace("$", "!")
|
||||
@@ -546,13 +576,15 @@ async def url_preview():
|
||||
"og:title": "cool cat"
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v1/media/preview_url")
|
||||
async def media_preview():
|
||||
response = send_file(config.cat)
|
||||
response.headers["Content-Disposition"] = f'inline; filename="cat.jpg"'
|
||||
response.headers["Content-Disposition"] = 'inline; filename="cat.jpg"'
|
||||
response.headers["Content-Type"] = "image/jpg"
|
||||
return response
|
||||
|
||||
|
||||
@client.route("/_matrix/media/v3/upload", methods=["POST"])
|
||||
@client.route("/_matrix/media/r0/upload", methods=["POST"])
|
||||
@client.route("/_matrix/media/v1/create", methods=["POST"])
|
||||
@@ -565,9 +597,10 @@ async def media_config():
|
||||
return jsonify({"m.upload.size": config.the_funny_number * 69420})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/profile/<userId>/<key>", methods=["GET", "PUT", "DELETE"])
|
||||
@client.route("/_matrix/client/r0/profile/<userId>/<key>", methods=["GET", "PUT", "DELETE"])
|
||||
async def profile_keys(userId, key):
|
||||
@client.route("/_matrix/client/api/v1/profile/<user>/<key>", methods=["GET", "PUT", "DELETE"])
|
||||
@client.route("/_matrix/client/v3/profile/<user>/<key>", methods=["GET", "PUT", "DELETE"])
|
||||
@client.route("/_matrix/client/r0/profile/<user>/<key>", methods=["GET", "PUT", "DELETE"])
|
||||
async def profile_keys(user, key):
|
||||
if request.method == "GET":
|
||||
if key == "avatar_url":
|
||||
return jsonify({"avatar_url": f"mxc://{config.server_name}/cat"})
|
||||
@@ -581,18 +614,20 @@ async def profile_keys(userId, key):
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@client.route("/_matrix/client/v3/profile/<userId>")
|
||||
@client.route("/_matrix/client/r0/profile/<userId>")
|
||||
async def user_profile(userId):
|
||||
|
||||
@client.route("/_matrix/client/v3/profile/<user>")
|
||||
@client.route("/_matrix/client/r0/profile/<user>")
|
||||
async def user_profile(user):
|
||||
return jsonify({
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"displayname": "Vona"
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/rooms/<roomId>/messages")
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/messages")
|
||||
async def room_messages(roomId):
|
||||
@client.route("/_matrix/client/api/v1/rooms/<room>/messages")
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/messages")
|
||||
@client.route("/_matrix/client/r0/rooms/<room>/messages")
|
||||
async def room_messages(room):
|
||||
return jsonify({
|
||||
"chunk": [{
|
||||
"content": {
|
||||
@@ -603,7 +638,7 @@ async def room_messages(roomId):
|
||||
},
|
||||
"event_id": globals.make_event_id(),
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"room_id": roomId,
|
||||
"room_id": room,
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"type": "m.room.message"
|
||||
}],
|
||||
@@ -611,6 +646,7 @@ async def room_messages(roomId):
|
||||
"start": f"{os.urandom(16).hex()}"
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/keys/query", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/keys/query", methods=["POST"])
|
||||
async def query_keys():
|
||||
@@ -622,6 +658,7 @@ async def query_keys():
|
||||
"user_signing_keys": user
|
||||
})
|
||||
|
||||
|
||||
@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"])
|
||||
@@ -639,19 +676,29 @@ async def mutual_rooms():
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/presence/<user>/status", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/r0/presence/<user>/status", methods=["GET", "PUT"])
|
||||
async def presence(user):
|
||||
if request.method == "PUT":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"presence": "online"
|
||||
})
|
||||
return jsonify({"presence": "online"})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/publicRooms", methods=["GET", "POST"])
|
||||
@client.route("/_matrix/client/v3/publicRooms", methods=["GET", "POST"])
|
||||
@client.route("/_matrix/client/r0/publicRooms", methods=["GET", "POST"])
|
||||
async def room_directory():
|
||||
return jsonify(globals.room_dir)
|
||||
|
||||
|
||||
@client.route("/_matrix/client/api/v1/rooms/<room>/state/<evttype>/<key>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/api/v1/rooms/<room>/state/<evttype>/", methods=["GET", "PUT"])
|
||||
async def state(room, evttype, key=None):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": f"Event with type {evttype} and key {key} not found"
|
||||
}), 404
|
||||
|
||||
return jsonify({"event_id": os.urandom(5).hex()})
|
||||
|
||||
153
vona/client/admin.py
Normal file
153
vona/client/admin.py
Normal file
@@ -0,0 +1,153 @@
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
from vona.federation import (
|
||||
send_join,
|
||||
bullshit,
|
||||
)
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
request,
|
||||
)
|
||||
|
||||
admin = Blueprint("admin", __name__)
|
||||
|
||||
# This implements misc standard admin APIs.
|
||||
|
||||
|
||||
@admin.route("/_matrix/client/v3/admin/whois/<user>")
|
||||
@admin.route("/_matrix/client/r0/admin/whois/<user>")
|
||||
async def whois(user):
|
||||
if user.startswith("@"):
|
||||
return jsonify({
|
||||
"devices": {
|
||||
"": {
|
||||
"sessions": [{
|
||||
"connections": [{
|
||||
"ip": "127.0.0.1",
|
||||
"last_seen": config.the_funny_number,
|
||||
"user_agent": f"Vona/{globals.version}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
},
|
||||
"user_id": user
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"errcode": "M_INVALID_PARAM",
|
||||
"error": "Expected UserID string to start with '@'"
|
||||
})
|
||||
|
||||
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/<user>", methods=["GET", "PUT"])
|
||||
@admin.route("/_matrix/client/v1/admin/suspend/<user>", 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})
|
||||
|
||||
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4323/admin/lock/<user>", methods=["GET", "PUT"])
|
||||
@admin.route("/_matrix/client/v1/admin/lock/<user>", 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})
|
||||
|
||||
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4375/admin/rooms")
|
||||
@admin.route("/_matrix/client/v1/admin/rooms")
|
||||
async def rooms():
|
||||
return jsonify({
|
||||
"chunk": [
|
||||
f"!qa:{config.server_name}",
|
||||
f"!br:{config.server_name}",
|
||||
f"!3:{config.server_name}",
|
||||
f"!D:{config.server_name}",
|
||||
f"!U:{config.server_name}",
|
||||
f"!f:{config.server_name}",
|
||||
f"!gx:{config.server_name}",
|
||||
f"!hx:{config.server_name}",
|
||||
f"!iy:{config.server_name}",
|
||||
f"!p0:{config.server_name}",
|
||||
f"!jZ:{config.server_name}",
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4375/admin/rooms/<room>")
|
||||
@admin.route("/_matrix/client/v1/admin/rooms/<room>")
|
||||
async def room_state(room):
|
||||
if ":" not in room:
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Unknown room"
|
||||
}), 404
|
||||
else:
|
||||
if room.split(":")[1] != config.server_name:
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Unknown room"
|
||||
}), 404
|
||||
|
||||
state = globals.strip_state(
|
||||
send_join(bullshit, room)["state"]
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"state": [state]
|
||||
})
|
||||
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4375/admin/rooms/<room>/evacuate", methods=["POST"])
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4375/admin/rooms/<room>", methods=["DELETE"])
|
||||
@admin.route("/_matrix/client/v1/admin/rooms/<room>/evacuate", methods=["POST"])
|
||||
@admin.route("/_matrix/client/v1/admin/rooms/<room>", methods=["DELETE"])
|
||||
async def evacuate_room(room):
|
||||
req = request.json
|
||||
|
||||
if (
|
||||
isinstance(req, dict)
|
||||
and "background" in req
|
||||
and isinstance(req["background"], bool)
|
||||
):
|
||||
background = req["background"]
|
||||
else:
|
||||
background = True
|
||||
|
||||
resp = {
|
||||
"background": background,
|
||||
}
|
||||
|
||||
if not background:
|
||||
resp["removed"] = config.the_funny_number
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4375/admin/rooms/<room>/evacuate/status")
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4375/admin/rooms/<room>/delete/status")
|
||||
@admin.route("/_matrix/client/v1/admin/rooms/<room>/evacuate/status")
|
||||
@admin.route("/_matrix/client/v1/admin/rooms/<room>/delete/status")
|
||||
async def evacuate_status(room):
|
||||
return jsonify({
|
||||
"started_at": globals.get_time(), # everything all at once always now!!!
|
||||
"total": config.the_funny_number,
|
||||
"evacuated": config.the_funny_number,
|
||||
"failed": config.the_funny_number,
|
||||
})
|
||||
|
||||
|
||||
@admin.route("/_matrix/client/unstable/uk.timedout.msc4375/admin/rooms/<room>/blocked", methods=["PUT"])
|
||||
@admin.route("/_matrix/client/v1/admin/rooms/<room>/blocked", methods=["PUT"])
|
||||
async def block(room):
|
||||
return jsonify({})
|
||||
119
vona/client/groups.py
Normal file
119
vona/client/groups.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
request,
|
||||
)
|
||||
|
||||
groups = Blueprint("groups", __name__)
|
||||
|
||||
# This implements the C2S API for groups
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/self/update_publicity", methods=["PUT"])
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/self/accept_invite", methods=["PUT"])
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/self/leave", methods=["PUT"])
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/admin/users/remove/<user>", methods=["PUT"])
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/admin/rooms/<room>", methods=["PUT", "DELETE"])
|
||||
async def empty(**k):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/publicised_groups/<user>")
|
||||
@groups.route("/_matrix/client/r0/joined_groups")
|
||||
async def joined_groups(user=None):
|
||||
return jsonify({
|
||||
"groups": [f"+vona:{config.server_name}"]
|
||||
})
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/summary")
|
||||
async def summary(group):
|
||||
return jsonify({
|
||||
"profile": {
|
||||
"name": "Vona",
|
||||
"short_description": "",
|
||||
"long_description": "",
|
||||
"avatar_url": "",
|
||||
"is_public": 1,
|
||||
"is_openly_joinable": True
|
||||
},
|
||||
"users_section": {
|
||||
"users": [],
|
||||
"roles": {},
|
||||
"total_user_count_estimate": 0
|
||||
},
|
||||
"rooms_section": {
|
||||
"rooms": [],
|
||||
"categories": {},
|
||||
"total_room_count_estimate": 0
|
||||
},
|
||||
"user": {
|
||||
"membership": "join",
|
||||
"is_public": 1,
|
||||
"is_privileged": 1,
|
||||
"is_publicised": True
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/rooms")
|
||||
async def rooms(group):
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/invited_users")
|
||||
async def invited_users(group):
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_user_count_estimate": 0
|
||||
})
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/users")
|
||||
async def users(group):
|
||||
return jsonify({
|
||||
"chunk": [
|
||||
{
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"displayname": "Vona",
|
||||
"avatar_url": None,
|
||||
"is_public": True,
|
||||
"is_privileged": True,
|
||||
"attestation": globals.sign_json({
|
||||
"group_id": group,
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"valid_until_ms": globals.get_time()
|
||||
})
|
||||
}
|
||||
],
|
||||
"total_user_count_estimate": 1
|
||||
})
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/groups/<group>/profile", methods=["GET", "POST"])
|
||||
async def profile(group):
|
||||
if request.method == "POST":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"name": "Vona",
|
||||
"short_description": "",
|
||||
"long_description": "",
|
||||
"avatar_url": "",
|
||||
"is_public": 1,
|
||||
"is_openly_joinable": True
|
||||
})
|
||||
|
||||
|
||||
@groups.route("/_matrix/client/r0/create_group", methods=["POST"])
|
||||
async def create_group():
|
||||
return jsonify({
|
||||
"group_id": f"+vona:{config.server_name}"
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
import os
|
||||
|
||||
|
||||
addr: str = "127.0.0.1"
|
||||
port: int = 5000
|
||||
@@ -12,7 +13,7 @@ server_name: str = ""
|
||||
signing_key: str = ""
|
||||
support: dict = {"contacts": []}
|
||||
|
||||
_CONFIG_PATH = Path("/etc/vona/config.toml")
|
||||
_CONFIG_PATH = Path(os.getenv("VONA_CONFIG", "/etc/vona/config.toml"))
|
||||
|
||||
|
||||
def _fatal(msg: str) -> None:
|
||||
@@ -35,6 +36,10 @@ def _load_toml(path: Path) -> dict:
|
||||
except tomllib.TOMLDecodeError as e:
|
||||
_fatal(f"Invalid TOML configuration: {e}")
|
||||
|
||||
# This will never be reached. It is here
|
||||
# specifically just to please ty
|
||||
return {}
|
||||
|
||||
|
||||
def _read_signing_key_from_path(path_value) -> str | None:
|
||||
p = Path(path_value)
|
||||
@@ -110,11 +115,11 @@ def _apply_config(cfg: dict) -> None:
|
||||
else:
|
||||
_warn("No support contacts are defined")
|
||||
|
||||
if "enable_registration" in cfg:
|
||||
users_can_register = cfg["enable_registration"]
|
||||
|
||||
support = support_obj
|
||||
|
||||
if "enable_registration" in cfg and isinstance(cfg["enable_registration"], bool):
|
||||
users_can_register = cfg["enable_registration"]
|
||||
|
||||
print("[INFO] Configuration file was valid")
|
||||
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
import vona.config
|
||||
str(vona.config.server_name) # Satisfy Ruff
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
from flask import Blueprint
|
||||
from flask import (
|
||||
Blueprint,
|
||||
)
|
||||
|
||||
from .hammerhead import hammerhead
|
||||
from .conduwuit import conduwuit
|
||||
from .dendrite import dendrite
|
||||
from .telodendria import telo
|
||||
from .synapse import synapse
|
||||
from .citadel import citadel
|
||||
|
||||
custom = Blueprint("custom", __name__)
|
||||
|
||||
# This implements custom endpoints
|
||||
# used by other homeserver
|
||||
# implementations. They do not start
|
||||
# with /_matrix/
|
||||
# This implements non-standard
|
||||
# endpoints created by other
|
||||
# homeserver implementations.
|
||||
|
||||
|
||||
from .conduwuit import conduwuit
|
||||
from .dendrite import dendrite
|
||||
from .synapse import synapse
|
||||
|
||||
custom.register_blueprint(hammerhead)
|
||||
custom.register_blueprint(conduwuit)
|
||||
custom.register_blueprint(dendrite)
|
||||
custom.register_blueprint(synapse)
|
||||
custom.register_blueprint(citadel)
|
||||
custom.register_blueprint(telo)
|
||||
|
||||
76
vona/custom/citadel.py
Normal file
76
vona/custom/citadel.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import vona.config as config
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
request,
|
||||
)
|
||||
|
||||
citadel = Blueprint("citadel", __name__)
|
||||
|
||||
# These are endpoints made by Thales Citadel
|
||||
|
||||
# TODO: Add more endpoints, this likely
|
||||
# isn't all of them
|
||||
|
||||
|
||||
@citadel.route("/_matrix/client/r0/citadel/stats/m.news/<event>", methods=["GET", "PUT"])
|
||||
async def news_stats(event):
|
||||
if request.method == "PUT":
|
||||
return jsonify({"success": True})
|
||||
|
||||
return jsonify({
|
||||
"total_clicks": config.the_funny_number,
|
||||
"user_readings": config.the_funny_number
|
||||
})
|
||||
|
||||
|
||||
@citadel.route("/_matrix/client/r0/citadel/rooms/<room>/closeRoom", methods=["POST"])
|
||||
async def close_room(room):
|
||||
req = request.json
|
||||
|
||||
if (
|
||||
isinstance(req, dict)
|
||||
and "store_response" in req
|
||||
and isinstance(req["store_response"], bool)
|
||||
):
|
||||
store_response = req["store_response"]
|
||||
else:
|
||||
store_response = True
|
||||
|
||||
operation_id = base64.b64encode(
|
||||
bytes(
|
||||
os.urandom(8).hex(),
|
||||
"utf-8"
|
||||
)
|
||||
).decode("utf-8")[:14]
|
||||
|
||||
|
||||
if store_response:
|
||||
resp = {"operation_id": operation_id}
|
||||
else:
|
||||
resp = {
|
||||
"operation_id": operation_id,
|
||||
"previous_state": {
|
||||
"progress": {
|
||||
"steps": {
|
||||
"step_kick_users": {
|
||||
"status": "complete"
|
||||
},
|
||||
"step_purge_history": {
|
||||
"status": "complete"
|
||||
},
|
||||
"step_purge_media": {
|
||||
"status": "complete"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": "complete"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return jsonify(resp)
|
||||
@@ -1,5 +1,8 @@
|
||||
from flask import Blueprint, jsonify
|
||||
import vona.globals as globals
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
conduwuit = Blueprint("conduwuit", __name__)
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from flask import Blueprint, jsonify
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
dendrite = Blueprint("dendrite", __name__)
|
||||
|
||||
# https://element-hq.github.io/dendrite/administration/adminapi
|
||||
# This implements the Dendrite admin API
|
||||
|
||||
|
||||
@dendrite.route("/_dendrite/admin/purgeRoom/<room>", methods=["POST"])
|
||||
|
||||
134
vona/custom/hammerhead.py
Normal file
134
vona/custom/hammerhead.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from datetime import datetime, timezone
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import time
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
from vona.federation import (
|
||||
send_join,
|
||||
bullshit,
|
||||
)
|
||||
|
||||
hammerhead = Blueprint("hammerhead", __name__)
|
||||
|
||||
# Hammerhead endpoints. Not documented, but code is at:
|
||||
# https://codeberg.org/timedout/hammerhead/src/branch/dev/hammerhead/router/routes/hammerhead
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/uptime")
|
||||
async def uptime():
|
||||
return jsonify({"started_at": config.the_funny_number})
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/version")
|
||||
async def version():
|
||||
return jsonify({"version": globals.version})
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/admin/reload-config")
|
||||
async def reload_config():
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/admin/server-info/<server>")
|
||||
async def server_info(server):
|
||||
return jsonify({
|
||||
"destination": {
|
||||
"expires": datetime.now(timezone.utc).isoformat(),
|
||||
"host_header": server,
|
||||
"ip_port": [
|
||||
f"{server}:443"
|
||||
],
|
||||
"server_name": server
|
||||
},
|
||||
"keys": globals.sign_json({
|
||||
"old_verify_keys": {},
|
||||
"server_name": server,
|
||||
"valid_until_ts": int(time.time() * 1000 + 604800000),
|
||||
"verify_keys": {
|
||||
f"ed25519:{config.signing_key.split()[1]}": {
|
||||
"key": globals.pubkey()
|
||||
}
|
||||
}
|
||||
}),
|
||||
"software": {
|
||||
"server": {
|
||||
"version": globals.version,
|
||||
"name": "Vona"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/admin/rooms/<room>/state-resolver")
|
||||
async def room_state(room):
|
||||
state = send_join(bullshit, room)["state"]
|
||||
formatted_state = {}
|
||||
event_cache = {}
|
||||
|
||||
for event in state:
|
||||
key = f"({event["type"]},'{event["state_key"]}')"
|
||||
formatted_state[key] = event
|
||||
|
||||
if "event_id" in event:
|
||||
event_id = event["event_id"]
|
||||
else:
|
||||
event_id = globals.make_ref_hash(
|
||||
event,
|
||||
int(globals.room_version_from_id(room))
|
||||
)
|
||||
|
||||
event_cache[event_id] = event
|
||||
|
||||
|
||||
return jsonify({
|
||||
"current_state": formatted_state,
|
||||
"event_cache": event_cache,
|
||||
"room_id": room,
|
||||
"room_version": globals.room_version_from_id(room)
|
||||
})
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/admin/rooms/<room>/debug")
|
||||
async def debug_room(room):
|
||||
state = send_join(bullshit, room)["state"]
|
||||
formatted_state = {}
|
||||
event_cache = {}
|
||||
|
||||
for event in state:
|
||||
key = f"({event["type"]},'{event["state_key"]}')"
|
||||
formatted_state[key] = event
|
||||
|
||||
if "event_id" in event:
|
||||
event_id = event["event_id"]
|
||||
else:
|
||||
event_id = globals.make_ref_hash(
|
||||
event,
|
||||
int(globals.room_version_from_id(room))
|
||||
)
|
||||
|
||||
event_cache[event_id] = event
|
||||
|
||||
|
||||
if globals.room_version_from_id(room) in ["1", "2"]:
|
||||
extremity = globals.make_event_id(seed=f"6_{room}")
|
||||
else:
|
||||
extremity = globals.make_ref_hash(
|
||||
state[5],
|
||||
int(globals.room_version_from_id(room)),
|
||||
)
|
||||
|
||||
|
||||
return jsonify({
|
||||
"aliases": [f"#vona:{config.server_name}"],
|
||||
"current_state": formatted_state,
|
||||
"event_cache": event_cache,
|
||||
"room_id": room,
|
||||
"room_version": globals.room_version_from_id(room),
|
||||
"forward_extremities": [extremity],
|
||||
"servers": None
|
||||
})
|
||||
@@ -1,32 +1,38 @@
|
||||
from flask import Blueprint, jsonify, request, Response
|
||||
from vona.federation import send_join
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import base64
|
||||
import re
|
||||
|
||||
import os
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
request,
|
||||
)
|
||||
|
||||
synapse = Blueprint("synapse", __name__)
|
||||
|
||||
# The absolute giant known as the Synapse admin API.
|
||||
# Very messy, needs cleaning
|
||||
# TODO: Very messy, needs cleaning
|
||||
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/suspend/<user_id>", methods=["PUT"])
|
||||
@synapse.route("/_synapse/admin/v1/deactivate/<user_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/reset_password/<user_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/admin", methods=["PUT"])
|
||||
@synapse.route("/_synapse/admin/v2/users/<user_id>/delete_devices", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/shadow_ban", methods=["DELETE", "POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/override_ratelimit", methods=["GET", "POST", "DELETE"])
|
||||
@synapse.route("/_synapse/admin/v1/suspend/<user>", methods=["PUT"])
|
||||
@synapse.route("/_synapse/admin/v1/deactivate/<user>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/reset_password/<user>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/admin", methods=["PUT"])
|
||||
@synapse.route("/_synapse/admin/v2/users/<user>/delete_devices", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/shadow_ban", methods=["DELETE", "POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/override_ratelimit", methods=["GET", "POST", "DELETE"])
|
||||
@synapse.route("/_synapse/admin/v1/media/protect/<media_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/media/unprotect/<media_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/media/quarantine/<s>/<media_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/media/unquarantine/<s>/<media_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/federation/destinations/<destination>/reset_connection", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/timestamp_to_event")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/timestamp_to_event")
|
||||
@synapse.route("/_synapse/admin/v2/rooms/delete_status/<delete_id>")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/make_room_admin", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/make_room_admin", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/background_updates/start_job", methods=["POST"])
|
||||
async def response(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
@@ -56,7 +62,7 @@ async def user_list():
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/users/<user_id>", methods=["GET", "PUT"])
|
||||
@synapse.route("/_synapse/admin/v2/users/<user>", methods=["GET", "PUT"])
|
||||
async def user_info(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
@@ -83,7 +89,7 @@ async def user_info(user_id):
|
||||
|
||||
return jsonify({}), 201
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/whois/<user_id>")
|
||||
@synapse.route("/_synapse/admin/v1/whois/<user>")
|
||||
async def whois(user_id):
|
||||
return jsonify({
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
@@ -91,7 +97,7 @@ async def whois(user_id):
|
||||
"": {
|
||||
"sessions": [{
|
||||
"connections": [{
|
||||
"ip":f"127.0.0.1",
|
||||
"ip":"127.0.0.1",
|
||||
"last_seen":config.the_funny_number,
|
||||
"user_agent":f"Vona/{globals.version}"
|
||||
}]
|
||||
@@ -100,44 +106,59 @@ async def whois(user_id):
|
||||
}
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/joined_rooms")
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/joined_rooms")
|
||||
async def user_joined_rooms(user_id):
|
||||
return jsonify({
|
||||
"joined_rooms": [globals.make_event_id().replace("$", "!")],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/sent_invite_count")
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/sent_invite_count")
|
||||
async def invite_count(user_id):
|
||||
return jsonify({"invite_count": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/accountdata")
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/accountdata")
|
||||
async def account_data(user_id):
|
||||
return jsonify({"account_data":{"global":{}}})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/media", methods=["GET", "DELETE"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/media", methods=["GET", "DELETE"])
|
||||
async def account_media(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({"media": [{"created_ts":config.the_funny_number,"last_access_ts":config.the_funny_number,"media_id":"cat","media_length":config.the_funny_number,"media_type":"image/jpeg","quarantined_by":"null","safe_from_quarantine":False,"upload_name":"cat.jpg"}], "total": config.the_funny_number})
|
||||
return jsonify({
|
||||
"media": [{
|
||||
"created_ts": config.the_funny_number,
|
||||
"last_access_ts": config.the_funny_number,
|
||||
"media_id": "cat",
|
||||
"media_length": config.the_funny_number,
|
||||
"media_type": "image/jpeg",
|
||||
"quarantined_by": None,
|
||||
"safe_from_quarantine": False,
|
||||
"upload_name": "cat.jpg"
|
||||
}],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
return jsonify({"deleted_media": ["cat"], "total": config.the_funny_number})
|
||||
return jsonify({
|
||||
"deleted_media": ["cat"],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/login", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/login", methods=["POST"])
|
||||
async def account_login(user_id):
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/_allow_cross_signing_replacement_without_uia", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/_allow_cross_signing_replacement_without_uia", methods=["POST"])
|
||||
async def stupid_mas_bullshit(user_id):
|
||||
return jsonify({"updatable_without_uia_before_ms": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/users/<user_id>/devices", methods=["GET", "POST"])
|
||||
@synapse.route("/_synapse/admin/v2/users/<user>/devices", methods=["GET", "POST"])
|
||||
async def 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_ip": config.addr,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.version}"
|
||||
}],
|
||||
@@ -146,20 +167,20 @@ async def device_list(user_id):
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/users/<user_id>/devices/<device_id>", methods=["GET", "PUT", "DELETE"])
|
||||
@synapse.route("/_synapse/admin/v2/users/<user>/devices/<device_id>", methods=["GET", "PUT", "DELETE"])
|
||||
async def 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_ip": config.addr,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.version}"
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/pushers")
|
||||
@synapse.route("/_synapse/admin/v1/users/<user>/pushers")
|
||||
async def pushers(user_id):
|
||||
return jsonify({"pushers": [], "total": config.the_funny_number})
|
||||
|
||||
@@ -169,18 +190,18 @@ async def username_available():
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/threepid/<medium>/users/<addr>")
|
||||
@synapse.route("/_synapse/admin/v1/auth_providers/<provider>/users/<ext>")
|
||||
async def threepid(p, a):
|
||||
async def threepid(**kwargs):
|
||||
return jsonify({"user_id": f"@vona:{config.server_name}"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/<user_id>/redact")
|
||||
def redact(user_id):
|
||||
@synapse.route("/_synapse/admin/v1/<user>/redact")
|
||||
async def redact(user_id):
|
||||
return jsonify({"redact_id": os.urandom(16).hex()})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/user/redact_status/<redact_id>")
|
||||
async def redact_status(redact_id):
|
||||
return jsonify({"status":"active","failed_redactions":[]})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/experimental_features/<user_id>", methods=["GET", "PUT"])
|
||||
@synapse.route("/_synapse/admin/v1/experimental_features/<user>", methods=["GET", "PUT"])
|
||||
async def experimental_features(user_id):
|
||||
return jsonify({"features": {}})
|
||||
|
||||
@@ -191,8 +212,8 @@ async def register():
|
||||
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/join/<roomId>", methods=["POST"])
|
||||
async def membership_manipulation(roomId):
|
||||
@synapse.route("/_synapse/admin/v1/join/<room>", methods=["POST"])
|
||||
async def membership_manipulation(room):
|
||||
return jsonify({"room_id": globals.make_event_id().replace("$", "!")})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/account_validity/validity", methods=["POST"])
|
||||
@@ -204,8 +225,8 @@ async def account_validity():
|
||||
async def server_notice(**kwargs):
|
||||
return jsonify({"event_id": globals.make_event_id()})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room_id>/<event_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room>/<event_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room>", methods=["POST"])
|
||||
async def purge_event(**kwargs):
|
||||
return jsonify({"purge_id": os.urandom(16).hex()})
|
||||
|
||||
@@ -213,12 +234,12 @@ async def purge_event(**kwargs):
|
||||
async def purge_status(purge_id):
|
||||
return jsonify({"status":"active"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/room/<room_id>/media")
|
||||
async def room_media(room_id):
|
||||
@synapse.route("/_synapse/admin/v1/room/<room>/media")
|
||||
async def room_media(room):
|
||||
return jsonify({"local": [f"mxc://{config.server_name}/cat"], "remote": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/room/<room_id>/media/quarantine", methods=["POST"])
|
||||
async def quarantine_room_media(room_id):
|
||||
@synapse.route("/_synapse/admin/v1/room/<room>/media/quarantine", methods=["POST"])
|
||||
async def quarantine_room_media(room):
|
||||
return jsonify({"num_quarantined": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/media/<s>/delete", methods=["POST"])
|
||||
@@ -233,7 +254,15 @@ async def delete_remote_media():
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/statistics/users/media")
|
||||
async def media_stats():
|
||||
return jsonify({"users":[{"displayname":"Vona","media_count":config.the_funny_number,"media_length":config.the_funny_number,"user_id":f"@vona:{config.server_name}"}],"total":config.the_funny_number})
|
||||
return jsonify({
|
||||
"users": [{
|
||||
"displayname": "Vona",
|
||||
"media_count": config.the_funny_number,
|
||||
"media_length": config.the_funny_number,
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
}],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/statistics/database/rooms")
|
||||
async def room_stats():
|
||||
@@ -249,12 +278,6 @@ async def room_stats():
|
||||
async def change_bg_update():
|
||||
return jsonify({"enabled": False})
|
||||
|
||||
# No documentation on what Synapse actually returns for this API, so a blank dict for now
|
||||
@synapse.route("/_synapse/admin/v1/background_updates/start_job", methods=["POST"])
|
||||
async def bg_update_start_job():
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/event_reports")
|
||||
async def event_reports():
|
||||
return jsonify({
|
||||
@@ -302,7 +325,7 @@ async def interact_with_reported_event(report_id):
|
||||
@synapse.route("/_synapse/admin/v1/federation/destinations")
|
||||
async def federation_destinations():
|
||||
return jsonify({
|
||||
"destinations": [{}],
|
||||
"destinations": [],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@@ -366,7 +389,7 @@ async def rooms():
|
||||
"total_rooms": 0
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/members")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/members")
|
||||
async def room_members(room):
|
||||
return jsonify({
|
||||
"members": [
|
||||
@@ -375,11 +398,11 @@ async def room_members(room):
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/state")
|
||||
async def room_state(room):
|
||||
return jsonify({"state": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/state")
|
||||
async def room_messages(room):
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
@@ -387,11 +410,11 @@ async def room_messages(room):
|
||||
"start": "vona"
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/block", methods=["GET", "PUT"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/block", methods=["GET", "PUT"])
|
||||
async def block_room(room):
|
||||
return jsonify({"block": False})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>", methods=["DELETE"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>", methods=["DELETE"])
|
||||
async def room_delete(room):
|
||||
return jsonify({
|
||||
"kicked_users": [
|
||||
@@ -399,18 +422,18 @@ async def room_delete(room):
|
||||
],
|
||||
"failed_to_kick_users": [],
|
||||
"local_aliases": [],
|
||||
"new_room_id": f"!vona:{config.server_name}"
|
||||
"new_room_id": globals.make_event_id(seed=room)
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room_id>", methods=["DELETE"])
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room>", methods=["DELETE"])
|
||||
async def room_delete_v2(room):
|
||||
return jsonify({"delete_id": "vona"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room_id>/delete_status")
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room>/delete_status")
|
||||
async def room_delete_status(room):
|
||||
return jsonify({"results": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/forward_extremities", methods=["GET"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/forward_extremities", methods=["GET"])
|
||||
async def forward_extremities(room):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({"deleted": 0})
|
||||
@@ -424,3 +447,38 @@ async def forward_extremities(room):
|
||||
"received_ts": config.the_funny_number
|
||||
}]
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/media/<origin>/<media_id>")
|
||||
async def media_info(origin, media_id):
|
||||
return jsonify({
|
||||
"media_info": {
|
||||
"media_origin": origin,
|
||||
"user_id": None,
|
||||
"media_id": media_id,
|
||||
"media_type": "img/jpeg",
|
||||
"media_length": 67,
|
||||
"upload_name": "cat.jpg",
|
||||
"created_ts": config.the_funny_number,
|
||||
"filesystem_id": os.urandom(3).hex(),
|
||||
"url_cache": None,
|
||||
"last_access_ts": config.the_funny_number,
|
||||
"quarantined_by": None,
|
||||
"authenticated": False,
|
||||
"safe_from_quarantine": None,
|
||||
"sha256": os.urandom(32).hex()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/fetch_event/<evt>")
|
||||
async def fetch_event(evt):
|
||||
class bs:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
return jsonify({
|
||||
"event": send_join(
|
||||
bs,
|
||||
globals.make_event_id(seed=evt).replace("$", "!")
|
||||
)["state"][5]
|
||||
})
|
||||
|
||||
90
vona/custom/telodendria.py
Normal file
90
vona/custom/telodendria.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from vona.globals import version
|
||||
import vona.config as config
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
jsonify,
|
||||
)
|
||||
|
||||
telo = Blueprint("telodendria", __name__)
|
||||
|
||||
# The telodendria admin API as specified by
|
||||
# https://git.telodendria.org/Telodendria/Telodendria/src/branch/master/docs/user/admin/README.md
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/restart", methods=["POST"])
|
||||
@telo.route("/_telodendria/admin/v1/shutdown", methods=["POST"])
|
||||
async def process_management(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/privileges/<lp>", methods=["GET", "PUT", "POST", "DELETE"])
|
||||
@telo.route("/_telodendria/admin/v1/privileges/", methods=["GET", "POST"])
|
||||
async def privileges(lp=None):
|
||||
return jsonify({
|
||||
"privileges": [
|
||||
"GRANT_PRIVILEGES",
|
||||
"PROC_CONTROL",
|
||||
"ISSUE_TOKENS",
|
||||
"DEACTIVATE",
|
||||
"CONFIG",
|
||||
"ALIAS",
|
||||
"ALL"
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/config", methods=["GET", "PUT", "POST"])
|
||||
async def configuration():
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"listen": [
|
||||
{
|
||||
"tls": None,
|
||||
"port": config.port,
|
||||
"threads": 1,
|
||||
"maxConnections": 32
|
||||
}
|
||||
],
|
||||
"serverName": config.server_name,
|
||||
"pid": "/dev/null",
|
||||
"baseUrl": f"https://{config.server_name}/",
|
||||
"identityServer": f"https://{config.server_name}/",
|
||||
"runAs": None,
|
||||
"federation": True,
|
||||
"registration": config.users_can_register,
|
||||
"log": {
|
||||
"output": "stdout",
|
||||
"level": "message",
|
||||
"timestampFormat": "default",
|
||||
"color": True
|
||||
},
|
||||
"maxCache": config.the_funny_number
|
||||
})
|
||||
|
||||
return jsonify({"restart_required": True})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/stats")
|
||||
async def stats():
|
||||
return jsonify({
|
||||
"memory_allocated": config.the_funny_number,
|
||||
"version": version
|
||||
})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/tokens/<name>", methods=["GET", "DELETE"])
|
||||
@telo.route("/_telodendria/admin/v1/tokens", methods=["GET", "POST"])
|
||||
async def tokens(name=None):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"name": "vona",
|
||||
"created_by": "vona",
|
||||
"created_on": config.the_funny_number,
|
||||
"expires_on": config.the_funny_number,
|
||||
"used": config.the_funny_number,
|
||||
"uses": config.the_funny_number
|
||||
})
|
||||
@@ -1,227 +1,32 @@
|
||||
from flask import jsonify, Response, request, send_file, abort, Blueprint
|
||||
from vona.config import *
|
||||
import vona.federation.rooms as rooms
|
||||
import vona.globals as globals
|
||||
import httpx
|
||||
import vona.config as config
|
||||
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
|
||||
from flask import (
|
||||
jsonify,
|
||||
Response,
|
||||
request,
|
||||
Blueprint,
|
||||
abort,
|
||||
)
|
||||
|
||||
server = Blueprint("federation", __name__)
|
||||
http = globals.http_client()
|
||||
|
||||
|
||||
def send_join(request, roomId) -> dict:
|
||||
event_chain = []
|
||||
event_hashes = []
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
event_ids = [
|
||||
globals.make_event_id(seed=f"1_{roomId}"),
|
||||
globals.make_event_id(seed=f"2_{roomId}"),
|
||||
globals.make_event_id(seed=f"3_{roomId}"),
|
||||
globals.make_event_id(seed=f"4_{roomId}"),
|
||||
globals.make_event_id(seed=f"5_{roomId}"),
|
||||
globals.make_event_id(seed=f"6_{roomId}"),
|
||||
]
|
||||
|
||||
create_event = {
|
||||
"content": {
|
||||
"m.federate": True,
|
||||
"creator": f"@vona:{server_name}",
|
||||
"room_version": globals.room_version_from_id(roomId)
|
||||
},
|
||||
"event_id": event_ids[0],
|
||||
"origin_server_ts": 1,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"depth": 1,
|
||||
"type": "m.room.create",
|
||||
"auth_events": [],
|
||||
"prev_events": []
|
||||
}
|
||||
|
||||
screate_event = globals.hash_and_sign_event(create_event)
|
||||
event_chain.append(screate_event)
|
||||
|
||||
our_join = {
|
||||
"content": {
|
||||
"displayname": "Vona",
|
||||
"avatar_url": f"mxc://{server_name}/cat",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin_server_ts": 2,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": f"@vona:{server_name}",
|
||||
"type": "m.room.member",
|
||||
"event_id": event_ids[1],
|
||||
"room_id": roomId,
|
||||
"depth": 2,
|
||||
"auth_events": [[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
]],
|
||||
"prev_events": [[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
sour_join = globals.hash_and_sign_event(our_join)
|
||||
event_chain.append(sour_join)
|
||||
|
||||
pls = {
|
||||
"content": {
|
||||
"users": {
|
||||
f"@vona:{server_name}": "100"
|
||||
}
|
||||
},
|
||||
"origin_server_ts": 3,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
"event_id": event_ids[2],
|
||||
"depth": 3,
|
||||
"user_id": f"@vona:{server_name}",
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
spls = globals.hash_and_sign_event(pls)
|
||||
event_chain.append(spls)
|
||||
|
||||
join_rule = {
|
||||
"content": {
|
||||
"join_rule": "public"
|
||||
},
|
||||
"origin_server_ts": 4,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules",
|
||||
"event_id": event_ids[3],
|
||||
"room_id": roomId,
|
||||
"depth": 4,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
sjoin_rule = globals.hash_and_sign_event(join_rule)
|
||||
event_chain.append(sjoin_rule)
|
||||
|
||||
guest_access = {
|
||||
"content": {
|
||||
"guest_access": "forbidden"
|
||||
},
|
||||
"origin_server_ts": 5,
|
||||
"depth": 5,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.guest_access",
|
||||
"event_id": event_ids[4],
|
||||
"room_id": roomId,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
sguest_access = globals.hash_and_sign_event(guest_access)
|
||||
event_chain.append(sguest_access)
|
||||
|
||||
history = {
|
||||
"content": {
|
||||
"history_visibility": "shared"
|
||||
},
|
||||
"type": "m.room.history_visibility",
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": "",
|
||||
"origin_server_ts": 6,
|
||||
"depth": 6,
|
||||
"event_id": event_ids[5],
|
||||
"room_id": roomId,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
sguest_access["event_id"],
|
||||
sguest_access["hashes"]
|
||||
]]
|
||||
}
|
||||
|
||||
shistory = globals.hash_and_sign_event(history)
|
||||
event_chain.append(shistory)
|
||||
|
||||
remote_join = request.get_json()
|
||||
|
||||
response = {
|
||||
"auth_chain": event_chain,
|
||||
"event": remote_join,
|
||||
"members_omitted": False,
|
||||
"servers_in_room": [server_name],
|
||||
"state": event_chain
|
||||
}
|
||||
|
||||
return response
|
||||
def send_join(request, room) -> dict:
|
||||
if globals.room_version_from_id(room) in ["1", "2"]:
|
||||
return rooms.v1_v2(request, room)
|
||||
else:
|
||||
return rooms.v3(request, room)
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/version")
|
||||
@@ -233,33 +38,36 @@ async def version():
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/key/v2/server")
|
||||
async def keys():
|
||||
return jsonify(globals.sign_json({
|
||||
"old_verify_keys": {},
|
||||
"server_name": server_name,
|
||||
"server_name": config.server_name,
|
||||
"valid_until_ts": int(time.time() * 1000 + 604800000),
|
||||
"verify_keys": {
|
||||
f"ed25519:{signing_key.split()[1]}": {
|
||||
f"ed25519:{config.signing_key.split()[1]}": {
|
||||
"key": globals.pubkey()
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/query/directory")
|
||||
async def room_query():
|
||||
return jsonify({
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"servers": [server_name]
|
||||
"servers": [config.server_name]
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/media/download/<media_id>")
|
||||
async def download_media(media_id):
|
||||
# Auth media requires this to be
|
||||
# multipart despite not even using
|
||||
# it for anything. Minor annoyance.
|
||||
|
||||
with open(cat, "rb") as img_file:
|
||||
with open(config.cat, "rb") as img_file:
|
||||
image_data = img_file.read()
|
||||
boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
||||
response_body = (
|
||||
@@ -276,6 +84,7 @@ async def download_media(media_id):
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/media/thumbnail/<media_id>")
|
||||
async def thumbnail_media(media_id):
|
||||
return jsonify({
|
||||
@@ -283,51 +92,65 @@ async def thumbnail_media(media_id):
|
||||
"error": "Cat is too cute to thumbnail"
|
||||
}), 418
|
||||
|
||||
@server.route("/_matrix/federation/v1/send_join/<roomId>/<eventId>", methods=["PUT"])
|
||||
async def send_join_v1(roomId, eventId):
|
||||
return jsonify([200, send_join(request, roomId)])
|
||||
|
||||
@server.route("/_matrix/federation/v2/send_join/<roomId>/<eventId>", methods=["PUT"])
|
||||
async def send_join_v2(roomId, eventId):
|
||||
return jsonify(send_join(request, roomId))
|
||||
@server.route("/_matrix/federation/v1/send_join/<room>/<path:eventId>", methods=["PUT"])
|
||||
async def send_join_v1(room, eventId):
|
||||
if globals.room_version_from_id(room) not in ["1", "2"]:
|
||||
return jsonify({
|
||||
"errcode": "M_INCOMPATIBLE_ROOM_VERSION",
|
||||
"error": "This room is not v1 or v2."
|
||||
}), 400
|
||||
|
||||
@server.route("/_matrix/federation/v1/make_join/<roomId>/<userId>")
|
||||
async def make_join(roomId, userId):
|
||||
def not_invited():
|
||||
return jsonify([200, send_join(request, room)])
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v2/send_join/<room>/<path:eventId>", methods=["PUT"])
|
||||
async def send_join_v2(room, eventId):
|
||||
if globals.room_version_from_id(room) in ["1", "2"]:
|
||||
# Spec says to fallback to the v1 send_join endpoint if this fails
|
||||
abort(404)
|
||||
|
||||
return jsonify(send_join(request, room))
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/make_join/<room>/<user>")
|
||||
async def make_join(room, user):
|
||||
if ":" in room:
|
||||
if room.split(":")[1] != config.server_name:
|
||||
return jsonify({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "You are not invited to this room."
|
||||
}), 403
|
||||
else:
|
||||
return jsonify({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "You are not invited to this room."
|
||||
}), 403
|
||||
|
||||
try:
|
||||
if roomId.split(":")[1] != server_name:
|
||||
return not_invited()
|
||||
except:
|
||||
return not_invited()
|
||||
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
room_ver = globals.room_version_from_id(room)
|
||||
|
||||
state = send_join(
|
||||
request=bullshit,
|
||||
roomId=roomId
|
||||
room=room
|
||||
)["state"]
|
||||
|
||||
join = {
|
||||
"content": {
|
||||
"join_authorised_via_users_server": f"@vona:{server_name}",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin": server_name,
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": 7,
|
||||
"room_id": roomId,
|
||||
"sender": userId,
|
||||
"state_key": userId,
|
||||
"room_id": room,
|
||||
"sender": user,
|
||||
"state_key": user,
|
||||
"type": "m.room.member",
|
||||
"depth": 7
|
||||
}
|
||||
|
||||
if room_ver in ["1", "2"]:
|
||||
join["event_id"] = globals.make_event_id(seed=f"{user}+{room}")
|
||||
|
||||
join["auth_events"] = [
|
||||
[
|
||||
state[0]["event_id"],
|
||||
@@ -347,21 +170,29 @@ async def make_join(roomId, userId):
|
||||
state[5]["event_id"],
|
||||
state[5]["hashes"]
|
||||
]]
|
||||
else:
|
||||
join["auth_events"] = [
|
||||
globals.make_ref_hash(state[0], int(room_ver)),
|
||||
globals.make_ref_hash(state[2], int(room_ver)),
|
||||
globals.make_ref_hash(state[3], int(room_ver)),
|
||||
]
|
||||
|
||||
join["prev_events"] = [
|
||||
globals.make_ref_hash(state[5], int(room_ver)),
|
||||
]
|
||||
|
||||
|
||||
return jsonify({
|
||||
"event": globals.hash_and_sign_event(join),
|
||||
"room_version": globals.room_version_from_id(roomId)
|
||||
"event": globals.hash_and_sign_event(join, int(room_ver)),
|
||||
"room_version": room_ver
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/publicRooms", methods=["POST", "GET"])
|
||||
async def room_directory():
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
return jsonify(globals.room_dir)
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/server-server-api/#transactions
|
||||
@server.route("/_matrix/federation/v1/send/<txnId>", methods=["PUT"])
|
||||
async def receive_txn(txnId):
|
||||
# We will need to implement a way to store every
|
||||
@@ -385,7 +216,13 @@ async def receive_txn(txnId):
|
||||
if "pdus" in parsed_data:
|
||||
for pdu in parsed_data["pdus"]:
|
||||
if "event_id" in pdu:
|
||||
response["pdus"][pdu["event_id"]] = {}
|
||||
# For v1 and v2 rooms
|
||||
event_id = pdu["event_id"]
|
||||
else:
|
||||
# Assume room v4 or over as most rooms will be anyway
|
||||
event_id = globals.make_ref_hash(pdu, 4)
|
||||
|
||||
response["pdus"][event_id] = {}
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
@@ -395,7 +232,7 @@ async def user_profile():
|
||||
field = request.args.get("field")
|
||||
if field:
|
||||
if field == "avatar_url":
|
||||
return jsonify({"avatar_url":f"mxc://{server_name}/cat"})
|
||||
return jsonify({"avatar_url":f"mxc://{config.server_name}/cat"})
|
||||
elif field == "displayname":
|
||||
return jsonify({"displayname":"Vona"})
|
||||
|
||||
@@ -404,71 +241,68 @@ async def user_profile():
|
||||
"error": "The requested profile key does not exist."
|
||||
}), 404
|
||||
|
||||
return jsonify({"avatar_url": f"mxc://{server_name}/cat","displayname": "Vona"})
|
||||
return jsonify({"avatar_url": f"mxc://{config.server_name}/cat","displayname": "Vona"})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/server-server-api/#device-management
|
||||
@server.route(f"/_matrix/federation/v1/user/devices/@/:{server_name}")
|
||||
@server.route(f"/_matrix/federation/v1/user/devices/@/:{config.server_name}")
|
||||
@server.route("/_matrix/federation/v1/user/devices/<user>")
|
||||
async def user_devices(user):
|
||||
return jsonify({
|
||||
"devices": [],
|
||||
"stream_id": the_funny_number,
|
||||
"user_id": f"@vona:{server_name}"
|
||||
"stream_id": config.the_funny_number,
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/user/keys/query", methods=["POST"])
|
||||
async def user_keys():
|
||||
try:
|
||||
users = request.json["device_keys"]
|
||||
except:
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "User does not exist"
|
||||
}), 404
|
||||
req = request.json
|
||||
|
||||
return jsonify({"device_keys": users})
|
||||
if (
|
||||
isinstance(req, dict)
|
||||
and "device_keys" in req
|
||||
and isinstance(req["device_keys"], dict)
|
||||
):
|
||||
device_keys = req["device_keys"]
|
||||
else:
|
||||
device_keys = {}
|
||||
|
||||
return jsonify({
|
||||
"device_keys": device_keys
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v2/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v2(room, txnId):
|
||||
return invite_user(request.data)
|
||||
invite_data = request.json
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v1(room, txnId):
|
||||
return [200, invite_user(request.data)]
|
||||
|
||||
|
||||
def invite_user(data):
|
||||
try:
|
||||
invite_data = json.loads(data)
|
||||
except:
|
||||
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}),
|
||||
|
||||
if "event" in invite_data:
|
||||
if (
|
||||
isinstance(invite_data, dict)
|
||||
and "event" in invite_data
|
||||
):
|
||||
if "room_version" in invite_data:
|
||||
if invite_data["room_version"] != "2":
|
||||
if invite_data["room_version"] not in [str(i) for i in range(1, 10)]:
|
||||
return jsonify({
|
||||
"errcode": "M_INCOMPATIBLE_ROOM_VERSION",
|
||||
"error": "Vona only supports room version 2.",
|
||||
"error": "Unsupported room version",
|
||||
"room_version": invite_data["room_version"]
|
||||
}), 400
|
||||
|
||||
event = invite_data.get("event", {})
|
||||
content = event.get("content", {})
|
||||
|
||||
# NOTE to crispycat: I know you loooooove this syntax
|
||||
if (
|
||||
"content" in event
|
||||
and "membership" in content
|
||||
and "state_key" in event
|
||||
and "room_id" in event
|
||||
and content["membership"] == "invite"
|
||||
and event["state_key"] == f"@vona:{server_name}"
|
||||
and event["state_key"] == f"@vona:{config.server_name}"
|
||||
):
|
||||
return jsonify({"event": globals.sign_json_without_discard(invite_data["event"]), "room_version": "2"})
|
||||
return jsonify({
|
||||
"event": globals.sign_json_without_discard(event),
|
||||
"room_version": invite_data["room_version"]
|
||||
})
|
||||
|
||||
|
||||
return jsonify({
|
||||
@@ -477,9 +311,166 @@ def invite_user(data):
|
||||
}), 403
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/hierarchy/<roomId>")
|
||||
async def space_hierachy(roomId):
|
||||
@server.route("/_matrix/federation/v1/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v1(room, txnId):
|
||||
event = request.json
|
||||
if (
|
||||
isinstance(event, dict)
|
||||
and "content" in event
|
||||
and isinstance(event["content"], dict)
|
||||
):
|
||||
content = event["content"]
|
||||
else:
|
||||
content = {}
|
||||
|
||||
if (
|
||||
isinstance(event, dict)
|
||||
and isinstance(content, dict)
|
||||
and "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:{config.server_name}"
|
||||
and "event_id" in event
|
||||
and ":" in event["event_id"]
|
||||
):
|
||||
return jsonify({
|
||||
"event": globals.sign_json_without_discard(event)
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid invitation PDU"
|
||||
}), 403
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/hierarchy/<room>")
|
||||
async def space_hierachy(room):
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Room does not exist."
|
||||
}), 404
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/org.matrix.msc4358/discover_common_rooms", methods=["POST"])
|
||||
@server.route("/_matrix/federation/v1/discover_common_rooms", methods=["POST"])
|
||||
async def discover_common_rooms():
|
||||
req = request.json
|
||||
if isinstance(req, dict):
|
||||
tags = req.get("room_participation_tags", [])
|
||||
else:
|
||||
tags = []
|
||||
|
||||
return jsonify({"recognised_tags": tags})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/backfill/<room>")
|
||||
async def backfill(room):
|
||||
# TODO: burger king foot lettuce
|
||||
|
||||
return jsonify({
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": globals.get_time(),
|
||||
"pdus": send_join(bullshit, room)["state"]
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/unstable/org.matrix.msc4370/extremities/<room>")
|
||||
@server.route("/_matrix/federation/v1/extremities/<room>")
|
||||
async def extremities(room):
|
||||
if ":" in room:
|
||||
if room.split(":")[1] != config.server_name:
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Room is unknown to this server"
|
||||
}), 404
|
||||
|
||||
room_ver = globals.room_version_from_id(room)
|
||||
|
||||
if room_ver in ["1", "2"]:
|
||||
event_id = globals.make_event_id(seed=f"6_{room}")
|
||||
else:
|
||||
event_id = globals.make_ref_hash(
|
||||
send_join(bullshit, room)["state"][5],
|
||||
int(room_ver)
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"prev_events": [event_id]
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/state_ids/<room>")
|
||||
async def state_ids(room):
|
||||
if (
|
||||
"event_id" in request.args
|
||||
and request.args["event_id"].strip() != ""
|
||||
):
|
||||
evt = request.args["event_id"]
|
||||
def explode():
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": f"Could not find event {evt}"
|
||||
}), 404
|
||||
|
||||
|
||||
if ":" not in evt:
|
||||
return explode()
|
||||
if ":" in room:
|
||||
if room.split(":")[1] != config.server_name:
|
||||
return explode()
|
||||
else:
|
||||
return explode()
|
||||
|
||||
server_name = evt.split(":")[1]
|
||||
|
||||
if server_name == config.server_name:
|
||||
state = send_join(bullshit, room)["state"]
|
||||
event_ids = []
|
||||
|
||||
for event in state:
|
||||
if "event_id" in event:
|
||||
event_ids.append(event["event_id"])
|
||||
else:
|
||||
event_ids.append(
|
||||
globals.make_ref_hash(event)
|
||||
)
|
||||
|
||||
if evt in event_ids:
|
||||
return jsonify({
|
||||
"auth_chain_ids": [event_ids],
|
||||
"pdu_ids": [event_ids]
|
||||
})
|
||||
else:
|
||||
return explode()
|
||||
|
||||
try:
|
||||
resp = http.get(
|
||||
path=request.full_path,
|
||||
destination=server_name,
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
raise
|
||||
|
||||
return jsonify(resp.json())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return explode()
|
||||
|
||||
return jsonify({
|
||||
"errcode": "M_MISSING_PARAM",
|
||||
"error": "Query parameter 'event_id' was not specified"
|
||||
}), 400
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/unstable/io.fsky.vel/edutypes")
|
||||
@server.route("/_matrix/federation/v1/edutypes")
|
||||
async def edutypes():
|
||||
return jsonify({
|
||||
"m.presence": False,
|
||||
"m.receipt": False,
|
||||
"m.typing": False,
|
||||
})
|
||||
|
||||
284
vona/federation/rooms.py
Normal file
284
vona/federation/rooms.py
Normal file
@@ -0,0 +1,284 @@
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
# This file is responsible for creating Matrix rooms.
|
||||
|
||||
# Room V1/V2
|
||||
def v1_v2(request, room) -> dict:
|
||||
event_chain = []
|
||||
|
||||
event_ids = [
|
||||
globals.make_event_id(seed=f"1_{room}"),
|
||||
globals.make_event_id(seed=f"2_{room}"),
|
||||
globals.make_event_id(seed=f"3_{room}"),
|
||||
globals.make_event_id(seed=f"4_{room}"),
|
||||
globals.make_event_id(seed=f"5_{room}"),
|
||||
globals.make_event_id(seed=f"6_{room}"),
|
||||
]
|
||||
|
||||
create_event = {
|
||||
"content": {
|
||||
"m.federate": True,
|
||||
"creator": f"@vona:{config.server_name}",
|
||||
"room_version": globals.room_version_from_id(room)
|
||||
},
|
||||
"event_id": event_ids[0],
|
||||
"origin_server_ts": 1,
|
||||
"room_id": room,
|
||||
"sender": f"@vona:{config.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://{config.server_name}/cat",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin_server_ts": 2,
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": f"@vona:{config.server_name}",
|
||||
"type": "m.room.member",
|
||||
"event_id": event_ids[1],
|
||||
"room_id": room,
|
||||
"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:{config.server_name}": "100"
|
||||
}
|
||||
},
|
||||
"origin_server_ts": 3,
|
||||
"room_id": room,
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
"event_id": event_ids[2],
|
||||
"depth": 3,
|
||||
"user_id": f"@vona:{config.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:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules",
|
||||
"event_id": event_ids[3],
|
||||
"room_id": room,
|
||||
"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:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.guest_access",
|
||||
"event_id": event_ids[4],
|
||||
"room_id": room,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["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:{config.server_name}",
|
||||
"state_key": "",
|
||||
"origin_server_ts": 6,
|
||||
"depth": 6,
|
||||
"event_id": event_ids[5],
|
||||
"room_id": room,
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
screate_event["hashes"]
|
||||
],
|
||||
[
|
||||
sour_join["event_id"],
|
||||
sour_join["hashes"]
|
||||
],
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["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()
|
||||
|
||||
return {
|
||||
"auth_chain": event_chain,
|
||||
"event": remote_join,
|
||||
"members_omitted": False,
|
||||
"servers_in_room": [config.server_name],
|
||||
"state": event_chain
|
||||
}
|
||||
|
||||
|
||||
# Room V3 to V10
|
||||
def v3(request, room) -> dict:
|
||||
initial_response = v1_v2(request, room)
|
||||
state = list(initial_response["state"])
|
||||
ver = int(globals.room_version_from_id(room))
|
||||
events = {}
|
||||
hash_map = {}
|
||||
|
||||
for event in state:
|
||||
events[event["type"]] = event
|
||||
del event["event_id"]
|
||||
del event["hashes"]
|
||||
del event["signatures"]
|
||||
|
||||
# m.room.create doesn't have prev_events or auth_events
|
||||
if ver >= 11:
|
||||
del events["m.room.create"]["content"]["creator"]
|
||||
events["m.room.create"] = globals.hash_and_sign_event(events["m.room.create"], ver)
|
||||
hash_map["m.room.create"] = globals.make_ref_hash(events["m.room.create"], ver)
|
||||
|
||||
events["m.room.member"]["auth_events"] = [hash_map["m.room.create"]]
|
||||
events["m.room.member"]["prev_events"] = [hash_map["m.room.create"]]
|
||||
events["m.room.member"] = globals.hash_and_sign_event(events["m.room.member"], ver)
|
||||
hash_map["m.room.member"] = globals.make_ref_hash(events["m.room.member"], ver)
|
||||
|
||||
events["m.room.power_levels"]["auth_events"] = [
|
||||
hash_map["m.room.create"],
|
||||
hash_map["m.room.member"],
|
||||
]
|
||||
events["m.room.power_levels"]["prev_events"] = [hash_map["m.room.member"]]
|
||||
if ver >= 10:
|
||||
events["m.room.power_levels"]["content"]["users"][f"@vona:{config.server_name}"] = 100
|
||||
events["m.room.power_levels"] = globals.hash_and_sign_event(events["m.room.power_levels"], ver)
|
||||
hash_map["m.room.power_levels"] = globals.make_ref_hash(events["m.room.power_levels"], ver)
|
||||
|
||||
events["m.room.join_rules"]["auth_events"] = [
|
||||
hash_map["m.room.create"],
|
||||
hash_map["m.room.member"],
|
||||
hash_map["m.room.power_levels"],
|
||||
]
|
||||
events["m.room.join_rules"]["prev_events"] = [hash_map["m.room.power_levels"]]
|
||||
events["m.room.join_rules"] = globals.hash_and_sign_event(events["m.room.join_rules"], ver)
|
||||
hash_map["m.room.join_rules"] = globals.make_ref_hash(events["m.room.join_rules"], ver)
|
||||
|
||||
events["m.room.guest_access"]["auth_events"] = [
|
||||
hash_map["m.room.create"],
|
||||
hash_map["m.room.member"],
|
||||
hash_map["m.room.power_levels"],
|
||||
]
|
||||
events["m.room.guest_access"]["prev_events"] = [hash_map["m.room.join_rules"]]
|
||||
events["m.room.guest_access"] = globals.hash_and_sign_event(events["m.room.guest_access"], ver)
|
||||
hash_map["m.room.guest_access"] = globals.make_ref_hash(events["m.room.guest_access"], ver)
|
||||
|
||||
events["m.room.history_visibility"]["auth_events"] = [
|
||||
hash_map["m.room.create"],
|
||||
hash_map["m.room.member"],
|
||||
hash_map["m.room.power_levels"],
|
||||
]
|
||||
events["m.room.history_visibility"]["prev_events"] = [hash_map["m.room.guest_access"]]
|
||||
events["m.room.history_visibility"] = globals.hash_and_sign_event(events["m.room.history_visibility"], ver)
|
||||
|
||||
new_state = []
|
||||
|
||||
for event in events:
|
||||
new_state.append(events[event])
|
||||
|
||||
|
||||
return {
|
||||
"auth_chain": new_state,
|
||||
"event": initial_response["event"],
|
||||
"members_omitted": False,
|
||||
"servers_in_room": [config.server_name],
|
||||
"state": new_state
|
||||
}
|
||||
@@ -1,17 +1,23 @@
|
||||
from resolvematrix import ServerResolver
|
||||
from types import SimpleNamespace
|
||||
from collections import Counter
|
||||
|
||||
import vona.config as config
|
||||
import nacl.encoding
|
||||
import nacl.signing
|
||||
import hashlib
|
||||
import base64
|
||||
import random
|
||||
import copy
|
||||
import httpx
|
||||
import json
|
||||
import copy
|
||||
import time
|
||||
import re
|
||||
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
|
||||
|
||||
def canonical_json(value):
|
||||
def canonical_json(value: dict | list) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
@@ -20,7 +26,7 @@ def canonical_json(value):
|
||||
).encode("UTF-8")
|
||||
|
||||
|
||||
def sign_json(data):
|
||||
def sign_json(data: dict) -> dict:
|
||||
parts = config.signing_key.split()
|
||||
base64_key = parts[2]
|
||||
|
||||
@@ -49,7 +55,7 @@ def sign_json(data):
|
||||
return signed_json
|
||||
|
||||
|
||||
def sign_json_without_discard(data):
|
||||
def sign_json_without_discard(data: dict) -> dict:
|
||||
parts = config.signing_key.split()
|
||||
base64_key = parts[2]
|
||||
|
||||
@@ -82,7 +88,7 @@ def sign_json_without_discard(data):
|
||||
return data
|
||||
|
||||
|
||||
def make_event_id(seed=None):
|
||||
def make_event_id(seed: str | int | None = None) -> str:
|
||||
if seed is not None:
|
||||
random.seed(seed)
|
||||
|
||||
@@ -100,7 +106,7 @@ def make_event_id(seed=None):
|
||||
return event_id
|
||||
|
||||
|
||||
def event_hash(event_object):
|
||||
def event_hash(event_object: dict) -> str:
|
||||
event_object = dict(event_object)
|
||||
|
||||
event_object.pop("unsigned", None)
|
||||
@@ -109,7 +115,9 @@ def event_hash(event_object):
|
||||
|
||||
event_json_bytes = canonical_json(event_object)
|
||||
|
||||
return base64.b64encode(hashlib.sha256(event_json_bytes).digest()).decode("utf-8")
|
||||
return base64.b64encode(
|
||||
hashlib.sha256(event_json_bytes).digest()
|
||||
).decode("utf-8").rstrip("=")
|
||||
|
||||
|
||||
def pubkey() -> str:
|
||||
@@ -127,7 +135,12 @@ def pubkey() -> str:
|
||||
)
|
||||
|
||||
|
||||
def make_auth_header(destination, method, path, content=None) -> str:
|
||||
def make_auth_header(
|
||||
destination: str,
|
||||
method: str,
|
||||
path: str,
|
||||
content = None
|
||||
) -> str:
|
||||
request_json = {
|
||||
"method": method,
|
||||
"uri": path,
|
||||
@@ -159,9 +172,13 @@ def make_auth_header(destination, method, path, content=None) -> str:
|
||||
return authorization_headers[0].decode("utf-8")
|
||||
|
||||
|
||||
def redact_event(event):
|
||||
def redact_event(
|
||||
event: dict,
|
||||
for_event_id: bool = False,
|
||||
room_ver: int = 1,
|
||||
) -> dict:
|
||||
# Returns a redacted event as per
|
||||
# the algorithm for v1/v2 rooms.
|
||||
# the algorithm for v1 to v11 rooms.
|
||||
|
||||
allowed_keys = [
|
||||
"event_id",
|
||||
@@ -171,16 +188,20 @@ def redact_event(event):
|
||||
"state_key",
|
||||
"content",
|
||||
"hashes",
|
||||
"signatures",
|
||||
"depth",
|
||||
"prev_events",
|
||||
"prev_state",
|
||||
"auth_events",
|
||||
"origin",
|
||||
"origin_server_ts",
|
||||
"membership",
|
||||
]
|
||||
|
||||
if not for_event_id:
|
||||
allowed_keys.append("signatures")
|
||||
|
||||
if room_ver < 11:
|
||||
allowed_keys.append("origin")
|
||||
allowed_keys.append("membership")
|
||||
allowed_keys.append("prev_state")
|
||||
|
||||
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:
|
||||
@@ -204,49 +225,246 @@ def redact_event(event):
|
||||
"m.room.history_visibility": ["history_visibility"],
|
||||
}
|
||||
|
||||
if room_ver >= 6:
|
||||
del content_key_rules["m.room.aliases"]
|
||||
|
||||
if room_ver >= 8:
|
||||
content_key_rules["m.room.join_rules"].append("allow")
|
||||
|
||||
if room_ver >= 9:
|
||||
content_key_rules["m.room.member"].append("join_authorised_via_users_server")
|
||||
|
||||
if room_ver >= 11:
|
||||
content_key_rules["m.room.redaction"] = ["redacts"]
|
||||
del content_key_rules["m.room.create"] # All keys will be permitted
|
||||
|
||||
|
||||
if event_type in content_key_rules:
|
||||
allowed_content_keys = content_key_rules[event_type]
|
||||
|
||||
if (
|
||||
room_ver >= 11
|
||||
and "third_party_invite" in redacted_event
|
||||
and "signed" in redacted_event["third_party_invite"]
|
||||
):
|
||||
third_party_invite_signature = copy.deepcopy(redacted_event["third_party_invite"]["signed"])
|
||||
else:
|
||||
third_party_invite_signature = None
|
||||
|
||||
redacted_event["content"] = {
|
||||
k: v
|
||||
for k, v in redacted_event["content"].items()
|
||||
if k in allowed_content_keys
|
||||
}
|
||||
|
||||
if third_party_invite_signature:
|
||||
redacted_event["content"]["third_party_invite"] = {
|
||||
"signed": third_party_invite_signature
|
||||
}
|
||||
|
||||
else:
|
||||
if room_ver >= 11 and event_type == "m.room.create":
|
||||
pass
|
||||
else:
|
||||
redacted_event["content"] = {}
|
||||
|
||||
return redacted_event
|
||||
|
||||
|
||||
def hash_and_sign_event(event_object):
|
||||
def hash_and_sign_event(
|
||||
event_object: dict,
|
||||
room_ver: int = 1,
|
||||
) -> dict:
|
||||
content_hash = event_hash(event_object)
|
||||
event_object["hashes"] = {"sha256": content_hash}
|
||||
stripped_object = redact_event(event_object)
|
||||
stripped_object = redact_event(
|
||||
event=event_object,
|
||||
for_event_id=False,
|
||||
room_ver=room_ver,
|
||||
)
|
||||
signed_object = sign_json(stripped_object)
|
||||
event_object["signatures"] = signed_object["signatures"]
|
||||
return event_object
|
||||
|
||||
|
||||
def room_version_from_id(room_id):
|
||||
room_id_no_sigil = room_id.replace("!", "")
|
||||
def make_ref_hash(
|
||||
event: dict,
|
||||
room_ver: int = 3,
|
||||
) -> str:
|
||||
stripped = redact_event(
|
||||
event=event,
|
||||
for_event_id=True,
|
||||
room_ver=room_ver,
|
||||
)
|
||||
evt_bytes = canonical_json(stripped)
|
||||
|
||||
evt_hash = base64.b64encode(
|
||||
hashlib.sha256(evt_bytes).digest()
|
||||
).decode("utf-8").rstrip("=")
|
||||
|
||||
if room_ver > 3:
|
||||
while "+" in evt_hash:
|
||||
evt_hash = evt_hash.replace("+", "-")
|
||||
while "/" in evt_hash:
|
||||
evt_hash = evt_hash.replace("/", "_")
|
||||
|
||||
return "$" + evt_hash
|
||||
|
||||
|
||||
def room_version_from_id(room) -> str:
|
||||
room_id_no_sigil = (
|
||||
room
|
||||
.replace("!", "")
|
||||
.replace(f":{config.server_name}", "")
|
||||
)
|
||||
hexadecimal_room_id = bytes(room_id_no_sigil, "utf-8").hex()
|
||||
|
||||
if "1" not in hexadecimal_room_id and "2" not in hexadecimal_room_id:
|
||||
# NOTE: v2 if impossible from room ID alone
|
||||
versions = [str(i) for i in range(0, 10)]
|
||||
versions.append("a")
|
||||
|
||||
if not any(ver in hexadecimal_room_id for ver in versions):
|
||||
hexadecimal_room_id = "2" + hexadecimal_room_id[1:]
|
||||
|
||||
def remove_chars(s):
|
||||
return re.sub("[^12]", "", s)
|
||||
return re.sub(f"[^{''.join(versions)}]", "", s)
|
||||
|
||||
nums = remove_chars(hexadecimal_room_id)
|
||||
|
||||
def most_common_character(s):
|
||||
s = s.replace(" ", "").lower()
|
||||
|
||||
counts = Counter(s)
|
||||
|
||||
most_common = counts.most_common(1)
|
||||
|
||||
return most_common[0] if most_common else None
|
||||
actual_most_common = most_common[0] if most_common else ("4",)
|
||||
if actual_most_common[0] == "0":
|
||||
return ("10",)
|
||||
elif actual_most_common[0] == "a":
|
||||
return ("11",)
|
||||
return actual_most_common
|
||||
|
||||
return most_common_character(nums)[0]
|
||||
return str(most_common_character(nums)[0])
|
||||
|
||||
|
||||
room_dir = {
|
||||
"chunk": [{
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"guest_can_join": False,
|
||||
"join_rule": "public",
|
||||
"name": "Vona",
|
||||
"num_joined_members": 1,
|
||||
"room_id": make_event_id().replace("$", "!"),
|
||||
"room_type": "m.room",
|
||||
"topic": "",
|
||||
"world_readable": False,
|
||||
"via": [config.server_name]
|
||||
}],
|
||||
"total_room_count_estimate": 1
|
||||
}
|
||||
|
||||
|
||||
class http_client:
|
||||
http = httpx.Client(headers={"User-Agent": f"Vona/{version}"})
|
||||
resolver = ServerResolver(client=http)
|
||||
|
||||
def _resolve(self, target) -> SimpleNamespace:
|
||||
r = self.resolver.resolve(target)
|
||||
|
||||
if r.sni:
|
||||
sni = r.sni
|
||||
else:
|
||||
sni = r.host_header
|
||||
|
||||
return SimpleNamespace(
|
||||
base_url=r.base_url,
|
||||
host_header=r.host_header,
|
||||
sni=sni
|
||||
)
|
||||
|
||||
def put(
|
||||
self,
|
||||
path: str,
|
||||
destination: str,
|
||||
headers: dict = {},
|
||||
authorize: bool = True,
|
||||
json: dict = {},
|
||||
):
|
||||
resolved = self._resolve(destination)
|
||||
|
||||
if authorize:
|
||||
headers["Authorization"] = make_auth_header(
|
||||
method="PUT",
|
||||
destination=destination,
|
||||
path=path,
|
||||
content=json,
|
||||
)
|
||||
|
||||
headers["Host"] = resolved.host_header
|
||||
|
||||
return self.http.put(
|
||||
f"{resolved.base_url}{path}",
|
||||
headers=headers,
|
||||
extensions={"sni_hostname": resolved.sni},
|
||||
json=json,
|
||||
)
|
||||
|
||||
def get(
|
||||
self,
|
||||
path: str,
|
||||
destination: str,
|
||||
headers: dict = {},
|
||||
authorize: bool = True,
|
||||
):
|
||||
resolved = self._resolve(destination)
|
||||
|
||||
if authorize:
|
||||
headers["Authorization"] = make_auth_header(
|
||||
method="GET",
|
||||
destination=destination,
|
||||
path=path,
|
||||
)
|
||||
|
||||
headers["Host"] = resolved.host_header
|
||||
|
||||
return self.http.get(
|
||||
f"{resolved.base_url}{path}",
|
||||
headers=headers,
|
||||
extensions={"sni_hostname": resolved.sni},
|
||||
)
|
||||
|
||||
|
||||
def strip_state(state_events) -> list:
|
||||
if not isinstance(state_events, list):
|
||||
return state_events
|
||||
|
||||
keys_to_remove = [
|
||||
"auth_events",
|
||||
"prev_events",
|
||||
"signatures",
|
||||
"hashes",
|
||||
"depth"
|
||||
]
|
||||
|
||||
new_list = []
|
||||
for d in state_events:
|
||||
if not isinstance(d, dict):
|
||||
new_list.append(d)
|
||||
continue
|
||||
|
||||
if "room_id" in d:
|
||||
ver = int(room_version_from_id(d["room_id"]))
|
||||
else:
|
||||
ver = 4
|
||||
|
||||
event_id = make_ref_hash(d, ver)
|
||||
new_dict = {}
|
||||
for k, v in d.items():
|
||||
if k in keys_to_remove:
|
||||
continue
|
||||
new_dict[k] = strip_state(v)
|
||||
new_dict["event_id"] = event_id
|
||||
new_list.append(new_dict)
|
||||
return new_list
|
||||
|
||||
|
||||
def get_time() -> int:
|
||||
return int(str(time.time() * 1000).split(".")[0])
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from vona.config import server_name, the_funny_number
|
||||
import vona.config as config
|
||||
import vona.globals as globals
|
||||
import time
|
||||
import os
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
jsonify,
|
||||
request,
|
||||
)
|
||||
|
||||
identity = Blueprint("identity", __name__)
|
||||
|
||||
@@ -8,10 +15,8 @@ identity = Blueprint("identity", __name__)
|
||||
# I'm pretty sure only Element uses this,
|
||||
# but oh well.
|
||||
|
||||
# https://spec.matrix.org/latest/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",
|
||||
@@ -27,10 +32,11 @@ async def versions():
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#authentication
|
||||
@identity.route("/_matrix/identity/v2/account")
|
||||
async def account_info():
|
||||
return jsonify({"user_id": f"@vona:{server_name}"})
|
||||
return jsonify({
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/account/logout", methods=["POST"])
|
||||
@@ -43,110 +49,140 @@ async def register():
|
||||
return jsonify({"token": "vona"})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#terms-of-service
|
||||
@identity.route("/_matrix/identity/v2/terms", methods=["GET", "POST"])
|
||||
async def policies():
|
||||
if request.method == "GET":
|
||||
return jsonify({"policies":{}})
|
||||
return jsonify({
|
||||
"policies": {}
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2")
|
||||
async def status():
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/pubkey/ephemeral/isvalid")
|
||||
@identity.route("/_matrix/identity/v2/pubkey/isvalid")
|
||||
async def pubkey_validity():
|
||||
return jsonify({"valid": True})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/pubkey/<key>")
|
||||
async def get_key(key):
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "The public key was not found"
|
||||
}), 404
|
||||
"public_key": globals.pubkey()
|
||||
})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/hash_details")
|
||||
async def hash_details():
|
||||
return jsonify({"algorithms":["none","sha256"],"lookup_pepper": "vona"})
|
||||
return jsonify({
|
||||
"algorithms": [
|
||||
"none",
|
||||
"sha256",
|
||||
],
|
||||
"lookup_pepper": "vona"
|
||||
})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/lookup", methods=["POST"])
|
||||
async def lookup():
|
||||
req = request.json
|
||||
|
||||
if "addresses" in req:
|
||||
return jsonify({"mappings": {req["addresses"][0]: f"@vona:{server_name}"}})
|
||||
else:
|
||||
return jsonify({"errcode": "M_INVALID_PEPPER","error": "Invalid pepper"})
|
||||
if (
|
||||
isinstance(req, dict)
|
||||
and "addresses" in req
|
||||
and isinstance(req["addresses"], list)
|
||||
and len(req["addresses"]) > 0
|
||||
):
|
||||
return jsonify({
|
||||
"mappings": {
|
||||
req["addresses"][0]: f"@vona:{config.server_name}"
|
||||
}
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"errcode": "M_INVALID_PEPPER",
|
||||
"error": "Invalid pepper"
|
||||
})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/validate/email/requestToken", methods=["POST"])
|
||||
@identity.route("/_matrix/identity/v2/validate/msisdn/requestToken", methods=["POST"])
|
||||
async def request_validation_token():
|
||||
return jsonify({"sid": str(the_funny_number)})
|
||||
return jsonify({
|
||||
"sid": os.urandom(16).hex()
|
||||
})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/validate/email/submitToken", methods=["GET", "POST"])
|
||||
@identity.route("/_matrix/identity/v2/validate/msisdn/submitToken", methods=["GET", "POST"])
|
||||
async def submit_validation_token():
|
||||
return jsonify({"success": True})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/3pid/bind", methods=["POST"])
|
||||
async def threepid_bind():
|
||||
if "mxid" in request.get_json():
|
||||
mxid = request.get_json()["mxid"]
|
||||
else:
|
||||
mxid = f"@vona:{server_name}"
|
||||
mxid = f"@vona:{config.server_name}"
|
||||
|
||||
return jsonify(globals.sign_json({
|
||||
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():
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@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
|
||||
"validated_at": config.the_funny_number
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/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": f"https://{server_name}/_matrix/identity/v2/pubkey/isvalid",
|
||||
"key_validity_url": f"https://{config.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"
|
||||
"key_validity_url": f"https://{config.server_name}/_matrix/identity/v2/pubkey/ephemeral/isvalid",
|
||||
"public_key": "burgerkingfootlettuce"
|
||||
}
|
||||
],
|
||||
"token": "vona"
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#ephemeral-invitation-signing
|
||||
@identity.route("/_matrix/identity/v2/sign-ed25519", methods=["POST"])
|
||||
async def invite_signing():
|
||||
required_keys = {"mxid", "private_key", "token"}
|
||||
d = data.get_json()
|
||||
d = request.data.get_json()
|
||||
|
||||
if set(d.keys()) == required_keys:
|
||||
return jsonify(sign_json(d))
|
||||
return jsonify(globals.sign_json(d))
|
||||
else:
|
||||
return jsonify({
|
||||
"errcode": "M_UNRECOGNIZED",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from flask import jsonify, Blueprint, request
|
||||
from flask import (
|
||||
jsonify,
|
||||
Blueprint,
|
||||
request,
|
||||
)
|
||||
|
||||
policy = Blueprint("policy", __name__)
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
print("Available utils:")
|
||||
|
||||
a = [
|
||||
"makekey"
|
||||
"makekey - Generate a signing key",
|
||||
"joinroom - Join a remote room",
|
||||
"roomwithver - Brute-force a room with a specific room version",
|
||||
]
|
||||
|
||||
for t in a:
|
||||
|
||||
121
vona/utils/joinroom.py
Normal file
121
vona/utils/joinroom.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
import urllib.parse
|
||||
import time
|
||||
import json
|
||||
import httpx
|
||||
|
||||
|
||||
http_client = globals.http_client()
|
||||
|
||||
versions = [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"10",
|
||||
"11",
|
||||
"12",
|
||||
"org.matrix.msc3757.10",
|
||||
"org.matrix.msc3757.11",
|
||||
"org.matrix.hydra.11",
|
||||
"org.matrix.msc3667",
|
||||
"org.matrix.msc3787",
|
||||
"org.matrix.msc4014",
|
||||
]
|
||||
|
||||
verparams = "&".join(f"ver={ver}" for ver in versions)
|
||||
|
||||
|
||||
def get_user_input(prompt):
|
||||
try:
|
||||
answer = input(prompt)
|
||||
while "\\n" in answer:
|
||||
answer = answer.replace("\\n", "\n")
|
||||
|
||||
return urllib.parse.quote(answer)
|
||||
except Exception as e:
|
||||
print(f"Error reading input: {e}")
|
||||
return None
|
||||
|
||||
|
||||
username = get_user_input("Username:\n\t")
|
||||
room_id = get_user_input("Room ID:\n\t")
|
||||
|
||||
|
||||
try:
|
||||
server_name = input("\nServer name to join via:\n\t")
|
||||
except Exception as e:
|
||||
print(f"Error reading server names: {e}")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
print("\nSending make_join request..")
|
||||
|
||||
make_join_response = http_client.get(
|
||||
path=f"/_matrix/federation/v1/make_join/{room_id}/%40{username}%3A{config.server_name}?{verparams}",
|
||||
destination=server_name,
|
||||
)
|
||||
|
||||
make_join_response.raise_for_status()
|
||||
make_join = make_join_response.json()
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(f"HTTP error occurred: {e.response.status_code} - {e.response.text}")
|
||||
exit(1)
|
||||
except json.JSONDecodeError:
|
||||
print("Failed to decode response.")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
join_event = make_join.get("event", {})
|
||||
if make_join.get("room_version", "1") in ["1", "2"]:
|
||||
# NOTE: if we always make it opaque than Synapse will 500 lmao
|
||||
join_event["event_id"] = globals.make_event_id()
|
||||
|
||||
elif make_join.get("room_version", "1") == "org.matrix.msc4014":
|
||||
# dendrite dislikes this :p
|
||||
join_event["sender_key"] = globals.pubkey()
|
||||
|
||||
join_event["content"]["mxid_mapping"] = globals.sign_json({
|
||||
"sender_key": globals.pubkey(),
|
||||
"user_id": f"@{username}:{config.server_name}"
|
||||
})
|
||||
|
||||
|
||||
timestamp = input("\nTimestamp (leave blank for now):\n\t")
|
||||
try:
|
||||
join_event["origin_server_ts"] = int(timestamp)
|
||||
except ValueError:
|
||||
join_event["origin_server_ts"] = int(f"{time.time() * 1000}".split(".")[0])
|
||||
|
||||
signed_join = globals.hash_and_sign_event(
|
||||
join_event,
|
||||
int(make_join.get("room_version", "1"))
|
||||
)
|
||||
|
||||
try:
|
||||
send_join_response = http_client.put(
|
||||
path=f"/_matrix/federation/v2/send_join/{room_id}/%24doesntmatter?omit_members=true",
|
||||
json=signed_join,
|
||||
destination=server_name,
|
||||
)
|
||||
|
||||
send_join_response.raise_for_status()
|
||||
|
||||
print("\nSuccess :)")
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(
|
||||
f"HTTP error occurred during send_join: {e.response.status_code} - {e.response.text}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"An error occurred during send_join: {e}")
|
||||
@@ -1,25 +1,23 @@
|
||||
# Generates a key in the format compatible with Synapse and Vona.
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
# Generates a key in the format compatible with Synapse and Vona.
|
||||
|
||||
|
||||
def mkchar() -> str:
|
||||
return os.urandom(4).hex()[:1]
|
||||
|
||||
|
||||
key = (
|
||||
base64.b64encode(os.urandom(32))
|
||||
.decode("utf-8")[:43]
|
||||
def random(length):
|
||||
return (
|
||||
base64.b64encode(os.urandom(length))
|
||||
.decode("utf-8")[:length]
|
||||
.replace("/", mkchar())
|
||||
.replace("+", mkchar())
|
||||
)
|
||||
|
||||
key_id = (
|
||||
base64.b64encode(os.urandom(32))
|
||||
.decode("utf-8")[:6]
|
||||
.replace("/", mkchar())
|
||||
.replace("+", mkchar())
|
||||
)
|
||||
|
||||
key = random(43)
|
||||
key_id = random(6)
|
||||
|
||||
print(f"ed25519 {key_id} {key}")
|
||||
|
||||
62
vona/utils/roomwithver.py
Normal file
62
vona/utils/roomwithver.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import multiprocessing
|
||||
|
||||
versions = [str(i) for i in range(1, 10)]
|
||||
|
||||
try:
|
||||
desired_ver = input("Desired room version:\n\t")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("")
|
||||
exit(0)
|
||||
|
||||
known = {
|
||||
"1": "qa",
|
||||
"2": "br",
|
||||
"3": "3",
|
||||
"4": "D",
|
||||
"5": "U",
|
||||
"6": "f",
|
||||
"7": "gx",
|
||||
"8": "hx",
|
||||
"9": "iy",
|
||||
"10": "p0",
|
||||
"11": "jZ",
|
||||
}
|
||||
|
||||
if desired_ver in known:
|
||||
print(f"!{known[desired_ver]}:{config.server_name} (from known room ID list)")
|
||||
try:
|
||||
input("Press enter to bruteforce anyway:\n\t")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("")
|
||||
exit(0)
|
||||
|
||||
elif desired_ver not in versions:
|
||||
print("Unsupported room version")
|
||||
exit(1)
|
||||
|
||||
def worker(room_found):
|
||||
try:
|
||||
while not room_found.value:
|
||||
room_id = globals.make_event_id().replace("$", "!")
|
||||
room_ver = globals.room_version_from_id(room_id)
|
||||
|
||||
if room_ver == desired_ver:
|
||||
print(room_id)
|
||||
room_found.value = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
room_found = multiprocessing.Value('b', False)
|
||||
processes = []
|
||||
|
||||
for _ in range(multiprocessing.cpu_count()):
|
||||
p = multiprocessing.Process(target=worker, args=(room_found,))
|
||||
processes.append(p)
|
||||
p.start()
|
||||
|
||||
for p in processes:
|
||||
p.join()
|
||||
Reference in New Issue
Block a user