From f1aea5a5f373fa350d9679e2173a9da592da68c0 Mon Sep 17 00:00:00 2001 From: gilesb Date: Wed, 7 Jan 2026 22:01:22 +0000 Subject: [PATCH] Add content negotiation for /users and /objects endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /users/{username}: Redirects to /ui/user/{username} for browsers (Accept: text/html) - /objects/{hash}: Redirects to /ui/asset/{name} for browsers - APIs still get JSON (application/activity+json) as before 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- server.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index 186949a..9d251e5 100644 --- a/server.py +++ b/server.py @@ -1383,10 +1383,19 @@ async def webfinger(resource: str): @app.get("/users/{username}") async def get_actor(username: str, request: Request): - """Get actor profile for any registered user.""" + """Get actor profile for any registered user. Content negotiation: HTML for browsers, JSON for APIs.""" if not user_exists(username): raise HTTPException(404, f"Unknown user: {username}") + # Check Accept header for content negotiation + accept = request.headers.get("accept", "") + wants_html = "text/html" in accept and "application/json" not in accept and "application/activity+json" not in accept + + if wants_html: + # Redirect to UI page for browsers + from fastapi.responses import RedirectResponse + return RedirectResponse(url=f"/ui/user/{username}", status_code=303) + actor = load_actor(username) # Add ActivityPub context @@ -1774,13 +1783,22 @@ async def get_activities(): @app.get("/objects/{content_hash}") -async def get_object(content_hash: str): - """Get object by content hash.""" +async def get_object(content_hash: str, request: Request): + """Get object by content hash. Content negotiation: HTML for browsers, JSON for APIs.""" registry = load_registry() # Find asset by hash for name, asset in registry.get("assets", {}).items(): if asset.get("content_hash") == content_hash: + # Check Accept header for content negotiation + accept = request.headers.get("accept", "") + wants_html = "text/html" in accept and "application/json" not in accept and "application/activity+json" not in accept + + if wants_html: + # Redirect to UI page for browsers + from fastapi.responses import RedirectResponse + return RedirectResponse(url=f"/ui/asset/{name}", status_code=303) + owner = asset.get("owner", "unknown") return JSONResponse( content={