"""Well-known federation endpoints: WebFinger, NodeInfo, host-meta. Ported from ~/art-dag/activity-pub/app/routers/federation.py. """ from __future__ import annotations import os from quart import Blueprint, request, abort, Response, g from shared.services.registry import services def _domain() -> str: return os.getenv("AP_DOMAIN", "rose-ash.com") def register(url_prefix=""): bp = Blueprint("wellknown", __name__, url_prefix=url_prefix) @bp.get("/.well-known/webfinger") async def webfinger(): resource = request.args.get("resource", "") if not resource.startswith("acct:"): abort(400, "Invalid resource format") parts = resource[5:].split("@") if len(parts) != 2: abort(400, "Invalid resource format") username, domain = parts if domain != _domain(): abort(404, "User not on this server") actor = await services.federation.get_actor_by_username(g.s, username) if not actor: abort(404, "User not found") domain = _domain() return Response( response=__import__("json").dumps({ "subject": resource, "aliases": [f"https://{domain}/users/{username}"], "links": [ { "rel": "self", "type": "application/activity+json", "href": f"https://{domain}/users/{username}", }, { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": f"https://{domain}/users/{username}", }, ], }), content_type="application/jrd+json", ) @bp.get("/.well-known/nodeinfo") async def nodeinfo_index(): domain = _domain() return Response( response=__import__("json").dumps({ "links": [ { "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0", "href": f"https://{domain}/nodeinfo/2.0", } ] }), content_type="application/json", ) @bp.get("/nodeinfo/2.0") async def nodeinfo(): stats = await services.federation.get_stats(g.s) return Response( response=__import__("json").dumps({ "version": "2.0", "software": { "name": "rose-ash", "version": "1.0.0", }, "protocols": ["activitypub"], "usage": { "users": { "total": stats.get("actors", 0), "activeMonth": stats.get("actors", 0), }, "localPosts": stats.get("activities", 0), }, "openRegistrations": False, "metadata": { "nodeName": "Rose Ash", "nodeDescription": "Cooperative platform with ActivityPub federation", }, }), content_type="application/json", ) @bp.get("/.well-known/host-meta") async def host_meta(): domain = _domain() xml = ( '\n' '\n' f' \n' '' ) return Response(response=xml, content_type="application/xrd+xml") return bp