forked from forks/microblog.pub
94 lines
2.8 KiB
Python
94 lines
2.8 KiB
Python
from typing import Any
|
|
from urllib.parse import urlparse
|
|
|
|
import httpx
|
|
from loguru import logger
|
|
|
|
from app import config
|
|
from app.utils.url import check_url
|
|
|
|
|
|
async def webfinger(
|
|
resource: str,
|
|
) -> dict[str, Any] | None: # noqa: C901
|
|
"""Mastodon-like WebFinger resolution to retrieve the activity stream Actor URL."""
|
|
logger.info(f"performing webfinger resolution for {resource}")
|
|
protos = ["https", "http"]
|
|
if resource.startswith("http://"):
|
|
protos.reverse()
|
|
host = urlparse(resource).netloc
|
|
elif resource.startswith("https://"):
|
|
host = urlparse(resource).netloc
|
|
else:
|
|
if resource.startswith("acct:"):
|
|
resource = resource[5:]
|
|
if resource.startswith("@"):
|
|
resource = resource[1:]
|
|
_, host = resource.split("@", 1)
|
|
resource = "acct:" + resource
|
|
|
|
is_404 = False
|
|
|
|
resp: httpx.Response | None = None
|
|
async with httpx.AsyncClient() as client:
|
|
for i, proto in enumerate(protos):
|
|
try:
|
|
url = f"{proto}://{host}/.well-known/webfinger"
|
|
check_url(url)
|
|
resp = await client.get(
|
|
url,
|
|
params={"resource": resource},
|
|
headers={
|
|
"User-Agent": config.USER_AGENT,
|
|
},
|
|
follow_redirects=True,
|
|
)
|
|
resp.raise_for_status()
|
|
break
|
|
except httpx.HTTPStatusError as http_error:
|
|
logger.exception("HTTP error")
|
|
if http_error.response.status_code in [403, 404, 410]:
|
|
is_404 = True
|
|
continue
|
|
raise
|
|
except httpx.HTTPError:
|
|
logger.exception("req failed")
|
|
# If we tried https first and the domain is "http only"
|
|
if i == 0:
|
|
continue
|
|
break
|
|
if is_404:
|
|
return None
|
|
|
|
if resp:
|
|
return resp.json()
|
|
else:
|
|
return None
|
|
|
|
|
|
async def get_remote_follow_template(resource: str) -> str | None:
|
|
data = await webfinger(resource)
|
|
if data is None:
|
|
return None
|
|
for link in data["links"]:
|
|
if link.get("rel") == "http://ostatus.org/schema/1.0/subscribe":
|
|
return link.get("template")
|
|
return None
|
|
|
|
|
|
async def get_actor_url(resource: str) -> str | None:
|
|
"""Mastodon-like WebFinger resolution to retrieve the activity stream Actor URL.
|
|
|
|
Returns:
|
|
the Actor URL or None if the resolution failed.
|
|
"""
|
|
data = await webfinger(resource)
|
|
if data is None:
|
|
return None
|
|
for link in data["links"]:
|
|
if (
|
|
link.get("rel") == "self"
|
|
and link.get("type") == "application/activity+json"
|
|
):
|
|
return link.get("href")
|
|
return None
|