Add fediverse social tables, protocols, and implementations
6 new ORM models (remote actors, following, remote posts, local posts, interactions, notifications), 20 new FederationService methods with SQL implementations and stubs, WebFinger client, and Alembic migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
68
utils/webfinger.py
Normal file
68
utils/webfinger.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""WebFinger client for resolving remote AP actor profiles."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
AP_CONTENT_TYPE = "application/activity+json"
|
||||
|
||||
|
||||
async def resolve_actor(acct: str) -> dict | None:
|
||||
"""Resolve user@domain to actor JSON via WebFinger + actor fetch.
|
||||
|
||||
Args:
|
||||
acct: Handle in the form ``user@domain`` (no leading ``@``).
|
||||
|
||||
Returns:
|
||||
Actor JSON-LD dict, or None if resolution fails.
|
||||
"""
|
||||
acct = acct.lstrip("@")
|
||||
if "@" not in acct:
|
||||
return None
|
||||
|
||||
_, domain = acct.rsplit("@", 1)
|
||||
webfinger_url = f"https://{domain}/.well-known/webfinger"
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10, follow_redirects=True) as client:
|
||||
# Step 1: WebFinger lookup
|
||||
resp = await client.get(
|
||||
webfinger_url,
|
||||
params={"resource": f"acct:{acct}"},
|
||||
headers={"Accept": "application/jrd+json, application/json"},
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
log.debug("WebFinger %s returned %d", webfinger_url, resp.status_code)
|
||||
return None
|
||||
|
||||
data = resp.json()
|
||||
# Find self link with AP content type
|
||||
actor_url = None
|
||||
for link in data.get("links", []):
|
||||
if link.get("rel") == "self" and link.get("type") in (
|
||||
AP_CONTENT_TYPE,
|
||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
||||
):
|
||||
actor_url = link.get("href")
|
||||
break
|
||||
|
||||
if not actor_url:
|
||||
log.debug("No AP self link in WebFinger response for %s", acct)
|
||||
return None
|
||||
|
||||
# Step 2: Fetch actor JSON
|
||||
resp = await client.get(
|
||||
actor_url,
|
||||
headers={"Accept": AP_CONTENT_TYPE},
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
|
||||
log.debug("Actor fetch %s returned %d", actor_url, resp.status_code)
|
||||
except Exception:
|
||||
log.exception("WebFinger resolution failed for %s", acct)
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user