use activitypub_federation::{config::Data, fetch::webfinger::extract_webfinger_name}; use axum::{ extract::Query, http::header, response::{IntoResponse, Response}, }; use serde::{Deserialize, Serialize}; use crate::data::FederationData; use crate::error::Error; #[derive(Deserialize)] pub struct WebfingerQuery { resource: String, } #[derive(Serialize)] struct WebfingerLink { rel: String, #[serde(rename = "type", skip_serializing_if = "Option::is_none")] kind: Option, #[serde(skip_serializing_if = "Option::is_none")] href: Option, } #[derive(Serialize)] struct WebfingerResponse { subject: String, /// Canonical URIs for the same account (acct: URI + AP actor URL). aliases: Vec, links: Vec, } pub async fn webfinger_handler( Query(query): Query, data: Data, ) -> Result { let name = extract_webfinger_name(&query.resource, &data)?; let user = data .user_repo .find_by_username(name) .await .map_err(Error::from)? .ok_or_else(|| Error::not_found(anyhow::anyhow!("user not found")))?; let ap_id = crate::urls::actor_url(&data.base_url, user.id); let acct_uri = format!("acct:{}@{}", user.username, data.domain); let wf = WebfingerResponse { subject: query.resource.clone(), aliases: vec![acct_uri, ap_id.to_string()], links: vec![ WebfingerLink { rel: "http://webfinger.net/rel/profile-page".to_string(), kind: Some("text/html".to_string()), href: Some(ap_id.to_string()), }, WebfingerLink { rel: "self".to_string(), kind: Some("application/activity+json".to_string()), href: Some(ap_id.to_string()), }, ], }; let body = serde_json::to_string(&wf).map_err(|e| Error::from(anyhow::anyhow!(e)))?; Ok(([(header::CONTENT_TYPE, "application/jrd+json")], body).into_response()) }