Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
492ffc62e9
|
|||
|
9ca4c913f3
|
|||
|
6dd4fb04e3
|
|||
|
f771130a16
|
|||
|
9aa2e062e5
|
|||
|
c8d6f57e5b
|
|||
|
753625ad5c
|
|||
|
74a36e0b34
|
|||
|
b178011ad9
|
|||
|
76f89beb29
|
|||
|
7e8eb6de7f
|
|||
|
4e28507dea
|
|||
|
357d3b7429
|
|||
|
8a2bae706e
|
|||
|
4603ed86ac
|
|||
|
4a6e65226e
|
|||
|
fc9787b8ec
|
|||
|
2dbec63ff7
|
|||
|
2021fc027b
|
|||
|
e0115fe8db
|
|||
|
7e77f009db
|
|||
|
316358a82a
|
|||
|
d801715dab
|
|||
|
6709b91d35
|
|||
|
6268ea67b0
|
|||
|
7f5cf23fd2
|
|||
|
2e7982b56f
|
|||
|
8fdffd573a
|
|||
|
2d3dea750f
|
|||
|
470d4cc4d9
|
|||
|
9985fb48a8
|
0
.hgignore → .gitignore
vendored
0
.hgignore → .gitignore
vendored
32
README.md
Normal file
32
README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Vona 🎉✨🔍🎊🌟💻🤔🛠️🚀
|
||||
Welcome to Vona! 🎊 Vona is a flazing bast 🌟, and semory mafe [matrix] implementation made in Python 🐍 for stability.
|
||||
Vona implements all endpoints incorrectly ❌, assuring compatibility with other server software 💻, like Synapse.
|
||||
|
||||
## How do I run it? 🔍🤔
|
||||
Please don't 🙅♂️
|
||||
|
||||
## Installation Instructions 🛠️🔧
|
||||
Use the power of Hopes and Dreams 🍀🌈✨
|
||||
|
||||
## Why you should use Vona 🚀🌟
|
||||
- Blazing fast ⚡, and memory safe 🛡️
|
||||
- Filters out spam efficiently 🚫📧
|
||||
- Uses minimal storage 💾📦
|
||||
- Secure 🔒 and low maintenance 🧹
|
||||
- Complies with the [spec](https://spec.matrix.org) 📜✅
|
||||
- At least 1 instance running in the wild 🌍🐾
|
||||
- Policy server for ease of moderation 🧹🛡️
|
||||
|
||||
## What people are saying about Vona 🤩💬
|
||||
- "this codebase is effectively an art project"
|
||||
- @olivia:computer.surgery, one of the maintainers of
|
||||
the [Grapevine](https://grapevine.computer.surgery)
|
||||
homeserver software 💻
|
||||
- "this is amazing 😄"
|
||||
- @sarah:4d2.org, president of the popular 4d2.org
|
||||
homeserver 👑
|
||||
|
||||
## License 📜🔑
|
||||
We use the Velicense. Read more in the LICENSE file 📂📖.
|
||||
|
||||
Enjoy your journey with Vona! 🌈🚀🎉
|
||||
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.
|
||||
@@ -1,18 +1,23 @@
|
||||
[project]
|
||||
name = "vona"
|
||||
version = "1.4.1"
|
||||
description = "Flazing bast Matrix homeserver"
|
||||
authors = [
|
||||
{name = "Kierre",email = "vel@riseup.net"}
|
||||
]
|
||||
license = {text = "Velicense"}
|
||||
requires-python = ">=3.13"
|
||||
requires-python = ">=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)",
|
||||
"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 = [
|
||||
{ name = "Kierre", email = "vel@riseup.net" }
|
||||
]
|
||||
|
||||
# We put this in the code itself rather than here.
|
||||
version = "0.0.0"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
|
||||
@@ -2,7 +2,9 @@ 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 vona.federation import server
|
||||
from vona.custom import custom
|
||||
@@ -24,9 +26,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 as e:
|
||||
return jsonify({"error": "Content not JSON.", "errcode": "M_NOT_JSON"}), 400
|
||||
|
||||
|
||||
@app.after_request
|
||||
async def handle_logging(response):
|
||||
@@ -36,7 +49,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 +56,31 @@ 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:
|
||||
pass
|
||||
|
||||
print(f'[{origin}] [{request.remote_addr}] [{datetime.now().strftime("%d/%b/%Y:%H:%M:%S")}] {request.method} {request.full_path} {response.status_code}')
|
||||
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
|
||||
|
||||
@@ -62,7 +92,7 @@ async def root():
|
||||
|
||||
@app.route("/_matrix/static/")
|
||||
async def matrix_static():
|
||||
return f'<!DOCTYPE html><html lang="en"><head><title>Vona {globals.vona_version} is running</title><style>body {{font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;max-width: 40em;margin: auto;text-align: center;}}h1,p {{margin: 1.5em;}}hr {{border: none;background-color: #ccc;color: #ccc;height: 1px;width: 7em;margin-top: 4em;}}.logo {{display: block;width: 12em;height: auto;margin: 4em auto;}}</style></head><body><img src="https://matrix.org/images/matrix-logo.svg" class="logo"><h1>It works! Vona {globals.vona_version} is running</h1><p>Your Vona server is listening on this port and is ready for messages.</p><p>To use this server you\"ll need <a href="https://matrix.org/ecosystem/clients/" target="_blank"rel="noopener noreferrer">a Matrix client</a>.</p><p>Welcome to the Matrix universe :)</p><hr><p><small><a href="https://natribu.org/en/" target="_blank" rel="noopener noreferrer">matrix.org</a></small></p></body></html>'
|
||||
return f'<!DOCTYPE html><html lang="en"><head><title>Vona {globals.version} is running</title><style>body {{font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;max-width: 40em;margin: auto;text-align: center;}}h1,p {{margin: 1.5em;}}hr {{border: none;background-color: #ccc;color: #ccc;height: 1px;width: 7em;margin-top: 4em;}}.logo {{display: block;width: 12em;height: auto;margin: 4em auto;}}</style></head><body><img src="https://matrix.org/images/matrix-logo.svg" class="logo"><h1>It works! Vona {globals.version} is running</h1><p>Your Vona server is listening on this port and is ready for messages.</p><p>To use this server you\"ll need <a href="https://matrix.org/ecosystem/clients/" target="_blank"rel="noopener noreferrer">a Matrix client</a>.</p><p>Welcome to the Matrix universe :)</p><hr><p><small><a href="https://natribu.org/en/" target="_blank" rel="noopener noreferrer">matrix.org</a></small></p></body></html>'
|
||||
|
||||
|
||||
# Error handlers
|
||||
@@ -100,7 +130,20 @@ async def client():
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host=config.addr, port=config.port)
|
||||
else:
|
||||
print("What the hell are you doing?")
|
||||
def federation_self_test():
|
||||
try:
|
||||
resp = globals.http_client().get(
|
||||
path="/",
|
||||
destination=config.server_name,
|
||||
)
|
||||
|
||||
resp.raise_for_status()
|
||||
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)
|
||||
|
||||
@@ -56,7 +56,7 @@ async def spec_versions():
|
||||
@client.route("/_matrix/client/v3/admin/whois/<user>")
|
||||
@client.route("/_matrix/client/r0/admin/whois/<user>")
|
||||
async def whois(user):
|
||||
if userId.startswith("@"):
|
||||
if user.startswith("@"):
|
||||
return jsonify({
|
||||
"devices": {
|
||||
"": {
|
||||
@@ -64,7 +64,7 @@ async def whois(user):
|
||||
"connections": [{
|
||||
"ip": "127.0.0.1",
|
||||
"last_seen": config.the_funny_number,
|
||||
"user_agent": f"Vona/{globals.vona_version}"
|
||||
"user_agent": f"Vona/{globals.version}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
@@ -446,7 +446,7 @@ async def room_summary(roomId):
|
||||
"join_rule": "public",
|
||||
"room_type": "m.room",
|
||||
"membership": "join",
|
||||
"room_version": 2
|
||||
"room_version": globals.room_version_from_id(roomId)
|
||||
})
|
||||
|
||||
|
||||
@@ -644,14 +644,10 @@ async def presence(user):
|
||||
if request.method == "PUT":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"presence": "online"
|
||||
})
|
||||
return jsonify({"presence": "online"})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/publicRooms", methods=["GET", "POST"])
|
||||
@client.route("/_matrix/client/r0/publicRooms", methods=["GET", "POST"])
|
||||
async def room_directory():
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
return jsonify(globals.room_dir)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
import mimetypes
|
||||
|
||||
addr: str = "127.0.0.1"
|
||||
port: int = 5000
|
||||
allow_registration: bool = False
|
||||
users_can_register: bool = False
|
||||
the_funny_number: int = 1337
|
||||
cat: str = "/etc/vona/cat.jpg"
|
||||
|
||||
@@ -13,7 +12,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:
|
||||
@@ -30,37 +29,34 @@ def _load_toml(path: Path) -> dict:
|
||||
with path.open("rb") as f:
|
||||
return tomllib.load(f)
|
||||
except FileNotFoundError:
|
||||
_fatal(f"[FATL] Configuration file not found at {path}")
|
||||
_fatal(f"Configuration file not found at {path}")
|
||||
except PermissionError:
|
||||
_fatal(f"[FATL] Permission denied when accessing configuration {path}")
|
||||
_fatal(f"Permission denied when accessing configuration {path}")
|
||||
except tomllib.TOMLDecodeError as e:
|
||||
_fatal(f"[FATL] Invalid TOML configuration: {e}")
|
||||
_fatal(f"Invalid TOML configuration: {e}")
|
||||
|
||||
|
||||
def _read_signing_key_from_path(path_value) -> str | None:
|
||||
p = Path(path_value)
|
||||
|
||||
if not p.exists():
|
||||
_fatal(f"[FATL] signing_key_path {p} does not exist")
|
||||
_fatal(f"signing_key_path {p} does not exist")
|
||||
try:
|
||||
return p.read_text(encoding="utf-8").strip()
|
||||
except Exception as e:
|
||||
_fatal(f"[FATL] Failed to read signing_key_path {p}: {e}")
|
||||
_fatal(f"Failed to read signing_key_path {p}: {e}")
|
||||
|
||||
|
||||
def _validate_cat_path(cat_path: str) -> Path:
|
||||
p = Path(cat_path)
|
||||
if not p.exists():
|
||||
_fatal(f"[FATL] Cat photo at {p} does not exist")
|
||||
|
||||
mtype, _ = mimetypes.guess_type(str(p))
|
||||
if mtype is None or not mtype.startswith("image/"):
|
||||
_warn(f"[WARN] Cat file {p} does not look like an image (mimetype={mtype})")
|
||||
_fatal(f"Cat photo at {p} does not exist")
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def _apply_config(cfg: dict) -> None:
|
||||
global addr, port, allow_registration, server_name, signing_key, cat, support
|
||||
global addr, port, server_name, signing_key, cat, support, users_can_register
|
||||
|
||||
if "address" in cfg:
|
||||
addr = str(cfg["address"])
|
||||
@@ -69,33 +65,30 @@ def _apply_config(cfg: dict) -> None:
|
||||
try:
|
||||
port = int(cfg["port"])
|
||||
except (TypeError, ValueError):
|
||||
_warn(
|
||||
f"[WARN] Invalid port in config: {cfg.get('port')}; using default {port}"
|
||||
)
|
||||
|
||||
if "allow_registration" in cfg:
|
||||
allow_registration = bool(cfg["allow_registration"])
|
||||
_warn(f"Invalid port in config: {cfg.get('port')}; using default {port}")
|
||||
|
||||
if "server_name" in cfg:
|
||||
server_name = str(cfg["server_name"])
|
||||
else:
|
||||
_fatal("[FATL] `server_name` is not in configuration")
|
||||
_fatal("`server_name` is not in configuration")
|
||||
|
||||
if "signing_key" in cfg and "signing_key_path" in cfg:
|
||||
_warn(
|
||||
"[WARN] Both `signing_key` and `signing_key_path` present. Using `signing_key`."
|
||||
)
|
||||
_warn("Both `signing_key` and `signing_key_path` present. Using `signing_key`.")
|
||||
|
||||
if "signing_key" in cfg:
|
||||
signing_key = str(cfg["signing_key"]).strip()
|
||||
elif "signing_key_path" in cfg:
|
||||
sk = _read_signing_key_from_path(cfg["signing_key_path"])
|
||||
|
||||
if sk:
|
||||
signing_key = sk
|
||||
else:
|
||||
_fatal(f"{cfg["signing_key_path"]} does not have a signing key.")
|
||||
|
||||
else:
|
||||
_fatal(
|
||||
"[FATL] `signing_key` is not in configuration. "
|
||||
"A signing key can be generated using `cmd/generate_key.py`."
|
||||
"A signing key is not present in the configuration. "
|
||||
"A key can be generated using `python3 -m vona.utils.makekey`."
|
||||
)
|
||||
|
||||
if "cat" in cfg:
|
||||
@@ -115,7 +108,11 @@ def _apply_config(cfg: dict) -> None:
|
||||
if len(contact) > 1:
|
||||
support_obj["contacts"].append(contact)
|
||||
else:
|
||||
_warn("[WARN] No support contacts are defined")
|
||||
_warn("No support contacts are defined")
|
||||
|
||||
if "enable_registration" in cfg:
|
||||
users_can_register = cfg["enable_registration"]
|
||||
|
||||
support = support_obj
|
||||
|
||||
print("[INFO] Configuration file was valid")
|
||||
|
||||
@@ -1,464 +1,22 @@
|
||||
from flask import Blueprint, jsonify, request, Response
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import base64
|
||||
import re
|
||||
import os
|
||||
from flask import Blueprint
|
||||
|
||||
custom = Blueprint("custom", __name__)
|
||||
|
||||
# This implements custom endpoints
|
||||
# used by other homeserver
|
||||
# implementations. They do not start
|
||||
# with /_matrix/
|
||||
|
||||
# This should be split into more
|
||||
# files eventually.
|
||||
|
||||
|
||||
@custom.route("/_synapse/admin/v1/suspend/<user_id>", methods=["PUT"])
|
||||
@custom.route("/_synapse/admin/v1/deactivate/<user_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/reset_password/<user_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/admin", methods=["PUT"])
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>/delete_devices", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/shadow_ban", methods=["DELETE", "POST"])
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/override_ratelimit", methods=["GET", "POST", "DELETE"])
|
||||
@custom.route("/_synapse/admin/v1/media/protect/<media_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/unprotect/<media_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/quarantine/<s>/<media_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/unquarantine/<s>/<media_id>", methods=["POST"])
|
||||
@custom.route("/_dendrite/admin/purgeRoom/<roomId>", methods=["POST"])
|
||||
@custom.route("/_dendrite/admin/refreshDevices/<userId>", methods=["POST"])
|
||||
@custom.route("/_dendrite/admin/fulltext/reindex")
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations/<destination>/reset_connection", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room>")
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/timestamp_to_event")
|
||||
@custom.route("/_synapse/admin/v2/rooms/delete_status/<delete_id>")
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/make_room_admin", methods=["POST"])
|
||||
async def empty_response(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
# Synapse
|
||||
@custom.route("/_synapse/admin/v1/server_version")
|
||||
async def synapse_version():
|
||||
return jsonify({"server_version": globals.vona_version})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users")
|
||||
async def synapse_user_list():
|
||||
return jsonify({
|
||||
"users": [
|
||||
{
|
||||
"name": f"@vona:{config.server_name}",
|
||||
"is_guest": 0,
|
||||
"admin": 0,
|
||||
"user_type": "vona",
|
||||
"deactivated": 0,
|
||||
"erased": False,
|
||||
"shadow_banned": 0,
|
||||
"displayname": "Vona",
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"creation_ts": config.the_funny_number,
|
||||
"locked": False
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>", methods=["GET", "PUT"])
|
||||
async def synapse_user_info(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"name": f"@vona:{config.server_name}",
|
||||
"displayname": "Vona",
|
||||
"threepids": [],
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"is_guest": 0,
|
||||
"admin": 0,
|
||||
"deactivated": 0,
|
||||
"erased": False,
|
||||
"shadow_banned": 0,
|
||||
"creation_ts": config.the_funny_number,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"appservice_id": config.the_funny_number,
|
||||
"consent_server_notice_sent":config.the_funny_number,
|
||||
"consent_version": config.the_funny_number,
|
||||
"consent_ts": config.the_funny_number,
|
||||
"external_ids": [],
|
||||
"user_type": "vona",
|
||||
"locked": False,
|
||||
"suspended": False
|
||||
})
|
||||
|
||||
return jsonify({}), 201
|
||||
|
||||
@custom.route("/_synapse/admin/v1/whois/<user_id>")
|
||||
async def synapse_whois(user_id):
|
||||
return jsonify({
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"devices": {
|
||||
"": {
|
||||
"sessions": [{
|
||||
"connections": [{
|
||||
"ip":f"127.0.0.1",
|
||||
"last_seen":config.the_funny_number,
|
||||
"user_agent":f"Vona/{globals.vona_version}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/joined_rooms")
|
||||
async def synapse_user_joined_rooms(user_id):
|
||||
return jsonify({
|
||||
"joined_rooms": [globals.make_event_id().replace("$", "!")],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/sent_invite_count")
|
||||
async def synapse_invite_count(user_id):
|
||||
return jsonify({"invite_count": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/accountdata")
|
||||
async def synapse_account_data(user_id):
|
||||
return jsonify({"account_data":{"global":{}}})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/media", methods=["GET", "DELETE"])
|
||||
async def synapse_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({"deleted_media": ["cat"], "total": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/login", methods=["POST"])
|
||||
async def synapse_account_login(user_id):
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/_allow_cross_signing_replacement_without_uia", methods=["POST"])
|
||||
async def synapse_stupid_mas_bullshit(user_id):
|
||||
return jsonify({"updatable_without_uia_before_ms": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>/devices", methods=["GET", "POST"])
|
||||
async def synapse_device_list(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"devices": [{
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.vona_version}"
|
||||
}],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>/devices/<device_id>", methods=["GET", "PUT", "DELETE"])
|
||||
async def synapse_device_info(user_id, device_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.vona_version}"
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/pushers")
|
||||
async def synapse_pushers(user_id):
|
||||
return jsonify({"pushers": [], "total": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/username_available")
|
||||
async def synapse_username_available():
|
||||
return jsonify({"available": True})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/threepid/<medium>/users/<addr>")
|
||||
@custom.route("/_synapse/admin/v1/auth_providers/<provider>/users/<ext>")
|
||||
async def synapse_threepid(p, a):
|
||||
return jsonify({"user_id": f"@vona:{config.server_name}"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/<user_id>/redact")
|
||||
def synapse_redact(user_id):
|
||||
return jsonify({"redact_id": os.urandom(16).hex()})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/user/redact_status/<redact_id>")
|
||||
async def synapse_redact_status(redact_id):
|
||||
return jsonify({"status":"active","failed_redactions":[]})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/experimental_features/<user_id>", methods=["GET", "PUT"])
|
||||
async def synapse_experimental_features(user_id):
|
||||
return jsonify({"features": {}})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/register", methods=["GET", "POST"])
|
||||
async def synapse_register():
|
||||
if request.method == "GET":
|
||||
return jsonify({"nonce": os.urandom(16).hex()})
|
||||
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/join/<roomId>", methods=["POST"])
|
||||
async def synapse_membership_manipulation(roomId):
|
||||
return jsonify({"room_id": globals.make_event_id().replace("$", "!")})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/account_validity/validity", methods=["POST"])
|
||||
async def synapse_account_validity():
|
||||
return jsonify({"expiration_ts": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/send_server_notice", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/send_server_notice/<txnId>", methods=["PUT"])
|
||||
async def synapse_server_notice(**kwargs):
|
||||
return jsonify({"event_id": globals.make_event_id()})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/purge_history/<room_id>/<event_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/purge_history/<room_id>", methods=["POST"])
|
||||
async def synapse_purge_event(**kwargs):
|
||||
return jsonify({"purge_id": os.urandom(16).hex()})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/purge_history_status/<purge_id>")
|
||||
async def synapse_purge_status(purge_id):
|
||||
return jsonify({"status":"active"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/room/<room_id>/media")
|
||||
async def synapse_room_media(room_id):
|
||||
return jsonify({"local": [f"mxc://{config.server_name}/cat"], "remote": []})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/room/<room_id>/media/quarantine", methods=["POST"])
|
||||
async def synapse_quarantine_room_media(room_id):
|
||||
return jsonify({"num_quarantined": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/media/<s>/delete", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/<s>/<media_id>", methods=["DELETE"])
|
||||
@custom.route("/_synapse/admin/v1/media/delete", methods=["POST"])
|
||||
async def synapse_delete_media_from_server(**kwargs):
|
||||
return jsonify({"deleted_media": ["cat"], "total": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/purge_media_cache", methods=["POST"])
|
||||
async def synapse_delete_remote_media():
|
||||
return jsonify({"deleted": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/statistics/users/media")
|
||||
async def synapse_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})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/statistics/database/rooms")
|
||||
async def synapse_room_stats():
|
||||
return jsonify({
|
||||
"rooms": [{
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"estimated_size": config.the_funny_number * 420
|
||||
}]
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/background_updates/enabled", methods=["POST", "GET"])
|
||||
@custom.route("/_synapse/admin/v1/background_updates/status")
|
||||
async def synapse_change_bg_update():
|
||||
return jsonify({"enabled":False})
|
||||
|
||||
# No documentation on what Synapse actually returns for this API, so a blank dict for now
|
||||
@custom.route("/_synapse/admin/v1/background_updates/start_job", methods=["POST"])
|
||||
async def synapse_bg_update_start_job():
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@custom.route("/_synapse/admin/v1/event_reports")
|
||||
async def synapse_event_reports():
|
||||
return jsonify({
|
||||
"event_reports": [{
|
||||
"event_id": globals.make_event_id(),
|
||||
"id": config.the_funny_number,
|
||||
"reason": "",
|
||||
"score": config.the_funny_number,
|
||||
"received_ts": config.the_funny_number,
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"name": "Vona",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
}],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
|
||||
@custom.route("/_synapse/admin/v1/event_reports/<report_id>", methods=["GET", "DELETE"])
|
||||
async def synapse_interact_with_reported_event(report_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"event_id": globals.make_event_id(),
|
||||
"event_json": globals.hash_and_sign_event({
|
||||
"auth_events": [],
|
||||
"content": {
|
||||
"msgtype": "m.text",
|
||||
"body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "Number 15: Burger King Foot Lettuce.<br />The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement "This is the lettuce you eat at Burger King.". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said "Oh, I know who that is, hes getting fired". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."
|
||||
},
|
||||
"depth": config.the_funny_number,
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"prev_events": [globals.make_event_id()],
|
||||
"prev_state": [],
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"type": "m.room.message"
|
||||
})
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations")
|
||||
async def synapse_federation_destinations():
|
||||
return jsonify({
|
||||
"destinations": [{}],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations/<destination>")
|
||||
async def synapse_destination(destination):
|
||||
return jsonify({
|
||||
"destination": destination,
|
||||
"retry_last_ts": config.the_funny_number,
|
||||
"retry_interval": config.the_funny_number,
|
||||
"failure_ts": config.the_funny_number,
|
||||
"last_successful_stream_ordering": None
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations/<destination>/rooms")
|
||||
async def synapse_destination_rooms(destination):
|
||||
return jsonify({
|
||||
"rooms": [],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/registration_tokens")
|
||||
async def synapse_reg_tokens():
|
||||
return jsonify({
|
||||
"registration_tokens": [{
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
}]
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/registration_tokens/<token>", methods=["GET", "PUT", "DELETE"])
|
||||
async def synapse_reg_token(token):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/registration_tokens/new", methods=["POST"])
|
||||
async def synapse_new_reg_token():
|
||||
return jsonify({
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms")
|
||||
async def synapse_rooms():
|
||||
return jsonify({
|
||||
"rooms": [],
|
||||
"offset": 0,
|
||||
"total_rooms": 0
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/members")
|
||||
async def synapse_room_members(room):
|
||||
return jsonify({
|
||||
"members": [
|
||||
f"@vona:{config.server_name}"
|
||||
],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
async def synapse_room_state(room):
|
||||
return jsonify({"state": []})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
async def synapse_room_messages(room):
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"end": "vona",
|
||||
"start": "vona"
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/block", methods=["GET", "PUT"])
|
||||
async def synapse_block_room(room):
|
||||
return jsonify({"block": False})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>", methods=["DELETE"])
|
||||
async def synapse_room_delete(room):
|
||||
return jsonify({
|
||||
"kicked_users": [
|
||||
f"@vona:{config.server_name}"
|
||||
],
|
||||
"failed_to_kick_users": [],
|
||||
"local_aliases": [],
|
||||
"new_room_id": f"!vona:{config.server_name}"
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/rooms/<room_id>", methods=["DELETE"])
|
||||
async def synapse_room_delete_v2(room):
|
||||
return jsonify({"delete_id": "vona"})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/rooms/<room_id>/delete_status")
|
||||
async def synapse_room_delete_status(room):
|
||||
return jsonify({"results": []})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/forward_extremities", methods=["GET"])
|
||||
async def synapse_forward_extremities(room):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({"deleted": 0})
|
||||
|
||||
return jsonify({
|
||||
"count": 1,
|
||||
"results": [{
|
||||
"event_id": globals.make_event_id(),
|
||||
"state_group": config.the_funny_number,
|
||||
"depth": config.the_funny_number,
|
||||
"received_ts": config.the_funny_number
|
||||
}]
|
||||
})
|
||||
|
||||
# Dendrite - https://element-hq.github.io/dendrite/administration/adminapi
|
||||
@custom.route("/_dendrite/admin/evacuateUser/<userId>", methods=["POST"])
|
||||
async def dendrite_evacuate_user(userId):
|
||||
return jsonify({"affected": [globals.make_event_id().replace("$", "!")]})
|
||||
|
||||
@custom.route("/_dendrite/admin/evacuateRoom/<roomId>", methods=["POST"])
|
||||
async def dendrite_evacuate_room(roomId):
|
||||
return jsonify({"affected": [f"@vona:{config.server_name}"]})
|
||||
|
||||
@custom.route("/_dendrite/admin/resetPassword/<userId>", methods=["POST"])
|
||||
async def dendrite_reset_pswd(userId):
|
||||
return jsonify({"password_updated": True})
|
||||
|
||||
# Conduwuit/Tuwunel/Continuwuity
|
||||
@custom.route("/_continuwuity/local_user_count")
|
||||
@custom.route("/_conduwuit/local_user_count")
|
||||
@custom.route("/_tuwunel/local_user_count")
|
||||
async def conduwuit_user_count():
|
||||
return jsonify({"count": 1})
|
||||
|
||||
@custom.route("/_continuwuity/server_version")
|
||||
@custom.route("/_conduwuit/server_version")
|
||||
@custom.route("/_tuwunel/server_version")
|
||||
async def conduwuit_server_version():
|
||||
return jsonify({
|
||||
"name": "Vona",
|
||||
"version":globals.vona_version
|
||||
})
|
||||
# This implements non-standard
|
||||
# endpoints created by other
|
||||
# homeserver implementations.
|
||||
|
||||
|
||||
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.register_blueprint(hammerhead)
|
||||
custom.register_blueprint(conduwuit)
|
||||
custom.register_blueprint(dendrite)
|
||||
custom.register_blueprint(synapse)
|
||||
custom.register_blueprint(citadel)
|
||||
custom.register_blueprint(telo)
|
||||
|
||||
61
vona/custom/citadel.py
Normal file
61
vona/custom/citadel.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
import vona.config as config
|
||||
import base64
|
||||
import os
|
||||
|
||||
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):
|
||||
store_response = request.json.get("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)
|
||||
25
vona/custom/conduwuit.py
Normal file
25
vona/custom/conduwuit.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from flask import Blueprint, jsonify
|
||||
import vona.globals as globals
|
||||
|
||||
conduwuit = Blueprint("conduwuit", __name__)
|
||||
|
||||
# Endpoints used by Conduwuit and forks.
|
||||
|
||||
|
||||
@conduwuit.route("/_continuwuity/local_user_count")
|
||||
@conduwuit.route("/_conduwuit/local_user_count")
|
||||
@conduwuit.route("/_tuwunel/local_user_count")
|
||||
async def user_count():
|
||||
return jsonify({
|
||||
"count": 1
|
||||
})
|
||||
|
||||
|
||||
@conduwuit.route("/_continuwuity/server_version")
|
||||
@conduwuit.route("/_conduwuit/server_version")
|
||||
@conduwuit.route("/_tuwunel/server_version")
|
||||
async def server_version():
|
||||
return jsonify({
|
||||
"name": "Vona",
|
||||
"version": globals.version
|
||||
})
|
||||
37
vona/custom/dendrite.py
Normal file
37
vona/custom/dendrite.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from flask import Blueprint, jsonify
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
dendrite = Blueprint("dendrite", __name__)
|
||||
|
||||
# https://element-hq.github.io/dendrite/administration/adminapi
|
||||
|
||||
|
||||
@dendrite.route("/_dendrite/admin/purgeRoom/<room>", methods=["POST"])
|
||||
@dendrite.route("/_dendrite/admin/refreshDevices/<user>", methods=["POST"])
|
||||
@dendrite.route("/_dendrite/admin/fulltext/reindex")
|
||||
async def empty(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@dendrite.route("/_dendrite/admin/evacuateUser/<user>", methods=["POST"])
|
||||
async def evacuate_user(user):
|
||||
return jsonify({
|
||||
"affected": [
|
||||
globals.make_event_id().replace("$", "!")
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@dendrite.route("/_dendrite/admin/evacuateRoom/<room>", methods=["POST"])
|
||||
async def evacuate_room(room):
|
||||
return jsonify({
|
||||
"affected": [
|
||||
f"@vona:{config.server_name}"
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@dendrite.route("/_dendrite/admin/resetPassword/<user>", methods=["POST"])
|
||||
async def reset_password(user):
|
||||
return jsonify({"password_updated": True})
|
||||
77
vona/custom/hammerhead.py
Normal file
77
vona/custom/hammerhead.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from vona.config import the_funny_number
|
||||
from datetime import datetime, timezone
|
||||
from vona.federation import send_join
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import time
|
||||
|
||||
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/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):
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
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
|
||||
|
||||
event_id = event["event_id"]
|
||||
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)
|
||||
})
|
||||
449
vona/custom/synapse.py
Normal file
449
vona/custom/synapse.py
Normal file
@@ -0,0 +1,449 @@
|
||||
from flask import Blueprint, jsonify, request, Response
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import base64
|
||||
import re
|
||||
import os
|
||||
|
||||
synapse = Blueprint("synapse", __name__)
|
||||
|
||||
# The absolute giant known as the Synapse admin API.
|
||||
# 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/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>/timestamp_to_event")
|
||||
@synapse.route("/_synapse/admin/v2/rooms/delete_status/<delete_id>")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/make_room_admin", methods=["POST"])
|
||||
async def response(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/server_version")
|
||||
async def version():
|
||||
return jsonify({"server_version": globals.version})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/users")
|
||||
async def user_list():
|
||||
return jsonify({
|
||||
"users": [
|
||||
{
|
||||
"name": f"@vona:{config.server_name}",
|
||||
"is_guest": 0,
|
||||
"admin": 0,
|
||||
"user_type": "vona",
|
||||
"deactivated": 0,
|
||||
"erased": False,
|
||||
"shadow_banned": 0,
|
||||
"displayname": "Vona",
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"creation_ts": config.the_funny_number,
|
||||
"locked": False
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/users/<user_id>", methods=["GET", "PUT"])
|
||||
async def user_info(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"name": f"@vona:{config.server_name}",
|
||||
"displayname": "Vona",
|
||||
"threepids": [],
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"is_guest": 0,
|
||||
"admin": 0,
|
||||
"deactivated": 0,
|
||||
"erased": False,
|
||||
"shadow_banned": 0,
|
||||
"creation_ts": config.the_funny_number,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"appservice_id": config.the_funny_number,
|
||||
"consent_server_notice_sent":config.the_funny_number,
|
||||
"consent_version": config.the_funny_number,
|
||||
"consent_ts": config.the_funny_number,
|
||||
"external_ids": [],
|
||||
"user_type": "vona",
|
||||
"locked": False,
|
||||
"suspended": False
|
||||
})
|
||||
|
||||
return jsonify({}), 201
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/whois/<user_id>")
|
||||
async def whois(user_id):
|
||||
return jsonify({
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"devices": {
|
||||
"": {
|
||||
"sessions": [{
|
||||
"connections": [{
|
||||
"ip":f"127.0.0.1",
|
||||
"last_seen":config.the_funny_number,
|
||||
"user_agent":f"Vona/{globals.version}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/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")
|
||||
async def invite_count(user_id):
|
||||
return jsonify({"invite_count": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/accountdata")
|
||||
async def account_data(user_id):
|
||||
return jsonify({"account_data":{"global":{}}})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/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": None,
|
||||
"safe_from_quarantine": False,
|
||||
"upload_name": "cat.jpg"
|
||||
}],
|
||||
"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"])
|
||||
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"])
|
||||
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"])
|
||||
async def device_list(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"devices": [{
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": config.addr,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.version}"
|
||||
}],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/users/<user_id>/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": 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")
|
||||
async def pushers(user_id):
|
||||
return jsonify({"pushers": [], "total": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/username_available")
|
||||
async def username_available():
|
||||
return jsonify({"available": True})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/threepid/<medium>/users/<addr>")
|
||||
@synapse.route("/_synapse/admin/v1/auth_providers/<provider>/users/<ext>")
|
||||
async def threepid(**kwargs):
|
||||
return jsonify({"user_id": f"@vona:{config.server_name}"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/<user_id>/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"])
|
||||
async def experimental_features(user_id):
|
||||
return jsonify({"features": {}})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/register", methods=["GET", "POST"])
|
||||
async def register():
|
||||
if request.method == "GET":
|
||||
return jsonify({"nonce": os.urandom(16).hex()})
|
||||
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@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"])
|
||||
async def account_validity():
|
||||
return jsonify({"expiration_ts": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/send_server_notice", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/send_server_notice/<txnId>", methods=["PUT"])
|
||||
async def server_notice(**kwargs):
|
||||
return jsonify({"event_id": globals.make_event_id()})
|
||||
|
||||
@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()})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/purge_history_status/<purge_id>")
|
||||
async def purge_status(purge_id):
|
||||
return jsonify({"status":"active"})
|
||||
|
||||
@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>/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"])
|
||||
@synapse.route("/_synapse/admin/v1/media/<s>/<media_id>", methods=["DELETE"])
|
||||
@synapse.route("/_synapse/admin/v1/media/delete", methods=["POST"])
|
||||
async def delete_media_from_server(**kwargs):
|
||||
return jsonify({"deleted_media": ["cat"], "total": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/purge_media_cache", methods=["POST"])
|
||||
async def delete_remote_media():
|
||||
return jsonify({"deleted": config.the_funny_number})
|
||||
|
||||
@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
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/statistics/database/rooms")
|
||||
async def room_stats():
|
||||
return jsonify({
|
||||
"rooms": [{
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"estimated_size": config.the_funny_number * 420
|
||||
}]
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/background_updates/enabled", methods=["POST", "GET"])
|
||||
@synapse.route("/_synapse/admin/v1/background_updates/status")
|
||||
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({
|
||||
"event_reports": [{
|
||||
"event_id": globals.make_event_id(),
|
||||
"id": config.the_funny_number,
|
||||
"reason": "",
|
||||
"score": config.the_funny_number,
|
||||
"received_ts": config.the_funny_number,
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"name": "Vona",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
}],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/event_reports/<report_id>", methods=["GET", "DELETE"])
|
||||
async def interact_with_reported_event(report_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"event_id": globals.make_event_id(),
|
||||
"event_json": globals.hash_and_sign_event({
|
||||
"auth_events": [],
|
||||
"content": {
|
||||
"msgtype": "m.text",
|
||||
"body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "Number 15: Burger King Foot Lettuce.<br />The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement "This is the lettuce you eat at Burger King.". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said "Oh, I know who that is, hes getting fired". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."
|
||||
},
|
||||
"depth": config.the_funny_number,
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"prev_events": [globals.make_event_id()],
|
||||
"prev_state": [],
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"type": "m.room.message"
|
||||
})
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/federation/destinations")
|
||||
async def federation_destinations():
|
||||
return jsonify({
|
||||
"destinations": [],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/federation/destinations/<destination>")
|
||||
async def destination(destination):
|
||||
return jsonify({
|
||||
"destination": destination,
|
||||
"retry_last_ts": config.the_funny_number,
|
||||
"retry_interval": config.the_funny_number,
|
||||
"failure_ts": config.the_funny_number,
|
||||
"last_successful_stream_ordering": None
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/federation/destinations/<destination>/rooms")
|
||||
async def destination_rooms(destination):
|
||||
return jsonify({
|
||||
"rooms": [],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/registration_tokens")
|
||||
async def reg_tokens():
|
||||
return jsonify({
|
||||
"registration_tokens": [{
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
}]
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/registration_tokens/<token>", methods=["GET", "PUT", "DELETE"])
|
||||
async def reg_token(token):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/registration_tokens/new", methods=["POST"])
|
||||
async def new_reg_token():
|
||||
return jsonify({
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms")
|
||||
async def rooms():
|
||||
return jsonify({
|
||||
"rooms": [],
|
||||
"offset": 0,
|
||||
"total_rooms": 0
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/members")
|
||||
async def room_members(room):
|
||||
return jsonify({
|
||||
"members": [
|
||||
f"@vona:{config.server_name}"
|
||||
],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/state")
|
||||
async def room_state(room):
|
||||
return jsonify({"state": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/state")
|
||||
async def room_messages(room):
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"end": "vona",
|
||||
"start": "vona"
|
||||
})
|
||||
|
||||
@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>", methods=["DELETE"])
|
||||
async def room_delete(room):
|
||||
return jsonify({
|
||||
"kicked_users": [
|
||||
f"@vona:{config.server_name}"
|
||||
],
|
||||
"failed_to_kick_users": [],
|
||||
"local_aliases": [],
|
||||
"new_room_id": globals.make_event_id(seed=room)
|
||||
})
|
||||
|
||||
@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>/delete_status")
|
||||
async def room_delete_status(room):
|
||||
return jsonify({"results": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/forward_extremities", methods=["GET"])
|
||||
async def forward_extremities(room):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({"deleted": 0})
|
||||
|
||||
return jsonify({
|
||||
"count": 1,
|
||||
"results": [{
|
||||
"event_id": globals.make_event_id(),
|
||||
"state_group": config.the_funny_number,
|
||||
"depth": config.the_funny_number,
|
||||
"received_ts": config.the_funny_number
|
||||
}]
|
||||
})
|
||||
85
vona/custom/telodendria.py
Normal file
85
vona/custom/telodendria.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from vona.globals import version
|
||||
import vona.config as config
|
||||
|
||||
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,7 +1,6 @@
|
||||
from flask import jsonify, Response, request, send_file, abort, Blueprint
|
||||
from vona.config import *
|
||||
import vona.globals as globals
|
||||
import httpx
|
||||
import vona.config as config
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
@@ -25,13 +24,13 @@ def send_join(request, roomId) -> dict:
|
||||
create_event = {
|
||||
"content": {
|
||||
"m.federate": True,
|
||||
"creator": f"@vona:{server_name}",
|
||||
"room_version": "2"
|
||||
"creator": f"@vona:{config.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}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"depth": 1,
|
||||
"type": "m.room.create",
|
||||
@@ -45,12 +44,12 @@ def send_join(request, roomId) -> dict:
|
||||
our_join = {
|
||||
"content": {
|
||||
"displayname": "Vona",
|
||||
"avatar_url": f"mxc://{server_name}/cat",
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin_server_ts": 2,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": f"@vona:{config.server_name}",
|
||||
"type": "m.room.member",
|
||||
"event_id": event_ids[1],
|
||||
"room_id": roomId,
|
||||
@@ -71,17 +70,17 @@ def send_join(request, roomId) -> dict:
|
||||
pls = {
|
||||
"content": {
|
||||
"users": {
|
||||
f"@vona:{server_name}": "100"
|
||||
f"@vona:{config.server_name}": "100"
|
||||
}
|
||||
},
|
||||
"origin_server_ts": 3,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
"event_id": event_ids[2],
|
||||
"depth": 3,
|
||||
"user_id": f"@vona:{server_name}",
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
@@ -106,7 +105,7 @@ def send_join(request, roomId) -> dict:
|
||||
"join_rule": "public"
|
||||
},
|
||||
"origin_server_ts": 4,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules",
|
||||
"event_id": event_ids[3],
|
||||
@@ -141,7 +140,7 @@ def send_join(request, roomId) -> dict:
|
||||
},
|
||||
"origin_server_ts": 5,
|
||||
"depth": 5,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.guest_access",
|
||||
"event_id": event_ids[4],
|
||||
@@ -158,10 +157,6 @@ def send_join(request, roomId) -> dict:
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
@@ -178,7 +173,7 @@ def send_join(request, roomId) -> dict:
|
||||
"history_visibility": "shared"
|
||||
},
|
||||
"type": "m.room.history_visibility",
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"origin_server_ts": 6,
|
||||
"depth": 6,
|
||||
@@ -196,10 +191,6 @@ def send_join(request, roomId) -> dict:
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
@@ -217,7 +208,7 @@ def send_join(request, roomId) -> dict:
|
||||
"auth_chain": event_chain,
|
||||
"event": remote_join,
|
||||
"members_omitted": False,
|
||||
"servers_in_room": [server_name],
|
||||
"servers_in_room": [config.server_name],
|
||||
"state": event_chain
|
||||
}
|
||||
|
||||
@@ -228,7 +219,7 @@ def send_join(request, roomId) -> dict:
|
||||
async def version():
|
||||
return jsonify({
|
||||
"server": {
|
||||
"version": globals.vona_version,
|
||||
"version": globals.version,
|
||||
"name": "Vona"
|
||||
}
|
||||
})
|
||||
@@ -237,10 +228,10 @@ async def version():
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -250,7 +241,7 @@ async def keys():
|
||||
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>")
|
||||
@@ -259,7 +250,7 @@ async def download_media(media_id):
|
||||
# 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 = (
|
||||
@@ -300,7 +291,7 @@ async def make_join(roomId, userId):
|
||||
}), 403
|
||||
|
||||
try:
|
||||
if roomId.split(":")[1] != server_name:
|
||||
if roomId.split(":")[1] != config.server_name:
|
||||
return not_invited()
|
||||
except:
|
||||
return not_invited()
|
||||
@@ -316,10 +307,10 @@ async def make_join(roomId, userId):
|
||||
|
||||
join = {
|
||||
"content": {
|
||||
"join_authorised_via_users_server": f"@vona:{server_name}",
|
||||
"join_authorised_via_users_server": f"@vona:{config.server_name}",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin": server_name,
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": 7,
|
||||
"room_id": roomId,
|
||||
"sender": userId,
|
||||
@@ -350,15 +341,13 @@ async def make_join(roomId, userId):
|
||||
|
||||
return jsonify({
|
||||
"event": globals.hash_and_sign_event(join),
|
||||
"room_version": "2"
|
||||
"room_version": globals.room_version_from_id(roomId)
|
||||
})
|
||||
|
||||
|
||||
@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
|
||||
@@ -395,7 +384,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,31 +393,24 @@ 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
|
||||
|
||||
return jsonify({"device_keys": users})
|
||||
return jsonify({
|
||||
"device_keys": request.json.get("device_keys", {})
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v2/invite/<room>/<txnId>", methods=["PUT"])
|
||||
@@ -438,21 +420,16 @@ async def invite_user_v2(room, txnId):
|
||||
|
||||
@server.route("/_matrix/federation/v1/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v1(room, txnId):
|
||||
return [200, invite_user(request.data)]
|
||||
return [200, invite_user(request.json)]
|
||||
|
||||
|
||||
def invite_user(data):
|
||||
try:
|
||||
invite_data = json.loads(data)
|
||||
except:
|
||||
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}),
|
||||
|
||||
def invite_user(invite_data):
|
||||
if "event" in invite_data:
|
||||
if "room_version" in invite_data:
|
||||
if invite_data["room_version"] != "2":
|
||||
if invite_data["room_version"] not in ["1", "2"]:
|
||||
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
|
||||
|
||||
@@ -466,9 +443,12 @@ def invite_user(data):
|
||||
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(invite_data["event"]),
|
||||
"room_version": invite_data["room_version"]
|
||||
})
|
||||
|
||||
|
||||
return jsonify({
|
||||
@@ -482,4 +462,26 @@ async def space_hierachy(roomId):
|
||||
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():
|
||||
tags = request.json.get("room_participation_tags", [])
|
||||
return jsonify({"recognised_tags": tags})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/backfill/<room>")
|
||||
async def backfill(room):
|
||||
# TODO: burger king foot lettuce
|
||||
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
return jsonify({
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": int(str(time.time() * 1000).split(".")[0]),
|
||||
"pdus": send_join(bullshit, room)["state"]
|
||||
})
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from importlib.metadata import version
|
||||
from resolvematrix import ServerResolver
|
||||
from types import SimpleNamespace
|
||||
from collections import Counter
|
||||
import vona.config as config
|
||||
import nacl.signing
|
||||
import hashlib
|
||||
import base64
|
||||
import random
|
||||
import httpx
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
|
||||
vona_version = version("vona")
|
||||
version = "1.4.4"
|
||||
|
||||
|
||||
def canonical_json(value):
|
||||
@@ -109,7 +112,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 +132,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,
|
||||
@@ -224,3 +234,115 @@ def hash_and_sign_event(event_object):
|
||||
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("!", "")
|
||||
|
||||
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
|
||||
hexadecimal_room_id = "2" + hexadecimal_room_id[1:]
|
||||
|
||||
def remove_chars(s):
|
||||
return re.sub("[^12]", "", 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
|
||||
|
||||
return 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,
|
||||
)
|
||||
|
||||
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}
|
||||
)
|
||||
|
||||
11
vona/utils/__main__.py
Normal file
11
vona/utils/__main__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
print("Available utils:")
|
||||
|
||||
a = [
|
||||
"makekey",
|
||||
"joinroom"
|
||||
]
|
||||
|
||||
for t in a:
|
||||
print(f"\t{t}")
|
||||
|
||||
print("")
|
||||
83
vona/utils/joinroom.py
Normal file
83
vona/utils/joinroom.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from resolvematrix import ServerResolver
|
||||
import urllib.parse, time, json, httpx
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
http_client = globals.http_client()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
resolver = ServerResolver(client=http_client)
|
||||
|
||||
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}?ver=1&ver=2&ver=3&ver=4&ver=5&ver=6&ver=7&ver=8&ver=9&ver=10&ver=11&ver=12",
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
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}")
|
||||
@@ -3,7 +3,23 @@
|
||||
import base64
|
||||
import os
|
||||
|
||||
key = base64.b64encode(os.urandom(32)).decode("utf-8")[:43].replace("/", "_")
|
||||
key_id = base64.b64encode(os.urandom(32)).decode("utf-8")[:6].replace("/", "_")
|
||||
|
||||
def mkchar() -> str:
|
||||
return os.urandom(4).hex()[:1]
|
||||
|
||||
|
||||
key = (
|
||||
base64.b64encode(os.urandom(32))
|
||||
.decode("utf-8")[:43]
|
||||
.replace("/", mkchar())
|
||||
.replace("+", mkchar())
|
||||
)
|
||||
|
||||
key_id = (
|
||||
base64.b64encode(os.urandom(32))
|
||||
.decode("utf-8")[:6]
|
||||
.replace("/", mkchar())
|
||||
.replace("+", mkchar())
|
||||
)
|
||||
|
||||
print(f"ed25519 {key_id} {key}")
|
||||
|
||||
Reference in New Issue
Block a user