From bc857b2c08ae5609f4a3ec6f8eb65e3667de27ff Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 27 May 2026 22:21:58 +0200 Subject: [PATCH] feat: signed actor lookup and display_name on DbActor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add display_name field to DbActor, populated from AP Person.name in from_json. Expose LookedUpActor type and lookup_actor_by_handle method on ActivityPubService — uses the existing signed webfinger_https path so strict instances (Threads, etc.) return full actor data. --- Cargo.toml | 2 +- src/actors.rs | 4 ++++ src/lib.rs | 2 +- src/service.rs | 27 +++++++++++++++++++++++++++ src/user.rs | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f9f736c..5b7ffa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "k-ap" -version = "0.1.0" +version = "0.1.4" edition = "2024" description = "Generic ActivityPub protocol layer" license = "MIT" diff --git a/src/actors.rs b/src/actors.rs index fe27bec..e282334 100644 --- a/src/actors.rs +++ b/src/actors.rs @@ -19,6 +19,7 @@ use crate::user::ApProfileField; pub struct DbActor { pub user_id: uuid::Uuid, pub username: String, + pub display_name: Option, pub public_key_pem: String, pub private_key_pem: Option, pub inbox_url: Url, @@ -152,6 +153,7 @@ pub async fn get_local_actor( Ok(DbActor { user_id, username: user.username, + display_name: None, public_key_pem: public_key, private_key_pem: Some(private_key), inbox_url, @@ -219,6 +221,7 @@ impl Object for DbActor { Ok(Some(DbActor { user_id, username: user.username, + display_name: None, public_key_pem: public_key, private_key_pem: private_key, inbox_url, @@ -327,6 +330,7 @@ impl Object for DbActor { Ok(DbActor { user_id, username: json.preferred_username.clone(), + display_name: json.name.clone(), public_key_pem: json.public_key.public_key_pem, private_key_pem: None, inbox_url, diff --git a/src/lib.rs b/src/lib.rs index 055d205..a58591b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,4 +25,4 @@ pub use repository::{ BlockedDomain, FederationRepository, Follower, FollowerStatus, FollowingStatus, RemoteActor, }; pub use service::ActivityPubService; -pub use user::{ApProfileField, ApUser, ApUserRepository}; +pub use user::{ApProfileField, ApUser, ApUserRepository, LookedUpActor}; diff --git a/src/service.rs b/src/service.rs index 2ae50b5..1d9a7b1 100644 --- a/src/service.rs +++ b/src/service.rs @@ -330,6 +330,33 @@ impl ActivityPubService { Ok(serde_json::to_string(&WithContext::new_default(person))?) } + /// Resolve a `@user@domain` handle to actor data using a signed HTTP request. + /// Unlike a plain unauthenticated fetch, this works with instances (e.g. Threads) + /// that require HTTP signatures before returning full actor JSON. + pub async fn lookup_actor_by_handle( + &self, + handle: &str, + ) -> anyhow::Result { + let data = self.federation_config.to_request_data(); + let actor = Self::webfinger_https(handle, &data).await?; + let domain = actor.ap_id.host_str().unwrap_or("").to_string(); + let handle = format!("{}@{}", actor.username, domain); + Ok(crate::user::LookedUpActor { + handle, + display_name: actor.display_name, + bio: actor.bio, + avatar_url: actor.avatar_url, + banner_url: actor.banner_url, + ap_url: actor.ap_id, + outbox_url: actor.outbox_url, + followers_url: actor.followers_url, + following_url: actor.following_url, + also_known_as: actor.also_known_as, + profile_url: actor.profile_url, + attachment: actor.attachment, + }) + } + /// Returns the ActivityPub router compatible with any outer state `S`. /// Handlers only use `Data` injected by the middleware layer, /// so the router is independent of the application state type. diff --git a/src/user.rs b/src/user.rs index a99092b..0dcc9b4 100644 --- a/src/user.rs +++ b/src/user.rs @@ -7,6 +7,24 @@ pub struct ApProfileField { pub value: String, } +/// Resolved actor data returned by [`crate::service::ActivityPubService::lookup_actor_by_handle`]. +/// Fetched via a signed HTTP request so strict instances (e.g. Threads) return full data. +#[derive(Debug, Clone)] +pub struct LookedUpActor { + pub handle: String, + pub display_name: Option, + pub bio: Option, + pub avatar_url: Option, + pub banner_url: Option, + pub ap_url: Url, + pub outbox_url: Url, + pub followers_url: Url, + pub following_url: Url, + pub also_known_as: Option, + pub profile_url: Option, + pub attachment: Vec, +} + #[derive(Debug, Clone)] pub struct ApUser { pub id: uuid::Uuid,