diff --git a/server/src/api/community.rs b/server/src/api/community.rs index c71608484..fb153895f 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -605,11 +605,11 @@ impl Perform for Oper { // Dont actually add to the community followers here, because you need // to wait for the accept user - .send_follow(&community.actor_id, &self.client, pool) + .send_follow(&community.actor_id()?, &self.client, pool) .await?; } else { user - .send_unfollow(&community.actor_id, &self.client, pool) + .send_unfollow(&community.actor_id()?, &self.client, pool) .await?; let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); if blocking(pool, unfollow).await?.is_err() { diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs index b5d6ce464..6b6b17b86 100644 --- a/server/src/apub/activities.rs +++ b/server/src/apub/activities.rs @@ -21,7 +21,7 @@ use uuid::Uuid; pub async fn send_activity_to_community( creator: &User_, community: &Community, - to: Vec, + to: Vec, activity: AnyBase, client: &Client, pool: &DbPool, @@ -43,17 +43,18 @@ pub async fn send_activity( client: &Client, activity: &AnyBase, actor: &dyn ActorType, - to: Vec, + to: Vec, ) -> Result<(), LemmyError> { let activity = serde_json::to_string(&activity)?; debug!("Sending activitypub activity {} to {:?}", activity, to); - for t in to { - let to_url = Url::parse(&t)?; + for to_url in to { check_is_apub_id_valid(&to_url)?; let res = retry_custom(|| async { - let request = client.post(&t).header("Content-Type", "application/json"); + let request = client + .post(to_url.as_str()) + .header("Content-Type", "application/json"); match sign(request, actor, activity.clone()).await { Ok(signed) => Ok(signed.send().await), diff --git a/server/src/apub/comment.rs b/server/src/apub/comment.rs index a9b4671a1..d90d8227b 100644 --- a/server/src/apub/comment.rs +++ b/server/src/apub/comment.rs @@ -302,7 +302,7 @@ impl ApubObjectType for Comment { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], delete.into_any_base()?, client, pool, @@ -344,7 +344,7 @@ impl ApubObjectType for Comment { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], undo.into_any_base()?, client, pool, @@ -377,7 +377,7 @@ impl ApubObjectType for Comment { send_activity_to_community( &mod_, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], remove.into_any_base()?, client, pool, @@ -419,7 +419,7 @@ impl ApubObjectType for Comment { send_activity_to_community( &mod_, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], undo.into_any_base()?, client, pool, @@ -455,7 +455,7 @@ impl ApubLikeableType for Comment { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], like.into_any_base()?, client, pool, @@ -488,7 +488,7 @@ impl ApubLikeableType for Comment { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], dislike.into_any_base()?, client, pool, @@ -529,7 +529,7 @@ impl ApubLikeableType for Comment { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], undo.into_any_base()?, client, pool, @@ -541,7 +541,7 @@ impl ApubLikeableType for Comment { struct MentionsAndAddresses { addressed_ccs: Vec, - inboxes: Vec, + inboxes: Vec, tags: Vec, } @@ -576,7 +576,7 @@ async fn collect_non_local_mentions_and_addresses( .filter(|m| !m.is_local()) .collect::>(); - let mut mention_inboxes = Vec::new(); + let mut mention_inboxes: Vec = Vec::new(); for mention in &mentions { // TODO should it be fetching it every time? if let Ok(actor_id) = fetch_webfinger_url(mention, client).await { @@ -584,7 +584,7 @@ async fn collect_non_local_mentions_and_addresses( addressed_ccs.push(actor_id.to_owned().to_string()); let mention_user = get_or_fetch_and_upsert_user(&actor_id, client, pool).await?; - let shared_inbox = mention_user.get_shared_inbox_url(); + let shared_inbox = mention_user.get_shared_inbox_url()?; mention_inboxes.push(shared_inbox); let mut mention_tag = Mention::new(); @@ -593,7 +593,7 @@ async fn collect_non_local_mentions_and_addresses( } } - let mut inboxes = vec![community.get_shared_inbox_url()]; + let mut inboxes = vec![community.get_shared_inbox_url()?]; inboxes.extend(mention_inboxes); inboxes = inboxes.into_iter().unique().collect(); diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 66a115405..fb8ef9bc6 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -7,8 +7,7 @@ use crate::{ create_apub_tombstone_response, create_tombstone, extensions::group_extensions::GroupExtension, - fetcher::get_or_fetch_and_upsert_user, - get_shared_inbox, + fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user}, insert_activity, ActorType, FromApub, @@ -49,7 +48,7 @@ use lemmy_db::{ post::Post, user::User_, }; -use lemmy_utils::{convert_datetime, location_info}; +use lemmy_utils::{convert_datetime, get_apub_protocol_string, location_info}; use serde::Deserialize; use url::Url; @@ -100,7 +99,7 @@ impl ToApub for Community { .set_following(self.get_following_url().parse()?) .set_liked(self.get_liked_url().parse()?) .set_endpoints(Endpoints { - shared_inbox: Some(self.get_shared_inbox_url().parse()?), + shared_inbox: Some(self.get_shared_inbox_url()?), ..Default::default() }); @@ -114,7 +113,7 @@ impl ToApub for Community { Ok(Ext2::new( ap_actor, group_extension, - self.get_public_key_ext(), + self.get_public_key_ext()?, )) } @@ -129,11 +128,11 @@ impl ActorType for Community { self.actor_id.to_owned() } - fn public_key(&self) -> String { - self.public_key.to_owned().unwrap() + fn public_key(&self) -> Option { + self.public_key.to_owned() } - fn private_key(&self) -> String { - self.private_key.to_owned().unwrap() + fn private_key(&self) -> Option { + self.private_key.to_owned() } /// As a local community, accept the follow request from a remote user. @@ -146,11 +145,11 @@ impl ActorType for Community { let actor_uri = follow .actor()? .as_single_xsd_any_uri() - .context(location_info!())? - .to_string(); + .context(location_info!())?; + let actor = get_or_fetch_and_upsert_actor(actor_uri, client, pool).await?; let mut accept = Accept::new(self.actor_id.to_owned(), follow.into_any_base()?); - let to = format!("{}/inbox", actor_uri); + let to = actor.get_inbox_url()?; accept .set_context(context()) .set_id(generate_activity_id(AcceptType::Accept)?) @@ -284,7 +283,10 @@ impl ActorType for Community { } /// For a given community, returns the inboxes of all followers. - async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError> { + /// + /// TODO: this function is very badly implemented, we should just store shared_inbox_url in + /// CommunityFollowerView + async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError> { let id = self.id; let inboxes = blocking(pool, move |conn| { @@ -293,8 +295,22 @@ impl ActorType for Community { .await??; let inboxes = inboxes .into_iter() - .map(|c| get_shared_inbox(&Url::parse(&c.user_actor_id).unwrap())) - .filter(|s| !s.is_empty()) + .map(|u| -> Result { + let url = Url::parse(&u.user_actor_id)?; + let domain = url.domain().context(location_info!())?; + let port = if let Some(port) = url.port() { + format!(":{}", port) + } else { + "".to_string() + }; + Ok(Url::parse(&format!( + "{}://{}{}/inbox", + get_apub_protocol_string(), + domain, + port, + ))?) + }) + .filter_map(Result::ok) .unique() .collect(); @@ -303,7 +319,7 @@ impl ActorType for Community { async fn send_follow( &self, - _follow_actor_id: &str, + _follow_actor_id: &Url, _client: &Client, _pool: &DbPool, ) -> Result<(), LemmyError> { @@ -312,7 +328,7 @@ impl ActorType for Community { async fn send_unfollow( &self, - _follow_actor_id: &str, + _follow_actor_id: &Url, _client: &Client, _pool: &DbPool, ) -> Result<(), LemmyError> { @@ -510,13 +526,15 @@ pub async fn do_announce( insert_activity(community.creator_id, announce.clone(), true, pool).await?; - let mut to = community.get_follower_inboxes(pool).await?; + let mut to: Vec = community.get_follower_inboxes(pool).await?; // dont send to the local instance, nor to the instance where the activity originally came from, // because that would result in a database error (same data inserted twice) // this seems to be the "easiest" stable alternative for remove_item() - to.retain(|x| *x != sender.get_shared_inbox_url()); - to.retain(|x| *x != community.get_shared_inbox_url()); + let sender_shared_inbox = sender.get_shared_inbox_url()?; + to.retain(|x| x != &sender_shared_inbox); + let community_shared_inbox = community.get_shared_inbox_url()?; + to.retain(|x| x != &community_shared_inbox); send_activity(client, &announce.into_any_base()?, community, to).await?; diff --git a/server/src/apub/extensions/signatures.rs b/server/src/apub/extensions/signatures.rs index 79648cec6..96063d5e0 100644 --- a/server/src/apub/extensions/signatures.rs +++ b/server/src/apub/extensions/signatures.rs @@ -2,11 +2,12 @@ use crate::{apub::ActorType, LemmyError}; use activitystreams::unparsed::UnparsedMutExt; use activitystreams_ext::UnparsedExtension; use actix_web::{client::ClientRequest, HttpRequest}; -use anyhow::anyhow; +use anyhow::{anyhow, Context}; use http_signature_normalization_actix::{ digest::{DigestClient, SignExt}, Config, }; +use lemmy_utils::location_info; use log::debug; use openssl::{ hash::MessageDigest, @@ -27,7 +28,7 @@ pub async fn sign( activity: String, ) -> Result, LemmyError> { let signing_key_id = format!("{}#main-key", actor.actor_id()?); - let private_key = actor.private_key(); + let private_key = actor.private_key().context(location_info!())?; let digest_client = request .signature_with_digest( @@ -49,6 +50,7 @@ pub async fn sign( } pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> { + let public_key = actor.public_key().context(location_info!())?; let verified = HTTP_SIG_CONFIG .begin_verify( request.method(), @@ -58,10 +60,9 @@ pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyE .verify(|signature, signing_string| -> Result { debug!( "Verifying with key {}, message {}", - &actor.public_key(), - &signing_string + &public_key, &signing_string ); - let public_key = PKey::public_key_from_pem(actor.public_key().as_bytes())?; + let public_key = PKey::public_key_from_pem(public_key.as_bytes())?; let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?; verifier.update(&signing_string.as_bytes())?; Ok(verifier.verify(&base64::decode(signature)?)?) diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index 5f3172bc4..f67471982 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -240,25 +240,13 @@ pub trait ApubLikeableType { ) -> Result<(), LemmyError>; } -pub fn get_shared_inbox(actor_id: &Url) -> String { - format!( - "{}://{}{}/inbox", - &actor_id.scheme(), - &actor_id.host_str().unwrap(), - if let Some(port) = actor_id.port() { - format!(":{}", port) - } else { - "".to_string() - }, - ) -} - #[async_trait::async_trait(?Send)] pub trait ActorType { fn actor_id_str(&self) -> String; - fn public_key(&self) -> String; - fn private_key(&self) -> String; + // TODO: every actor should have a public key, so this shouldnt be an option (needs to be fixed in db) + fn public_key(&self) -> Option; + fn private_key(&self) -> Option; /// numeric id in the database, used for insert_activity fn user_id(&self) -> i32; @@ -268,13 +256,13 @@ pub trait ActorType { #[allow(unused_variables)] async fn send_follow( &self, - follow_actor_id: &str, + follow_actor_id: &Url, client: &Client, pool: &DbPool, ) -> Result<(), LemmyError>; async fn send_unfollow( &self, - follow_actor_id: &str, + follow_actor_id: &Url, client: &Client, pool: &DbPool, ) -> Result<(), LemmyError>; @@ -314,7 +302,7 @@ pub trait ActorType { ) -> Result<(), LemmyError>; /// For a given community, returns the inboxes of all followers. - async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError>; + async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError>; fn actor_id(&self) -> Result { Url::parse(&self.actor_id_str()) @@ -325,9 +313,19 @@ pub trait ActorType { Url::parse(&format!("{}/inbox", &self.actor_id_str())) } - // TODO: make this return `Result - fn get_shared_inbox_url(&self) -> String { - get_shared_inbox(&self.actor_id().unwrap()) + fn get_shared_inbox_url(&self) -> Result { + let actor_id = self.actor_id()?; + let url = format!( + "{}://{}{}/inbox", + &actor_id.scheme(), + &actor_id.host_str().context(location_info!())?, + if let Some(port) = actor_id.port() { + format!(":{}", port) + } else { + "".to_string() + }, + ); + Ok(Url::parse(&url)?) } fn get_outbox_url(&self) -> Result { @@ -346,13 +344,15 @@ pub trait ActorType { format!("{}/liked", &self.actor_id_str()) } - fn get_public_key_ext(&self) -> PublicKeyExtension { - PublicKey { - id: format!("{}#main-key", self.actor_id_str()), - owner: self.actor_id_str(), - public_key_pem: self.public_key(), - } - .to_ext() + fn get_public_key_ext(&self) -> Result { + Ok( + PublicKey { + id: format!("{}#main-key", self.actor_id_str()), + owner: self.actor_id_str(), + public_key_pem: self.public_key().context(location_info!())?, + } + .to_ext(), + ) } } diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs index 9d16c2360..caf8f076a 100644 --- a/server/src/apub/post.rs +++ b/server/src/apub/post.rs @@ -32,7 +32,7 @@ use activitystreams::{ Update, }, context, - object::{kind::PageType, Image, Page, Tombstone}, + object::{kind::PageType, Image, Object, Page, Tombstone}, prelude::*, public, }; @@ -148,6 +148,50 @@ impl ToApub for Post { } } +struct EmbedType { + title: Option, + description: Option, + html: Option, +} + +fn extract_embed_from_apub( + page: &Ext1, PageExtension>, +) -> Result { + match page.inner.preview() { + Some(preview) => { + let preview_page = Page::from_any_base(preview.one().context(location_info!())?.to_owned())? + .context(location_info!())?; + let title = preview_page + .name() + .map(|n| n.one()) + .flatten() + .map(|s| s.as_xsd_string()) + .flatten() + .map(|s| s.to_string()); + let description = preview_page + .summary() + .map(|s| s.as_single_xsd_string()) + .flatten() + .map(|s| s.to_string()); + let html = preview_page + .content() + .map(|c| c.as_single_xsd_string()) + .flatten() + .map(|s| s.to_string()); + Ok(EmbedType { + title, + description, + html, + }) + } + None => Ok(EmbedType { + title: None, + description: None, + html: None, + }), + } +} + #[async_trait::async_trait(?Send)] impl FromApub for PostForm { type ApubType = PageExt; @@ -196,22 +240,7 @@ impl FromApub for PostForm { None => None, }; - let (embed_title, embed_description, embed_html) = match page.inner.preview() { - Some(preview) => { - let preview_page = Page::from_any_base(preview.one().unwrap().to_owned())?.unwrap(); - let name = preview_page - .name() - .map(|n| n.as_one().unwrap().as_xsd_string().unwrap().to_string()); - let summary = preview_page - .summary() - .map(|s| s.as_single_xsd_string().unwrap().to_string()); - let content = preview_page - .content() - .map(|c| c.as_single_xsd_string().unwrap().to_string()); - (name, summary, content) - } - None => (None, None, None), - }; + let embed = extract_embed_from_apub(page)?; let name = page .inner @@ -225,12 +254,16 @@ impl FromApub for PostForm { .inner .url() .as_ref() - .map(|u| u.as_single_xsd_string().unwrap().to_string()); + .map(|u| u.as_single_xsd_string()) + .flatten() + .map(|s| s.to_string()); let body = page .inner .content() .as_ref() - .map(|c| c.as_single_xsd_string().unwrap().to_string()); + .map(|c| c.as_single_xsd_string()) + .flatten() + .map(|s| s.to_string()); check_slurs(&name)?; let body_slurs_removed = body.map(|b| remove_slurs(&b)); Ok(PostForm { @@ -254,9 +287,9 @@ impl FromApub for PostForm { deleted: None, nsfw: ext.sensitive, stickied: Some(ext.stickied), - embed_title, - embed_description, - embed_html, + embed_title: embed.title, + embed_description: embed.description, + embed_html: embed.html, thumbnail_url, ap_id: check_actor_domain(page, expected_domain)?, local: false, @@ -288,7 +321,7 @@ impl ApubObjectType for Post { send_activity_to_community( creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], create.into_any_base()?, client, pool, @@ -319,7 +352,7 @@ impl ApubObjectType for Post { send_activity_to_community( creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], update.into_any_base()?, client, pool, @@ -349,7 +382,7 @@ impl ApubObjectType for Post { send_activity_to_community( creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], delete.into_any_base()?, client, pool, @@ -387,7 +420,7 @@ impl ApubObjectType for Post { send_activity_to_community( creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], undo.into_any_base()?, client, pool, @@ -417,7 +450,7 @@ impl ApubObjectType for Post { send_activity_to_community( mod_, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], remove.into_any_base()?, client, pool, @@ -455,7 +488,7 @@ impl ApubObjectType for Post { send_activity_to_community( mod_, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], undo.into_any_base()?, client, pool, @@ -488,7 +521,7 @@ impl ApubLikeableType for Post { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], like.into_any_base()?, client, pool, @@ -518,7 +551,7 @@ impl ApubLikeableType for Post { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], dislike.into_any_base()?, client, pool, @@ -556,7 +589,7 @@ impl ApubLikeableType for Post { send_activity_to_community( &creator, &community, - vec![community.get_shared_inbox_url()], + vec![community.get_shared_inbox_url()?], undo.into_any_base()?, client, pool, diff --git a/server/src/apub/private_message.rs b/server/src/apub/private_message.rs index b5f3bfb8f..743a484f2 100644 --- a/server/src/apub/private_message.rs +++ b/server/src/apub/private_message.rs @@ -6,6 +6,7 @@ use crate::{ create_tombstone, fetcher::get_or_fetch_and_upsert_user, insert_activity, + ActorType, ApubObjectType, FromApub, ToApub, @@ -132,7 +133,7 @@ impl ApubObjectType for PrivateMessage { let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut create = Create::new(creator.actor_id.to_owned(), note.into_any_base()?); - let to = format!("{}/inbox", recipient.actor_id); + let to = recipient.get_inbox_url()?; create .set_context(context()) .set_id(generate_activity_id(CreateType::Create)?) @@ -157,7 +158,7 @@ impl ApubObjectType for PrivateMessage { let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?); - let to = format!("{}/inbox", recipient.actor_id); + let to = recipient.get_inbox_url()?; update .set_context(context()) .set_id(generate_activity_id(UpdateType::Update)?) @@ -181,7 +182,7 @@ impl ApubObjectType for PrivateMessage { let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?); - let to = format!("{}/inbox", recipient.actor_id); + let to = recipient.get_inbox_url()?; delete .set_context(context()) .set_id(generate_activity_id(DeleteType::Delete)?) @@ -205,7 +206,7 @@ impl ApubObjectType for PrivateMessage { let recipient = blocking(pool, move |conn| User_::read(conn, recipient_id)).await??; let mut delete = Delete::new(creator.actor_id.to_owned(), note.into_any_base()?); - let to = format!("{}/inbox", recipient.actor_id); + let to = recipient.get_inbox_url()?; delete .set_context(context()) .set_id(generate_activity_id(DeleteType::Delete)?) diff --git a/server/src/apub/user.rs b/server/src/apub/user.rs index 89c86b7db..5f80b4817 100644 --- a/server/src/apub/user.rs +++ b/server/src/apub/user.rs @@ -4,6 +4,7 @@ use crate::{ activities::{generate_activity_id, send_activity}, check_actor_domain, create_apub_response, + fetcher::get_or_fetch_and_upsert_actor, insert_activity, ActorType, FromApub, @@ -83,7 +84,7 @@ impl ToApub for User_ { .set_following(self.get_following_url().parse()?) .set_liked(self.get_liked_url().parse()?) .set_endpoints(Endpoints { - shared_inbox: Some(self.get_shared_inbox_url().parse()?), + shared_inbox: Some(self.get_shared_inbox_url()?), ..Default::default() }); @@ -91,7 +92,7 @@ impl ToApub for User_ { ap_actor.set_preferred_username(i.to_owned()); } - Ok(Ext1::new(ap_actor, self.get_public_key_ext())) + Ok(Ext1::new(ap_actor, self.get_public_key_ext()?)) } fn to_tombstone(&self) -> Result { unimplemented!() @@ -104,26 +105,27 @@ impl ActorType for User_ { self.actor_id.to_owned() } - fn public_key(&self) -> String { - self.public_key.to_owned().unwrap() + fn public_key(&self) -> Option { + self.public_key.to_owned() } - fn private_key(&self) -> String { - self.private_key.to_owned().unwrap() + fn private_key(&self) -> Option { + self.private_key.to_owned() } /// As a given local user, send out a follow request to a remote community. async fn send_follow( &self, - follow_actor_id: &str, + follow_actor_id: &Url, client: &Client, pool: &DbPool, ) -> Result<(), LemmyError> { - let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id); + let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str()); follow .set_context(context()) .set_id(generate_activity_id(FollowType::Follow)?); - let to = format!("{}/inbox", follow_actor_id); + let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, client, pool).await?; + let to = follow_actor.get_inbox_url()?; insert_activity(self.id, follow.clone(), true, pool).await?; @@ -133,16 +135,17 @@ impl ActorType for User_ { async fn send_unfollow( &self, - follow_actor_id: &str, + follow_actor_id: &Url, client: &Client, pool: &DbPool, ) -> Result<(), LemmyError> { - let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id); + let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id.as_str()); follow .set_context(context()) .set_id(generate_activity_id(FollowType::Follow)?); + let follow_actor = get_or_fetch_and_upsert_actor(follow_actor_id, client, pool).await?; - let to = format!("{}/inbox", follow_actor_id); + let to = follow_actor.get_inbox_url()?; // Undo that fake activity let mut undo = Undo::new(Url::parse(&self.actor_id)?, follow.into_any_base()?); @@ -201,7 +204,7 @@ impl ActorType for User_ { unimplemented!() } - async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result, LemmyError> { + async fn get_follower_inboxes(&self, _pool: &DbPool) -> Result, LemmyError> { unimplemented!() } @@ -257,7 +260,9 @@ impl FromApub for UserForm { let bio = person .inner .summary() - .map(|s| s.as_single_xsd_string().unwrap().into()); + .map(|s| s.as_single_xsd_string()) + .flatten() + .map(|s| s.to_string()); check_slurs(&name)?; check_slurs_opt(&preferred_username)?; check_slurs_opt(&bio)?;