diff --git a/Cargo.lock b/Cargo.lock index 146b72e74..34431d3ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -795,8 +795,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +dependencies = [ + "darling_core 0.13.0", + "darling_macro 0.13.0", ] [[package]] @@ -809,7 +819,21 @@ dependencies = [ "ident_case", "proc-macro2 1.0.27", "quote 1.0.9", - "strsim", + "strsim 0.9.3", + "syn 1.0.73", +] + +[[package]] +name = "darling_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.27", + "quote 1.0.9", + "strsim 0.10.0", "syn 1.0.73", ] @@ -819,7 +843,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core", + "darling_core 0.10.2", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "darling_macro" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +dependencies = [ + "darling_core 0.13.0", "quote 1.0.9", "syn 1.0.73", ] @@ -840,7 +875,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" dependencies = [ - "darling", + "darling 0.10.2", "derive_builder_core", "proc-macro2 1.0.27", "quote 1.0.9", @@ -853,7 +888,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ - "darling", + "darling 0.10.2", "proc-macro2 1.0.27", "quote 1.0.9", "syn 1.0.73", @@ -1661,7 +1696,6 @@ name = "lemmy_apub" version = "0.11.3" dependencies = [ "activitystreams", - "activitystreams-ext", "actix", "actix-rt", "actix-web", @@ -1695,6 +1729,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_with", "sha2", "strum", "strum_macros", @@ -2732,6 +2767,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + [[package]] name = "ryu" version = "1.0.5" @@ -2861,6 +2902,29 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad9fdbb69badc8916db738c25efd04f0a65297d26c2f8de4b62e57b8c12bc72" +dependencies = [ + "rustversion", + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1569374bd54623ec8bd592cf22ba6e03c0f177ff55fbc8c29a49e296e7adecf" +dependencies = [ + "darling 0.13.0", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + [[package]] name = "serial_test" version = "0.5.1" @@ -3056,6 +3120,12 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.21.0" diff --git a/crates/api/src/community.rs b/crates/api/src/community.rs index 017ef8493..0ca4ac47a 100644 --- a/crates/api/src/community.rs +++ b/crates/api/src/community.rs @@ -8,7 +8,13 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, is_mod_or_admin, }; -use lemmy_apub::{ActorType, CommunityType, UserType}; +use lemmy_apub::{ + activities::following::{ + follow::FollowCommunity as FollowCommunityApub, + undo::UndoFollowCommunity, + }, + CommunityType, +}; use lemmy_db_queries::{ source::{comment::Comment_, community::CommunityModerator_, post::Post_}, Bannable, @@ -74,15 +80,9 @@ impl Perform for FollowCommunity { } else if data.follow { // Dont actually add to the community followers here, because you need // to wait for the accept - local_user_view - .person - .send_follow(&community.actor_id(), context) - .await?; + FollowCommunityApub::send(&local_user_view.person, &community, context).await?; } else { - local_user_view - .person - .send_unfollow(&community.actor_id(), context) - .await?; + UndoFollowCommunity::send(&local_user_view.person, &community, context).await?; let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); if blocking(context.pool(), unfollow).await?.is_err() { return Err(ApiError::err("community_follower_already_exists").into()); diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs index 757596cd1..78dcd9311 100644 --- a/crates/api_crud/src/private_message/delete.rs +++ b/crates/api_crud/src/private_message/delete.rs @@ -5,7 +5,10 @@ use lemmy_api_common::{ get_local_user_view_from_jwt, person::{DeletePrivateMessage, PrivateMessageResponse}, }; -use lemmy_apub::ApubObjectType; +use lemmy_apub::activities::private_message::{ + delete::DeletePrivateMessage as DeletePrivateMessageApub, + undo_delete::UndoDeletePrivateMessage, +}; use lemmy_db_queries::{source::private_message::PrivateMessage_, Crud, DeleteableOrRemoveable}; use lemmy_db_schema::source::private_message::PrivateMessage; use lemmy_db_views::{local_user_view::LocalUserView, private_message_view::PrivateMessageView}; @@ -45,13 +48,14 @@ impl PerformCrud for DeletePrivateMessage { // Send the apub update if data.deleted { - updated_private_message - .blank_out_deleted_or_removed_info() - .send_delete(&local_user_view.person, context) - .await?; + DeletePrivateMessageApub::send( + &local_user_view.person, + &updated_private_message.blank_out_deleted_or_removed_info(), + context, + ) + .await?; } else { - updated_private_message - .send_undo_delete(&local_user_view.person, context) + UndoDeletePrivateMessage::send(&local_user_view.person, &updated_private_message, context) .await?; } diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index 1937dffd7..e0c714c10 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -21,11 +21,11 @@ lemmy_api_common = { version = "=0.11.3", path = "../api_common" } lemmy_websocket = { version = "=0.11.3", path = "../websocket" } diesel = "1.4.7" activitystreams = "0.7.0-alpha.11" -activitystreams-ext = "0.1.0-alpha.2" bcrypt = "0.10.0" chrono = { version = "0.4.19", features = ["serde"] } serde_json = { version = "1.0.64", features = ["preserve_order"] } serde = { version = "1.0.126", features = ["derive"] } +serde_with = "1.9.4" actix = "0.12.0" actix-web = { version = "4.0.0-beta.8", default-features = false } actix-rt = { version = "2.2.0", default-features = false } diff --git a/crates/apub/src/activities/comment/create_or_update.rs b/crates/apub/src/activities/comment/create_or_update.rs index 4cafafbaa..bfc1b7fe6 100644 --- a/crates/apub/src/activities/comment/create_or_update.rs +++ b/crates/apub/src/activities/comment/create_or_update.rs @@ -96,7 +96,7 @@ impl ActivityHandler for CreateOrUpdateComment { request_counter, ) .await?; - verify_domains_match(&self.common.actor, &self.object.id)?; + verify_domains_match(&self.common.actor, self.object.id_unchecked())?; // TODO: should add a check that the correct community is in cc (probably needs changes to // comment deserialization) self.object.verify(context, request_counter).await?; @@ -104,18 +104,12 @@ impl ActivityHandler for CreateOrUpdateComment { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let comment = Comment::from_apub( - &self.object, - context, - self.common.actor.clone(), - request_counter, - false, - ) - .await?; + let comment = + Comment::from_apub(&self.object, context, &self.common.actor, request_counter).await?; let recipients = get_notif_recipients(&self.common.actor, &comment, context, request_counter).await?; let notif_type = match self.kind { diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index 0dcd9818b..db6d369a1 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -45,7 +45,7 @@ impl ActivityHandler for AddMod { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { @@ -71,7 +71,7 @@ impl ActivityHandler for AddMod { .await??; } if community.local { - let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?; + let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(&self)?)?; community .send_announce(anybase, Some(self.object.clone()), context) .await?; diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 9cde8a43c..fc3bebfed 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -110,7 +110,7 @@ impl ActivityHandler for AnnounceActivity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index 34909b2e6..bf362c482 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -42,7 +42,7 @@ impl ActivityHandler for BlockUserFromCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs index 53b665db1..514c90ccd 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -43,7 +43,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index fd0bf2d45..964a42388 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -5,8 +5,7 @@ use crate::{ verify_mod_action, verify_person_in_community, }, - objects::FromApubToForm, - GroupExt, + objects::community::Group, }; use activitystreams::activity::kind::UpdateType; use lemmy_api_common::blocking; @@ -23,7 +22,7 @@ use url::Url; #[serde(rename_all = "camelCase")] pub struct UpdateCommunity { to: PublicUrl, - object: GroupExt, + object: Group, cc: [Url; 1], #[serde(rename = "type")] kind: UpdateType, @@ -45,9 +44,9 @@ impl ActivityHandler for UpdateCommunity { } async fn receive( - &self, + self, context: &LemmyContext, - request_counter: &mut i32, + _request_counter: &mut i32, ) -> Result<(), LemmyError> { let cc = self.cc[0].clone().into(); let community = blocking(context.pool(), move |conn| { @@ -55,14 +54,8 @@ impl ActivityHandler for UpdateCommunity { }) .await??; - let updated_community = CommunityForm::from_apub( - &self.object, - context, - community.actor_id.clone().into(), - request_counter, - false, - ) - .await?; + let updated_community = + Group::from_apub_to_form(&self.object, &community.actor_id.clone().into()).await?; let cf = CommunityForm { name: updated_community.name, title: updated_community.title, diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index cabfcce86..d377a6696 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -74,7 +74,7 @@ impl ActivityHandler for DeletePostCommentOrCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index ea70e5f5e..e57e6b401 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -62,7 +62,7 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index 6bd5d272f..bc6895899 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -1,12 +1,23 @@ use crate::{ - activities::{following::follow::FollowCommunity, verify_activity, verify_community}, + activities::{ + following::follow::FollowCommunity, + generate_activity_id, + verify_activity, + verify_community, + }, + activity_queue::send_activity_new, + extensions::context::lemmy_context, fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, + ActorType, }; use activitystreams::activity::kind::AcceptType; use lemmy_api_common::blocking; use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler}; -use lemmy_db_queries::Followable; -use lemmy_db_schema::source::community::CommunityFollower; +use lemmy_db_queries::{ApubObject, Followable}; +use lemmy_db_schema::source::{ + community::{Community, CommunityFollower}, + person::Person, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; @@ -22,6 +33,35 @@ pub struct AcceptFollowCommunity { common: ActivityCommonFields, } +impl AcceptFollowCommunity { + pub async fn send(follow: FollowCommunity, context: &LemmyContext) -> Result<(), LemmyError> { + let community_id = follow.object.clone(); + let community = blocking(context.pool(), move |conn| { + Community::read_from_apub_id(conn, &community_id.into()) + }) + .await??; + let person_id = follow.common.actor.clone(); + let person = blocking(context.pool(), move |conn| { + Person::read_from_apub_id(conn, &person_id.into()) + }) + .await??; + + let id = generate_activity_id(AcceptType::Accept)?; + let accept = AcceptFollowCommunity { + to: person.actor_id(), + object: follow, + kind: AcceptType::Accept, + common: ActivityCommonFields { + context: lemmy_context(), + id: id.clone(), + actor: community.actor_id(), + unparsed: Default::default(), + }, + }; + let inbox = vec![person.inbox_url.into()]; + send_activity_new(context, &accept, &id, &community, inbox, true).await + } +} /// Handle accepted follows #[async_trait::async_trait(?Send)] impl ActivityHandler for AcceptFollowCommunity { @@ -39,7 +79,7 @@ impl ActivityHandler for AcceptFollowCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index 49c9970c0..aa96fb39f 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -1,18 +1,24 @@ use crate::{ - activities::{verify_activity, verify_person}, + activities::{ + following::accept::AcceptFollowCommunity, + generate_activity_id, + verify_activity, + verify_person, + }, + activity_queue::send_activity_new, + extensions::context::lemmy_context, fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, - CommunityType, + ActorType, }; -use activitystreams::{ - activity::{kind::FollowType, Follow}, - base::{AnyBase, ExtendsExt}, -}; -use anyhow::Context; +use activitystreams::activity::kind::FollowType; use lemmy_api_common::blocking; use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::Followable; -use lemmy_db_schema::source::community::{CommunityFollower, CommunityFollowerForm}; -use lemmy_utils::{location_info, LemmyError}; +use lemmy_db_schema::source::{ + community::{Community, CommunityFollower, CommunityFollowerForm}, + person::Person, +}; +use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; @@ -22,11 +28,44 @@ pub struct FollowCommunity { pub(in crate::activities::following) to: Url, pub(in crate::activities::following) object: Url, #[serde(rename = "type")] - kind: FollowType, + pub(in crate::activities::following) kind: FollowType, #[serde(flatten)] pub(in crate::activities::following) common: ActivityCommonFields, } +impl FollowCommunity { + pub async fn send( + actor: &Person, + community: &Community, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let community_follower_form = CommunityFollowerForm { + community_id: community.id, + person_id: actor.id, + pending: true, + }; + blocking(context.pool(), move |conn| { + CommunityFollower::follow(conn, &community_follower_form).ok() + }) + .await?; + + let id = generate_activity_id(FollowType::Follow)?; + let follow = FollowCommunity { + to: community.actor_id(), + object: community.actor_id(), + kind: FollowType::Follow, + common: ActivityCommonFields { + context: lemmy_context(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + let inbox = vec![community.inbox_url.clone().into()]; + send_activity_new(context, &follow, &id, actor, inbox, true).await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for FollowCommunity { async fn verify( @@ -41,7 +80,7 @@ impl ActivityHandler for FollowCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { @@ -61,10 +100,7 @@ impl ActivityHandler for FollowCommunity { }) .await?; - // TODO: avoid the conversion and pass our own follow struct directly - let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?; - let anybase = Follow::from_any_base(anybase)?.context(location_info!())?; - community.send_accept_follow(anybase, context).await + AcceptFollowCommunity::send(self, context).await } fn common(&self) -> &ActivityCommonFields { diff --git a/crates/apub/src/activities/following/undo.rs b/crates/apub/src/activities/following/undo.rs index 1936997f4..7fbc7be5b 100644 --- a/crates/apub/src/activities/following/undo.rs +++ b/crates/apub/src/activities/following/undo.rs @@ -1,12 +1,23 @@ use crate::{ - activities::{following::follow::FollowCommunity, verify_activity, verify_person}, + activities::{ + following::follow::FollowCommunity, + generate_activity_id, + verify_activity, + verify_person, + }, + activity_queue::send_activity_new, + extensions::context::lemmy_context, fetcher::{community::get_or_fetch_and_upsert_community, person::get_or_fetch_and_upsert_person}, + ActorType, }; -use activitystreams::activity::kind::UndoType; +use activitystreams::activity::kind::{FollowType, UndoType}; use lemmy_api_common::blocking; use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler}; use lemmy_db_queries::Followable; -use lemmy_db_schema::source::community::{CommunityFollower, CommunityFollowerForm}; +use lemmy_db_schema::source::{ + community::{Community, CommunityFollower, CommunityFollowerForm}, + person::Person, +}; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; @@ -22,6 +33,39 @@ pub struct UndoFollowCommunity { common: ActivityCommonFields, } +impl UndoFollowCommunity { + pub async fn send( + actor: &Person, + community: &Community, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let id = generate_activity_id(UndoType::Undo)?; + let undo = UndoFollowCommunity { + to: community.actor_id(), + object: FollowCommunity { + to: community.actor_id(), + object: community.actor_id(), + kind: FollowType::Follow, + common: ActivityCommonFields { + context: lemmy_context(), + id: generate_activity_id(FollowType::Follow)?, + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }, + kind: UndoType::Undo, + common: ActivityCommonFields { + context: lemmy_context(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + let inbox = vec![community.get_shared_inbox_or_inbox_url()]; + send_activity_new(context, &undo, &id, actor, inbox, true).await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for UndoFollowCommunity { async fn verify( @@ -38,7 +82,7 @@ impl ActivityHandler for UndoFollowCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/post/create_or_update.rs b/crates/apub/src/activities/post/create_or_update.rs index 4af720753..94838e16f 100644 --- a/crates/apub/src/activities/post/create_or_update.rs +++ b/crates/apub/src/activities/post/create_or_update.rs @@ -87,7 +87,7 @@ impl ActivityHandler for CreateOrUpdatePost { verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?; match self.kind { CreateOrUpdateType::Create => { - verify_domains_match(&self.common.actor, &self.object.id)?; + verify_domains_match(&self.common.actor, self.object.id_unchecked())?; verify_urls_match(&self.common.actor, &self.object.attributed_to)?; // Check that the post isnt locked or stickied, as that isnt possible for newly created posts. // However, when fetching a remote post we generate a new create activity with the current @@ -104,7 +104,7 @@ impl ActivityHandler for CreateOrUpdatePost { if is_mod_action { verify_mod_action(&self.common.actor, community_id, context).await?; } else { - verify_domains_match(&self.common.actor, &self.object.id)?; + verify_domains_match(&self.common.actor, self.object.id_unchecked())?; verify_urls_match(&self.common.actor, &self.object.attributed_to)?; } } @@ -114,20 +114,13 @@ impl ActivityHandler for CreateOrUpdatePost { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { let actor = get_or_fetch_and_upsert_person(&self.common.actor, context, request_counter).await?; - let post = Post::from_apub( - &self.object, - context, - actor.actor_id(), - request_counter, - false, - ) - .await?; + let post = Post::from_apub(&self.object, context, &actor.actor_id(), request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreatePost, diff --git a/crates/apub/src/activities/private_message/create_or_update.rs b/crates/apub/src/activities/private_message/create_or_update.rs index 05f3c98f2..09582689b 100644 --- a/crates/apub/src/activities/private_message/create_or_update.rs +++ b/crates/apub/src/activities/private_message/create_or_update.rs @@ -53,15 +53,8 @@ impl CreateOrUpdatePrivateMessage { unparsed: Default::default(), }, }; - send_activity_new( - context, - &create_or_update, - &id, - actor, - vec![recipient.get_shared_inbox_or_inbox_url()], - true, - ) - .await + let inbox = vec![recipient.get_shared_inbox_or_inbox_url()]; + send_activity_new(context, &create_or_update, &id, actor, inbox, true).await } } #[async_trait::async_trait(?Send)] @@ -73,24 +66,18 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { ) -> Result<(), LemmyError> { verify_activity(self.common())?; verify_person(&self.common.actor, context, request_counter).await?; - verify_domains_match(&self.common.actor, &self.object.id)?; + verify_domains_match(&self.common.actor, self.object.id_unchecked())?; self.object.verify(context, request_counter).await?; Ok(()) } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let private_message = PrivateMessage::from_apub( - &self.object, - context, - self.common.actor.clone(), - request_counter, - false, - ) - .await?; + let private_message = + PrivateMessage::from_apub(&self.object, context, &self.common.actor, request_counter).await?; let notif_type = match self.kind { CreateOrUpdateType::Create => UserOperationCrud::CreatePrivateMessage, diff --git a/crates/apub/src/activities/private_message/delete.rs b/crates/apub/src/activities/private_message/delete.rs index b7f1a3cd9..4c990a2d1 100644 --- a/crates/apub/src/activities/private_message/delete.rs +++ b/crates/apub/src/activities/private_message/delete.rs @@ -1,9 +1,19 @@ -use crate::activities::{private_message::send_websocket_message, verify_activity, verify_person}; +use crate::{ + activities::{ + generate_activity_id, + private_message::send_websocket_message, + verify_activity, + verify_person, + }, + activity_queue::send_activity_new, + extensions::context::lemmy_context, + ActorType, +}; use activitystreams::activity::kind::DeleteType; use lemmy_api_common::blocking; use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandler}; -use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject}; -use lemmy_db_schema::source::private_message::PrivateMessage; +use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud}; +use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_utils::LemmyError; use lemmy_websocket::{LemmyContext, UserOperationCrud}; use url::Url; @@ -11,14 +21,41 @@ use url::Url; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct DeletePrivateMessage { - to: Url, + pub(in crate::activities::private_message) to: Url, pub(in crate::activities::private_message) object: Url, #[serde(rename = "type")] - kind: DeleteType, + pub(in crate::activities::private_message) kind: DeleteType, #[serde(flatten)] pub(in crate::activities::private_message) common: ActivityCommonFields, } +impl DeletePrivateMessage { + pub async fn send( + actor: &Person, + pm: &PrivateMessage, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let recipient_id = pm.recipient_id; + let recipient = + blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; + + let id = generate_activity_id(DeleteType::Delete)?; + let delete = DeletePrivateMessage { + to: actor.actor_id(), + object: pm.ap_id.clone().into(), + kind: DeleteType::Delete, + common: ActivityCommonFields { + context: lemmy_context(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + let inbox = vec![recipient.get_shared_inbox_or_inbox_url()]; + send_activity_new(context, &delete, &id, actor, inbox, true).await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for DeletePrivateMessage { async fn verify( @@ -33,7 +70,7 @@ impl ActivityHandler for DeletePrivateMessage { } async fn receive( - &self, + self, context: &LemmyContext, _request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/private_message/undo_delete.rs b/crates/apub/src/activities/private_message/undo_delete.rs index dc297b5af..5bbca90e6 100644 --- a/crates/apub/src/activities/private_message/undo_delete.rs +++ b/crates/apub/src/activities/private_message/undo_delete.rs @@ -1,9 +1,15 @@ -use crate::activities::{ - private_message::{delete::DeletePrivateMessage, send_websocket_message}, - verify_activity, - verify_person, +use crate::{ + activities::{ + generate_activity_id, + private_message::{delete::DeletePrivateMessage, send_websocket_message}, + verify_activity, + verify_person, + }, + activity_queue::send_activity_new, + extensions::context::lemmy_context, + ActorType, }; -use activitystreams::activity::kind::UndoType; +use activitystreams::activity::kind::{DeleteType, UndoType}; use lemmy_api_common::blocking; use lemmy_apub_lib::{ verify_domains_match, @@ -11,8 +17,8 @@ use lemmy_apub_lib::{ ActivityCommonFields, ActivityHandler, }; -use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject}; -use lemmy_db_schema::source::private_message::PrivateMessage; +use lemmy_db_queries::{source::private_message::PrivateMessage_, ApubObject, Crud}; +use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; use lemmy_utils::LemmyError; use lemmy_websocket::{LemmyContext, UserOperationCrud}; use url::Url; @@ -28,6 +34,45 @@ pub struct UndoDeletePrivateMessage { common: ActivityCommonFields, } +impl UndoDeletePrivateMessage { + pub async fn send( + actor: &Person, + pm: &PrivateMessage, + context: &LemmyContext, + ) -> Result<(), LemmyError> { + let recipient_id = pm.recipient_id; + let recipient = + blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; + + let object = DeletePrivateMessage { + to: recipient.actor_id(), + object: pm.ap_id.clone().into(), + kind: DeleteType::Delete, + common: ActivityCommonFields { + context: lemmy_context(), + id: generate_activity_id(DeleteType::Delete)?, + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + + let id = generate_activity_id(UndoType::Undo)?; + let undo = UndoDeletePrivateMessage { + to: recipient.actor_id(), + object, + kind: UndoType::Undo, + common: ActivityCommonFields { + context: lemmy_context(), + id: id.clone(), + actor: actor.actor_id(), + unparsed: Default::default(), + }, + }; + let inbox = vec![recipient.get_shared_inbox_or_inbox_url()]; + send_activity_new(context, &undo, &id, actor, inbox, true).await + } +} + #[async_trait::async_trait(?Send)] impl ActivityHandler for UndoDeletePrivateMessage { async fn verify( @@ -44,7 +89,7 @@ impl ActivityHandler for UndoDeletePrivateMessage { } async fn receive( - &self, + self, context: &LemmyContext, _request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/removal/remove.rs b/crates/apub/src/activities/removal/remove.rs index 053ddadf8..83aaabc6f 100644 --- a/crates/apub/src/activities/removal/remove.rs +++ b/crates/apub/src/activities/removal/remove.rs @@ -77,7 +77,7 @@ impl ActivityHandler for RemovePostCommentCommunityOrMod { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { @@ -115,7 +115,7 @@ impl ActivityHandler for RemovePostCommentCommunityOrMod { CommunityModerator::leave(conn, &form) }) .await??; - let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(self)?)?; + let anybase = AnyBase::from_arbitrary_json(serde_json::to_string(&self)?)?; community .send_announce(anybase, Some(self.object.clone()), context) .await?; diff --git a/crates/apub/src/activities/removal/undo_remove.rs b/crates/apub/src/activities/removal/undo_remove.rs index db4518b27..003eb4aaa 100644 --- a/crates/apub/src/activities/removal/undo_remove.rs +++ b/crates/apub/src/activities/removal/undo_remove.rs @@ -62,7 +62,7 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/send/community.rs b/crates/apub/src/activities/send/community.rs index 852410624..6f88dd36f 100644 --- a/crates/apub/src/activities/send/community.rs +++ b/crates/apub/src/activities/send/community.rs @@ -1,9 +1,9 @@ use crate::{ activities::generate_activity_id, - activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers}, + activity_queue::{send_to_community, send_to_community_followers}, check_is_apub_id_valid, extensions::context::lemmy_context, - fetcher::{get_or_fetch_and_upsert_actor, person::get_or_fetch_and_upsert_person}, + fetcher::get_or_fetch_and_upsert_actor, generate_moderators_url, insert_activity, objects::ToApub, @@ -13,7 +13,6 @@ use crate::{ use activitystreams::{ activity::{ kind::{ - AcceptType, AddType, AnnounceType, BlockType, @@ -23,13 +22,10 @@ use activitystreams::{ UndoType, UpdateType, }, - Accept, - ActorAndObjectRefExt, Add, Announce, Block, Delete, - Follow, OptTargetRefExt, Remove, Undo, @@ -81,31 +77,6 @@ impl CommunityType for Community { self.followers_url.clone().into() } - /// As a local community, accept the follow request from a remote person. - async fn send_accept_follow( - &self, - follow: Follow, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let actor_uri = follow - .actor()? - .as_single_xsd_any_uri() - .context(location_info!())?; - let person = get_or_fetch_and_upsert_person(actor_uri, context, &mut 0).await?; - - let mut accept = Accept::new( - self.actor_id.to_owned().into_inner(), - follow.into_any_base()?, - ); - accept - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(AcceptType::Accept)?) - .set_to(person.actor_id()); - - send_activity_single_dest(accept, self, person.inbox_url.into(), context).await?; - Ok(()) - } - /// If a remote community is updated by a local mod, send the updated info to the community's /// instance. async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError> { @@ -114,7 +85,7 @@ impl CommunityType for Community { } else { let mut update = Update::new( mod_.actor_id(), - self.to_apub(context.pool()).await?.into_any_base()?, + AnyBase::from_arbitrary_json(self.to_apub(context.pool()).await?)?, ); update .set_many_contexts(lemmy_context()) diff --git a/crates/apub/src/activities/send/mod.rs b/crates/apub/src/activities/send/mod.rs index 65135bdda..c00ecb1a7 100644 --- a/crates/apub/src/activities/send/mod.rs +++ b/crates/apub/src/activities/send/mod.rs @@ -2,4 +2,3 @@ pub(crate) mod comment; pub(crate) mod community; pub(crate) mod person; pub(crate) mod post; -pub(crate) mod private_message; diff --git a/crates/apub/src/activities/send/person.rs b/crates/apub/src/activities/send/person.rs index b7b43c433..cb53bd6a9 100644 --- a/crates/apub/src/activities/send/person.rs +++ b/crates/apub/src/activities/send/person.rs @@ -1,27 +1,5 @@ -use crate::{ - activities::generate_activity_id, - activity_queue::send_activity_single_dest, - extensions::context::lemmy_context, - ActorType, - UserType, -}; -use activitystreams::{ - activity::{ - kind::{FollowType, UndoType}, - Follow, - Undo, - }, - base::{BaseExt, ExtendsExt}, - object::ObjectExt, -}; -use lemmy_api_common::blocking; -use lemmy_db_queries::{ApubObject, Followable}; -use lemmy_db_schema::source::{ - community::{Community, CommunityFollower, CommunityFollowerForm}, - person::Person, -}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; +use crate::ActorType; +use lemmy_db_schema::source::person::Person; use url::Url; impl ActorType for Person { @@ -51,69 +29,3 @@ impl ActorType for Person { .into() } } - -#[async_trait::async_trait(?Send)] -impl UserType for Person { - /// As a given local person, send out a follow request to a remote community. - async fn send_follow( - &self, - follow_actor_id: &Url, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let follow_actor_id = follow_actor_id.to_owned(); - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &follow_actor_id.into()) - }) - .await??; - - let community_follower_form = CommunityFollowerForm { - community_id: community.id, - person_id: self.id, - pending: true, - }; - blocking(context.pool(), move |conn| { - CommunityFollower::follow(conn, &community_follower_form).ok() - }) - .await?; - - let mut follow = Follow::new(self.actor_id(), community.actor_id()); - follow - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(FollowType::Follow)?) - .set_to(community.actor_id()); - - send_activity_single_dest(follow, self, community.inbox_url.into(), context).await?; - Ok(()) - } - - async fn send_unfollow( - &self, - follow_actor_id: &Url, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let follow_actor_id = follow_actor_id.to_owned(); - let community = blocking(context.pool(), move |conn| { - Community::read_from_apub_id(conn, &follow_actor_id.into()) - }) - .await??; - - let mut follow = Follow::new(self.actor_id(), community.actor_id()); - follow - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(FollowType::Follow)?) - .set_to(community.actor_id()); - - // Undo that fake activity - let mut undo = Undo::new( - self.actor_id.to_owned().into_inner(), - follow.into_any_base()?, - ); - undo - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(UndoType::Undo)?) - .set_to(community.actor_id()); - - send_activity_single_dest(undo, self, community.inbox_url.into(), context).await?; - Ok(()) - } -} diff --git a/crates/apub/src/activities/send/private_message.rs b/crates/apub/src/activities/send/private_message.rs deleted file mode 100644 index edcff3771..000000000 --- a/crates/apub/src/activities/send/private_message.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{ - activities::generate_activity_id, - activity_queue::send_activity_single_dest, - extensions::context::lemmy_context, - ActorType, - ApubObjectType, -}; -use activitystreams::{ - activity::{ - kind::{DeleteType, UndoType}, - Delete, - Undo, - }, - base::{BaseExt, ExtendsExt}, - object::ObjectExt, -}; -use lemmy_api_common::blocking; -use lemmy_db_queries::Crud; -use lemmy_db_schema::source::{person::Person, private_message::PrivateMessage}; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; - -#[async_trait::async_trait(?Send)] -impl ApubObjectType for PrivateMessage { - async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> { - let recipient_id = self.recipient_id; - let recipient = - blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; - - let mut delete = Delete::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - delete - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(DeleteType::Delete)?) - .set_to(recipient.actor_id()); - - send_activity_single_dest(delete, creator, recipient.inbox_url.into(), context).await?; - Ok(()) - } - - async fn send_undo_delete( - &self, - creator: &Person, - context: &LemmyContext, - ) -> Result<(), LemmyError> { - let recipient_id = self.recipient_id; - let recipient = - blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; - - let mut delete = Delete::new( - creator.actor_id.to_owned().into_inner(), - self.ap_id.to_owned().into_inner(), - ); - delete - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(DeleteType::Delete)?) - .set_to(recipient.actor_id()); - - // Undo that fake activity - let mut undo = Undo::new( - creator.actor_id.to_owned().into_inner(), - delete.into_any_base()?, - ); - undo - .set_many_contexts(lemmy_context()) - .set_id(generate_activity_id(UndoType::Undo)?) - .set_to(recipient.actor_id()); - - send_activity_single_dest(undo, creator, recipient.inbox_url.into(), context).await?; - Ok(()) - } - - async fn send_remove(&self, _mod_: &Person, _context: &LemmyContext) -> Result<(), LemmyError> { - unimplemented!() - } - - async fn send_undo_remove( - &self, - _mod_: &Person, - _context: &LemmyContext, - ) -> Result<(), LemmyError> { - unimplemented!() - } -} diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index 8c3137e87..5b5feac9d 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -101,7 +101,7 @@ impl ActivityHandler for UndoVote { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 0f5a2b5c4..4183c2ad0 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -113,7 +113,7 @@ impl ActivityHandler for Vote { } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { diff --git a/crates/apub/src/activity_queue.rs b/crates/apub/src/activity_queue.rs index c7ff0a81d..48cccc612 100644 --- a/crates/apub/src/activity_queue.rs +++ b/crates/apub/src/activity_queue.rs @@ -31,34 +31,6 @@ use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin}; use url::Url; -/// Sends a local activity to a single, remote actor. -/// -/// * `activity` the apub activity to be sent -/// * `creator` the local actor which created the activity -/// * `inbox` the inbox url where the activity should be delivered to -pub(crate) async fn send_activity_single_dest( - activity: T, - creator: &dyn ActorType, - inbox: Url, - context: &LemmyContext, -) -> Result<(), LemmyError> -where - T: AsObject + Extends + Debug + BaseExt, - Kind: Serialize, - >::Error: From + Send + Sync + 'static, -{ - if check_is_apub_id_valid(&inbox, false).is_ok() { - debug!( - "Sending activity {:?} to {}", - &activity.id_unchecked().map(ToString::to_string), - &inbox - ); - send_activity_internal(context, activity, creator, vec![inbox], true, true).await?; - } - - Ok(()) -} - /// From a local community, send activity to all remote followers. /// /// * `activity` the apub activity to send diff --git a/crates/apub/src/extensions/group_extension.rs b/crates/apub/src/extensions/group_extension.rs deleted file mode 100644 index c83becf21..000000000 --- a/crates/apub/src/extensions/group_extension.rs +++ /dev/null @@ -1,43 +0,0 @@ -use activitystreams::unparsed::UnparsedMutExt; -use activitystreams_ext::UnparsedExtension; -use lemmy_utils::LemmyError; -use serde::{Deserialize, Serialize}; -use url::Url; - -/// Activitystreams extension to allow (de)serializing additional Community field -/// `sensitive` (called 'nsfw' in Lemmy). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct GroupExtension { - pub sensitive: Option, - pub moderators: Option, -} - -impl GroupExtension { - pub fn new(sensitive: bool, moderators_url: Url) -> Result { - Ok(GroupExtension { - sensitive: Some(sensitive), - moderators: Some(moderators_url), - }) - } -} - -impl UnparsedExtension for GroupExtension -where - U: UnparsedMutExt, -{ - type Error = serde_json::Error; - - fn try_from_unparsed(unparsed_mut: &mut U) -> Result { - Ok(GroupExtension { - sensitive: unparsed_mut.remove("sensitive")?, - moderators: unparsed_mut.remove("moderators")?, - }) - } - - fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> { - unparsed_mut.insert("sensitive", self.sensitive)?; - unparsed_mut.insert("moderators", self.moderators)?; - Ok(()) - } -} diff --git a/crates/apub/src/extensions/mod.rs b/crates/apub/src/extensions/mod.rs index 781e89e6e..7c58789a9 100644 --- a/crates/apub/src/extensions/mod.rs +++ b/crates/apub/src/extensions/mod.rs @@ -1,4 +1,2 @@ pub mod context; -pub(crate) mod group_extension; -pub(crate) mod person_extension; pub mod signatures; diff --git a/crates/apub/src/extensions/person_extension.rs b/crates/apub/src/extensions/person_extension.rs deleted file mode 100644 index 248adcc43..000000000 --- a/crates/apub/src/extensions/person_extension.rs +++ /dev/null @@ -1,36 +0,0 @@ -use activitystreams::unparsed::UnparsedMutExt; -use activitystreams_ext::UnparsedExtension; -use lemmy_utils::LemmyError; -use serde::{Deserialize, Serialize}; - -/// Activitystreams extension to allow (de)serializing additional Person field -/// `also_known_as` (used for Matrix profile link). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct PersonExtension { - pub matrix_user_id: Option, -} - -impl PersonExtension { - pub fn new(matrix_user_id: Option) -> Result { - Ok(PersonExtension { matrix_user_id }) - } -} - -impl UnparsedExtension for PersonExtension -where - U: UnparsedMutExt, -{ - type Error = serde_json::Error; - - fn try_from_unparsed(unparsed_mut: &mut U) -> Result { - Ok(PersonExtension { - matrix_user_id: unparsed_mut.remove("matrix_user_id")?, - }) - } - - fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> { - unparsed_mut.insert("matrix_user_id", self.matrix_user_id)?; - Ok(()) - } -} diff --git a/crates/apub/src/extensions/signatures.rs b/crates/apub/src/extensions/signatures.rs index be323d51b..8fded5152 100644 --- a/crates/apub/src/extensions/signatures.rs +++ b/crates/apub/src/extensions/signatures.rs @@ -1,5 +1,3 @@ -use activitystreams::unparsed::UnparsedMutExt; -use activitystreams_ext::UnparsedExtension; use actix_web::HttpRequest; use anyhow::anyhow; use http::{header::HeaderName, HeaderMap, HeaderValue}; @@ -90,15 +88,6 @@ pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), L } } -/// Extension for actor public key, which is needed on person and community for HTTP signatures. -/// -/// Taken from: https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct PublicKeyExtension { - pub public_key: PublicKey, -} - #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PublicKey { @@ -106,29 +95,3 @@ pub struct PublicKey { pub owner: Url, pub public_key_pem: String, } - -impl PublicKey { - pub fn to_ext(&self) -> PublicKeyExtension { - PublicKeyExtension { - public_key: self.to_owned(), - } - } -} - -impl UnparsedExtension for PublicKeyExtension -where - U: UnparsedMutExt, -{ - type Error = serde_json::Error; - - fn try_from_unparsed(unparsed_mut: &mut U) -> Result { - Ok(PublicKeyExtension { - public_key: unparsed_mut.remove("publicKey")?, - }) - } - - fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> { - unparsed_mut.insert("publicKey", self.public_key)?; - Ok(()) - } -} diff --git a/crates/apub/src/fetcher/community.rs b/crates/apub/src/fetcher/community.rs index 670493a98..ff94129be 100644 --- a/crates/apub/src/fetcher/community.rs +++ b/crates/apub/src/fetcher/community.rs @@ -6,13 +6,9 @@ use crate::{ person::get_or_fetch_and_upsert_person, should_refetch_actor, }, - objects::FromApub, - GroupExt, -}; -use activitystreams::{ - actor::ApActorExt, - collection::{CollectionExt, OrderedCollection}, + objects::{community::Group, FromApub}, }; +use activitystreams::collection::{CollectionExt, OrderedCollection}; use anyhow::Context; use diesel::result::Error::NotFound; use lemmy_api_common::blocking; @@ -63,7 +59,7 @@ async fn fetch_remote_community( old_community: Option, request_counter: &mut i32, ) -> Result { - let group = fetch_remote_object::(context.client(), apub_id, request_counter).await; + let group = fetch_remote_object::(context.client(), apub_id, request_counter).await; if let Some(c) = old_community.to_owned() { if is_deleted(&group) { @@ -78,22 +74,20 @@ async fn fetch_remote_community( } let group = group?; - let community = - Community::from_apub(&group, context, apub_id.to_owned(), request_counter, false).await?; + let community = Community::from_apub(&group, context, apub_id, request_counter).await?; update_community_mods(&group, &community, context, request_counter).await?; // only fetch outbox for new communities, otherwise this can create an infinite loop if old_community.is_none() { - let outbox = group.inner.outbox()?.context(location_info!())?; - fetch_community_outbox(context, outbox, request_counter).await? + fetch_community_outbox(context, &group.outbox, request_counter).await? } Ok(community) } async fn update_community_mods( - group: &GroupExt, + group: &Group, community: &Community, context: &LemmyContext, request_counter: &mut i32, @@ -168,10 +162,10 @@ async fn fetch_community_outbox( pub(crate) async fn fetch_community_mods( context: &LemmyContext, - group: &GroupExt, + group: &Group, recursion_counter: &mut i32, ) -> Result, LemmyError> { - if let Some(mods_url) = &group.ext_one.moderators { + if let Some(mods_url) = &group.moderators { let mods = fetch_remote_object::(context.client(), mods_url, recursion_counter) .await?; diff --git a/crates/apub/src/fetcher/objects.rs b/crates/apub/src/fetcher/objects.rs index a06b99d63..835bf63a3 100644 --- a/crates/apub/src/fetcher/objects.rs +++ b/crates/apub/src/fetcher/objects.rs @@ -34,14 +34,7 @@ pub async fn get_or_fetch_and_insert_post( debug!("Fetching and creating remote post: {}", post_ap_id); let page = fetch_remote_object::(context.client(), post_ap_id, recursion_counter).await?; - let post = Post::from_apub( - &page, - context, - post_ap_id.to_owned(), - recursion_counter, - false, - ) - .await?; + let post = Post::from_apub(&page, context, post_ap_id, recursion_counter).await?; Ok(post) } @@ -73,14 +66,7 @@ pub async fn get_or_fetch_and_insert_comment( ); let comment = fetch_remote_object::(context.client(), comment_ap_id, recursion_counter).await?; - let comment = Comment::from_apub( - &comment, - context, - comment_ap_id.to_owned(), - recursion_counter, - false, - ) - .await?; + let comment = Comment::from_apub(&comment, context, comment_ap_id, recursion_counter).await?; let post_id = comment.post_id; let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; diff --git a/crates/apub/src/fetcher/person.rs b/crates/apub/src/fetcher/person.rs index 4e6191abb..3acedb086 100644 --- a/crates/apub/src/fetcher/person.rs +++ b/crates/apub/src/fetcher/person.rs @@ -1,7 +1,6 @@ use crate::{ fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor}, - objects::FromApub, - PersonExt, + objects::{person::Person as ApubPerson, FromApub}, }; use anyhow::anyhow; use diesel::result::Error::NotFound; @@ -33,7 +32,7 @@ pub async fn get_or_fetch_and_upsert_person( Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => { debug!("Fetching and updating from remote person: {}", apub_id); let person = - fetch_remote_object::(context.client(), apub_id, recursion_counter).await; + fetch_remote_object::(context.client(), apub_id, recursion_counter).await; if is_deleted(&person) { // TODO: use Person::update_deleted() once implemented @@ -46,14 +45,7 @@ pub async fn get_or_fetch_and_upsert_person( return Ok(u); } - let person = Person::from_apub( - &person?, - context, - apub_id.to_owned(), - recursion_counter, - false, - ) - .await?; + let person = Person::from_apub(&person?, context, apub_id, recursion_counter).await?; let person_id = person.id; blocking(context.pool(), move |conn| { @@ -67,16 +59,9 @@ pub async fn get_or_fetch_and_upsert_person( Err(NotFound {}) => { debug!("Fetching and creating remote person: {}", apub_id); let person = - fetch_remote_object::(context.client(), apub_id, recursion_counter).await?; + fetch_remote_object::(context.client(), apub_id, recursion_counter).await?; - let person = Person::from_apub( - &person, - context, - apub_id.to_owned(), - recursion_counter, - false, - ) - .await?; + let person = Person::from_apub(&person, context, apub_id, recursion_counter).await?; Ok(person) } diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 647d057a6..2c9bebb02 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -6,13 +6,10 @@ use crate::{ is_deleted, }, find_object_by_id, - objects::{comment::Note, post::Page, FromApub}, - GroupExt, + objects::{comment::Note, community::Group, person::Person as ApubPerson, post::Page, FromApub}, Object, - PersonExt, }; -use activitystreams::base::BaseExt; -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use lemmy_api_common::{blocking, site::SearchResponse}; use lemmy_db_queries::{ source::{ @@ -42,8 +39,8 @@ use url::Url; #[derive(serde::Deserialize, Debug)] #[serde(untagged)] enum SearchAcceptedObjects { - Person(Box), - Group(Box), + Person(Box), + Group(Box), Page(Box), Comment(Box), } @@ -109,7 +106,6 @@ async fn build_response( recursion_counter: &mut i32, context: &LemmyContext, ) -> Result { - let domain = query_url.domain().context("url has no domain")?; let mut response = SearchResponse { type_: SearchType::All.to_string(), comments: vec![], @@ -120,9 +116,8 @@ async fn build_response( match fetch_response { SearchAcceptedObjects::Person(p) => { - let person_uri = p.inner.id(domain)?.context("person has no id")?; - - let person = get_or_fetch_and_upsert_person(person_uri, context, recursion_counter).await?; + let person_id = p.id(&query_url)?; + let person = get_or_fetch_and_upsert_person(person_id, context, recursion_counter).await?; response.users = vec![ blocking(context.pool(), move |conn| { @@ -132,8 +127,7 @@ async fn build_response( ]; } SearchAcceptedObjects::Group(g) => { - let community_uri = g.inner.id(domain)?.context("group has no id")?; - + let community_uri = g.id(&query_url)?; let community = get_or_fetch_and_upsert_community(community_uri, context, recursion_counter).await?; @@ -145,13 +139,13 @@ async fn build_response( ]; } SearchAcceptedObjects::Page(p) => { - let p = Post::from_apub(&p, context, query_url, recursion_counter, false).await?; + let p = Post::from_apub(&p, context, &query_url, recursion_counter).await?; response.posts = vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??]; } SearchAcceptedObjects::Comment(c) => { - let c = Comment::from_apub(&c, context, query_url, recursion_counter, false).await?; + let c = Comment::from_apub(&c, context, &query_url, recursion_counter).await?; response.comments = vec![ blocking(context.pool(), move |conn| { diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index 75f97aadd..950f11dc4 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -9,21 +9,8 @@ pub mod http; pub mod migrations; pub mod objects; -use crate::{ - extensions::{ - group_extension::GroupExtension, - person_extension::PersonExtension, - signatures::{PublicKey, PublicKeyExtension}, - }, - fetcher::community::get_or_fetch_and_upsert_community, -}; -use activitystreams::{ - activity::Follow, - actor, - base::AnyBase, - object::{ApObject, AsObject, ObjectExt}, -}; -use activitystreams_ext::Ext2; +use crate::extensions::signatures::PublicKey; +use activitystreams::base::AnyBase; use anyhow::{anyhow, Context}; use diesel::NotFound; use lemmy_api_common::blocking; @@ -47,20 +34,6 @@ use serde::Serialize; use std::net::IpAddr; use url::{ParseError, Url}; -/// Activitystreams type for community -pub type GroupExt = - Ext2>, GroupExtension, PublicKeyExtension>; -/// Activitystreams type for person -type PersonExt = - Ext2>>, PersonExtension, PublicKeyExtension>; -pub type SiteExt = actor::ApActor>; - -#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)] -pub enum UserTypes { - Person, - Service, -} - pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json"; /// Checks if the ID is allowed for sending or receiving. @@ -71,7 +44,10 @@ pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json"; /// - URL being in the allowlist (if it is active) /// - URL not being in the blocklist (if it is active) /// -pub fn check_is_apub_id_valid(apub_id: &Url, use_strict_allowlist: bool) -> Result<(), LemmyError> { +pub(crate) fn check_is_apub_id_valid( + apub_id: &Url, + use_strict_allowlist: bool, +) -> Result<(), LemmyError> { let settings = Settings::get(); let domain = apub_id.domain().context(location_info!())?.to_string(); let local_instance = settings.get_hostname_without_port()?; @@ -169,15 +145,12 @@ pub trait ActorType { Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?) } - fn get_public_key_ext(&self) -> Result { - Ok( - PublicKey { - id: format!("{}#main-key", self.actor_id()), - owner: self.actor_id(), - public_key_pem: self.public_key().context(location_info!())?, - } - .to_ext(), - ) + fn get_public_key(&self) -> Result { + Ok(PublicKey { + id: format!("{}#main-key", self.actor_id()), + owner: self.actor_id(), + public_key_pem: self.public_key().context(location_info!())?, + }) } } @@ -185,11 +158,6 @@ pub trait ActorType { pub trait CommunityType { fn followers_url(&self) -> Url; async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError>; - async fn send_accept_follow( - &self, - follow: Follow, - context: &LemmyContext, - ) -> Result<(), LemmyError>; async fn send_update(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>; async fn send_delete(&self, mod_: Person, context: &LemmyContext) -> Result<(), LemmyError>; @@ -232,20 +200,6 @@ pub trait CommunityType { ) -> Result<(), LemmyError>; } -#[async_trait::async_trait(?Send)] -pub trait UserType { - async fn send_follow( - &self, - follow_actor_id: &Url, - context: &LemmyContext, - ) -> Result<(), LemmyError>; - async fn send_unfollow( - &self, - follow_actor_id: &Url, - context: &LemmyContext, - ) -> Result<(), LemmyError>; -} - pub enum EndpointType { Community, Person, @@ -255,7 +209,7 @@ pub enum EndpointType { } /// Generates an apub endpoint for a given domain, IE xyz.tld -pub fn generate_apub_endpoint_for_domain( +pub(crate) fn generate_apub_endpoint_for_domain( endpoint_type: EndpointType, name: &str, domain: &str, @@ -332,7 +286,7 @@ pub fn build_actor_id_from_shortname( /// Store a sent or received activity in the database, for logging purposes. These records are not /// persistent. -pub async fn insert_activity( +pub(crate) async fn insert_activity( ap_id: &Url, activity: T, local: bool, @@ -368,7 +322,7 @@ impl PostOrComment { /// Tries to find a post or comment in the local database, without any network requests. /// This is used to handle deletions and removals, because in case we dont have the object, we can /// simply ignore the activity. -pub async fn find_post_or_comment_by_id( +pub(crate) async fn find_post_or_comment_by_id( context: &LemmyContext, apub_id: Url, ) -> Result { @@ -402,7 +356,10 @@ pub enum Object { PrivateMessage(Box), } -pub async fn find_object_by_id(context: &LemmyContext, apub_id: Url) -> Result { +pub(crate) async fn find_object_by_id( + context: &LemmyContext, + apub_id: Url, +) -> Result { let ap_id = apub_id.clone(); if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await { return Ok(match pc { @@ -440,7 +397,7 @@ pub async fn find_object_by_id(context: &LemmyContext, apub_id: Url) -> Result(activity: &T) -> Vec -where - T: AsObject, -{ - let mut to_and_cc = vec![]; - if let Some(to) = activity.to() { - let to = to.to_owned().unwrap_to_vec(); - let mut to = to - .iter() - .map(|t| t.as_xsd_any_uri()) - .flatten() - .map(|t| t.to_owned()) - .collect(); - to_and_cc.append(&mut to); - } - if let Some(cc) = activity.cc() { - let cc = cc.to_owned().unwrap_to_vec(); - let mut cc = cc - .iter() - .map(|c| c.as_xsd_any_uri()) - .flatten() - .map(|c| c.to_owned()) - .collect(); - to_and_cc.append(&mut cc); - } - to_and_cc -} - -pub async fn get_community_from_to_or_cc( - activity: &T, - context: &LemmyContext, - request_counter: &mut i32, -) -> Result -where - T: AsObject, -{ - for cid in get_activity_to_and_cc(activity) { - let community = get_or_fetch_and_upsert_community(&cid, context, request_counter).await; - if community.is_ok() { - return community; - } - } - Err(NotFound.into()) -} diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index d402613a0..1bebb39e9 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -41,16 +41,18 @@ use lemmy_utils::{ }; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use std::ops::Deref; use url::Url; +#[skip_serializing_none] #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Note { #[serde(rename = "@context")] context: OneOrMany, r#type: NoteType, - pub(crate) id: Url, + id: Url, pub(crate) attributed_to: Url, /// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain /// the community ID, as it would be incompatible with Pleroma (and we can get the community from @@ -67,6 +69,14 @@ pub struct Note { } impl Note { + pub(crate) fn id_unchecked(&self) -> &Url { + &self.id + } + pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { + verify_domains_match(&self.id, expected_domain)?; + Ok(&self.id) + } + async fn get_parents( &self, context: &LemmyContext, @@ -212,10 +222,10 @@ impl FromApub for Comment { async fn from_apub( note: &Note, context: &LemmyContext, - _expected_domain: Url, + expected_domain: &Url, request_counter: &mut i32, - _mod_action_allowed: bool, ) -> Result { + let ap_id = Some(note.id(expected_domain)?.clone().into()); let creator = get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?; let (post, parent_comment_id) = note.get_parents(context, request_counter).await?; @@ -233,7 +243,7 @@ impl FromApub for Comment { published: Some(note.published.naive_local()), updated: note.updated.map(|u| u.to_owned().naive_local()), deleted: None, - ap_id: Some(note.id.clone().into()), + ap_id, local: Some(false), }; Ok(blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??) diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index f9948588d..5269d7663 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -1,101 +1,157 @@ use crate::{ - extensions::{context::lemmy_context, group_extension::GroupExtension}, + extensions::{context::lemmy_context, signatures::PublicKey}, fetcher::community::fetch_community_mods, generate_moderators_url, - objects::{ - check_object_domain, - create_tombstone, - get_object_from_apub, - get_source_markdown_value, - set_content_and_source, - FromApub, - FromApubToForm, - ToApub, - }, + objects::{create_tombstone, FromApub, ImageObject, Source, ToApub}, ActorType, - GroupExt, }; use activitystreams::{ - actor::{kind::GroupType, ApActor, Endpoints, Group}, - base::BaseExt, - object::{ApObject, Image, Tombstone}, - prelude::*, + actor::{kind::GroupType, Endpoints}, + base::AnyBase, + object::{kind::ImageType, Tombstone}, + primitives::OneOrMany, + unparsed::Unparsed, }; -use activitystreams_ext::Ext2; -use anyhow::Context; +use chrono::{DateTime, FixedOffset}; use lemmy_api_common::blocking; -use lemmy_db_queries::DbPool; +use lemmy_apub_lib::{ + values::{MediaTypeHtml, MediaTypeMarkdown}, + verify_domains_match, +}; +use lemmy_db_queries::{ApubObject, DbPool}; use lemmy_db_schema::{ naive_now, source::community::{Community, CommunityForm}, }; -use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView; use lemmy_utils::{ - location_info, - utils::{check_slurs, check_slurs_opt, convert_datetime}, + utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html}, LemmyError, }; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use url::Url; +#[skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Group { + #[serde(rename = "@context")] + context: OneOrMany, + #[serde(rename = "type")] + kind: GroupType, + id: Url, + /// username, set at account creation and can never be changed + preferred_username: String, + /// title (can be changed at any time) + name: String, + content: Option, + media_type: Option, + source: Option, + icon: Option, + /// banner + image: Option, + // lemmy extension + sensitive: Option, + // lemmy extension + pub(crate) moderators: Option, + inbox: Url, + pub(crate) outbox: Url, + followers: Url, + endpoints: Endpoints, + public_key: PublicKey, + published: DateTime, + updated: Option>, + #[serde(flatten)] + unparsed: Unparsed, +} + +impl Group { + pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { + verify_domains_match(&self.id, expected_domain)?; + Ok(&self.id) + } + pub(crate) async fn from_apub_to_form( + group: &Group, + expected_domain: &Url, + ) -> Result { + let actor_id = Some(group.id(expected_domain)?.clone().into()); + let name = group.preferred_username.clone(); + let title = group.name.clone(); + let description = group.source.clone().map(|s| s.content); + let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into()); + + check_slurs(&name)?; + check_slurs(&title)?; + check_slurs_opt(&description)?; + + Ok(CommunityForm { + name, + title, + description, + removed: None, + published: Some(group.published.naive_local()), + updated: group.updated.map(|u| u.naive_local()), + deleted: None, + nsfw: Some(group.sensitive.unwrap_or(false)), + actor_id, + local: Some(false), + private_key: None, + public_key: Some(group.public_key.public_key_pem.clone()), + last_refreshed_at: Some(naive_now()), + icon: Some(group.icon.clone().map(|i| i.url.into())), + banner: Some(group.image.clone().map(|i| i.url.into())), + followers_url: Some(group.followers.clone().into()), + inbox_url: Some(group.inbox.clone().into()), + shared_inbox_url: Some(shared_inbox), + }) + } +} + #[async_trait::async_trait(?Send)] impl ToApub for Community { - type ApubType = GroupExt; + type ApubType = Group; - async fn to_apub(&self, pool: &DbPool) -> Result { - let id = self.id; - let moderators = blocking(pool, move |conn| { - CommunityModeratorView::for_community(conn, id) - }) - .await??; - let moderators: Vec = moderators - .into_iter() - .map(|m| m.moderator.actor_id.into_inner()) - .collect(); + async fn to_apub(&self, _pool: &DbPool) -> Result { + let source = self.description.clone().map(|bio| Source { + content: bio, + media_type: MediaTypeMarkdown::Markdown, + }); + let icon = self.icon.clone().map(|url| ImageObject { + kind: ImageType::Image, + url: url.into(), + }); + let image = self.banner.clone().map(|url| ImageObject { + kind: ImageType::Image, + url: url.into(), + }); - let mut group = ApObject::new(Group::new()); - group - .set_many_contexts(lemmy_context()) - .set_id(self.actor_id.to_owned().into()) - .set_name(self.title.to_owned()) - .set_published(convert_datetime(self.published)) - // NOTE: included attritubed_to field for compatibility with lemmy v0.9.9 - .set_many_attributed_tos(moderators); - - if let Some(u) = self.updated.to_owned() { - group.set_updated(convert_datetime(u)); - } - if let Some(d) = self.description.to_owned() { - set_content_and_source(&mut group, &d)?; - } - - if let Some(icon_url) = &self.icon { - let mut image = Image::new(); - image.set_url::(icon_url.to_owned().into()); - group.set_icon(image.into_any_base()?); - } - - if let Some(banner_url) = &self.banner { - let mut image = Image::new(); - image.set_url::(banner_url.to_owned().into()); - group.set_image(image.into_any_base()?); - } - - let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), group); - ap_actor - .set_preferred_username(self.name.to_owned()) - .set_outbox(self.get_outbox_url()?) - .set_followers(self.followers_url.clone().into()) - .set_endpoints(Endpoints { - shared_inbox: Some(self.get_shared_inbox_or_inbox_url()), + let group = Group { + context: lemmy_context(), + kind: GroupType::Group, + id: self.actor_id(), + preferred_username: self.name.clone(), + name: self.title.clone(), + content: self.description.as_ref().map(|b| markdown_to_html(b)), + media_type: self.description.as_ref().map(|_| MediaTypeHtml::Html), + source, + icon, + image, + sensitive: Some(self.nsfw), + moderators: Some(generate_moderators_url(&self.actor_id)?.into()), + inbox: self.inbox_url.clone().into(), + outbox: self.get_outbox_url()?, + followers: self.followers_url.clone().into(), + endpoints: Endpoints { + shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()), ..Default::default() - }); - - Ok(Ext2::new( - ap_actor, - GroupExtension::new(self.nsfw, generate_moderators_url(&self.actor_id)?.into())?, - self.get_public_key_ext()?, - )) + }, + public_key: self.get_public_key()?, + published: convert_datetime(self.published), + updated: self.updated.map(convert_datetime), + unparsed: Default::default(), + }; + Ok(group) } fn to_tombstone(&self) -> Result { @@ -110,116 +166,19 @@ impl ToApub for Community { #[async_trait::async_trait(?Send)] impl FromApub for Community { - type ApubType = GroupExt; + type ApubType = Group; /// Converts a `Group` to `Community`, inserts it into the database and updates moderators. async fn from_apub( - group: &GroupExt, + group: &Group, context: &LemmyContext, - expected_domain: Url, + expected_domain: &Url, request_counter: &mut i32, - mod_action_allowed: bool, ) -> Result { - get_object_from_apub( - group, - context, - expected_domain, - request_counter, - mod_action_allowed, - ) - .await - } -} - -#[async_trait::async_trait(?Send)] -impl FromApubToForm for CommunityForm { - async fn from_apub( - group: &GroupExt, - context: &LemmyContext, - expected_domain: Url, - request_counter: &mut i32, - _mod_action_allowed: bool, - ) -> Result { fetch_community_mods(context, group, request_counter).await?; + let form = Group::from_apub_to_form(group, expected_domain).await?; - let name = group - .inner - .preferred_username() - .context(location_info!())? - .to_string(); - let title = group - .inner - .name() - .context(location_info!())? - .as_one() - .context(location_info!())? - .as_xsd_string() - .context(location_info!())? - .to_string(); - - let description = get_source_markdown_value(group)?; - - check_slurs(&name)?; - check_slurs(&title)?; - check_slurs_opt(&description)?; - - let icon = match group.icon() { - Some(any_image) => Some( - Image::from_any_base(any_image.as_one().context(location_info!())?.clone()) - .context(location_info!())? - .context(location_info!())? - .url() - .context(location_info!())? - .as_single_xsd_any_uri() - .map(|u| u.to_owned().into()), - ), - None => None, - }; - let banner = match group.image() { - Some(any_image) => Some( - Image::from_any_base(any_image.as_one().context(location_info!())?.clone()) - .context(location_info!())? - .context(location_info!())? - .url() - .context(location_info!())? - .as_single_xsd_any_uri() - .map(|u| u.to_owned().into()), - ), - None => None, - }; - let shared_inbox = group - .inner - .endpoints()? - .map(|e| e.shared_inbox) - .flatten() - .map(|s| s.to_owned().into()); - - Ok(CommunityForm { - name, - title, - description, - removed: None, - published: group.inner.published().map(|u| u.to_owned().naive_local()), - updated: group.inner.updated().map(|u| u.to_owned().naive_local()), - deleted: None, - nsfw: Some(group.ext_one.sensitive.unwrap_or(false)), - actor_id: Some(check_object_domain(group, expected_domain, true)?), - local: Some(false), - private_key: None, - public_key: Some(group.ext_two.to_owned().public_key.public_key_pem), - last_refreshed_at: Some(naive_now()), - icon, - banner, - followers_url: Some( - group - .inner - .followers()? - .context(location_info!())? - .to_owned() - .into(), - ), - inbox_url: Some(group.inner.inbox()?.to_owned().into()), - shared_inbox_url: Some(shared_inbox), - }) + let community = blocking(context.pool(), move |conn| Community::upsert(conn, &form)).await??; + Ok(community) } } diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index 0a29f29a3..2114a1d89 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -1,22 +1,13 @@ -use crate::{check_is_apub_id_valid, fetcher::person::get_or_fetch_and_upsert_person}; +use crate::fetcher::person::get_or_fetch_and_upsert_person; use activitystreams::{ - base::{AsBase, BaseExt, ExtendsExt}, - markers::Base, - mime::{FromStrError, Mime}, - object::{ApObjectExt, Object, ObjectExt, Tombstone, TombstoneExt}, + base::BaseExt, + object::{kind::ImageType, Tombstone, TombstoneExt}, }; -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use chrono::NaiveDateTime; -use lemmy_api_common::blocking; use lemmy_apub_lib::values::MediaTypeMarkdown; -use lemmy_db_queries::{ApubObject, Crud, DbPool}; -use lemmy_db_schema::DbUrl; -use lemmy_utils::{ - location_info, - settings::structs::Settings, - utils::{convert_datetime, markdown_to_html}, - LemmyError, -}; +use lemmy_db_queries::DbPool; +use lemmy_utils::{utils::convert_datetime, LemmyError}; use lemmy_websocket::LemmyContext; use url::Url; @@ -46,22 +37,8 @@ pub trait FromApub { async fn from_apub( apub: &Self::ApubType, context: &LemmyContext, - expected_domain: Url, + expected_domain: &Url, request_counter: &mut i32, - mod_action_allowed: bool, - ) -> Result - where - Self: Sized; -} - -#[async_trait::async_trait(?Send)] -pub trait FromApubToForm { - async fn from_apub( - apub: &ApubType, - context: &LemmyContext, - expected_domain: Url, - request_counter: &mut i32, - mod_action_allowed: bool, ) -> Result where Self: Sized; @@ -74,6 +51,14 @@ pub struct Source { media_type: MediaTypeMarkdown, } +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ImageObject { + #[serde(rename = "type")] + kind: ImageType, + url: Url, +} + /// Updated is actually the deletion time fn create_tombstone( deleted: bool, @@ -98,120 +83,3 @@ where Err(anyhow!("Cant convert object to tombstone if it wasnt deleted").into()) } } - -pub(in crate::objects) fn check_object_domain( - apub: &T, - expected_domain: Url, - use_strict_allowlist: bool, -) -> Result -where - T: Base + AsBase, -{ - let domain = expected_domain.domain().context(location_info!())?; - let object_id = apub.id(domain)?.context(location_info!())?; - check_is_apub_id_valid(object_id, use_strict_allowlist)?; - Ok(object_id.to_owned().into()) -} - -pub(in crate::objects) fn set_content_and_source( - object: &mut T, - markdown_text: &str, -) -> Result<(), LemmyError> -where - T: ApObjectExt + ObjectExt + AsBase, -{ - let mut source = Object::<()>::new_none_type(); - source - .set_content(markdown_text) - .set_media_type(mime_markdown()?); - object.set_source(source.into_any_base()?); - - object.set_content(markdown_to_html(markdown_text)); - object.set_media_type(mime_html()?); - Ok(()) -} - -pub(in crate::objects) fn get_source_markdown_value( - object: &T, -) -> Result, LemmyError> -where - T: ApObjectExt + ObjectExt + AsBase, -{ - let content = object - .content() - .map(|s| s.as_single_xsd_string().map(|s2| s2.to_string())) - .flatten(); - if content.is_some() { - let source = object.source().context(location_info!())?; - let source = Object::<()>::from_any_base(source.to_owned())?.context(location_info!())?; - check_is_markdown(source.media_type())?; - let source_content = source - .content() - .map(|s| s.as_single_xsd_string().map(|s2| s2.to_string())) - .flatten() - .context(location_info!())?; - return Ok(Some(source_content)); - } - Ok(None) -} - -fn mime_markdown() -> Result { - "text/markdown".parse() -} - -fn mime_html() -> Result { - "text/html".parse() -} - -pub(in crate::objects) fn check_is_markdown(mime: Option<&Mime>) -> Result<(), LemmyError> { - let mime = mime.context(location_info!())?; - if !mime.eq(&mime_markdown()?) { - Err(LemmyError::from(anyhow!( - "Lemmy only supports markdown content" - ))) - } else { - Ok(()) - } -} - -/// Converts an ActivityPub object (eg `Note`) to a database object (eg `Comment`). If an object -/// with the same ActivityPub ID already exists in the database, it is returned directly. Otherwise -/// the apub object is parsed, inserted and returned. -pub async fn get_object_from_apub( - from: &From, - context: &LemmyContext, - expected_domain: Url, - request_counter: &mut i32, - is_mod_action: bool, -) -> Result -where - From: BaseExt, - To: ApubObject + Crud + Send + 'static, - ToForm: FromApubToForm + Send + 'static, -{ - let object_id = from.id_unchecked().context(location_info!())?.to_owned(); - let domain = object_id.domain().context(location_info!())?; - - // if its a local object, return it directly from the database - if Settings::get().hostname == domain { - let object = blocking(context.pool(), move |conn| { - To::read_from_apub_id(conn, &object_id.into()) - }) - .await??; - Ok(object) - } - // otherwise parse and insert, assuring that it comes from the right domain - else { - let to_form = ToForm::from_apub( - from, - context, - expected_domain, - request_counter, - is_mod_action, - ) - .await?; - - let to = blocking(context.pool(), move |conn| To::upsert(conn, &to_form)).await??; - Ok(to) - } -} diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index cbc80b505..ec14a943e 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -1,95 +1,129 @@ use crate::{ - extensions::{context::lemmy_context, person_extension::PersonExtension}, - objects::{ - check_object_domain, - get_source_markdown_value, - set_content_and_source, - FromApub, - FromApubToForm, - ToApub, - }, + check_is_apub_id_valid, + extensions::{context::lemmy_context, signatures::PublicKey}, + objects::{FromApub, ImageObject, Source, ToApub}, ActorType, - PersonExt, - UserTypes, }; use activitystreams::{ - actor::{Actor, ApActor, ApActorExt, Endpoints}, - base::{BaseExt, ExtendsExt}, - object::{ApObject, Image, Object, ObjectExt, Tombstone}, + actor::Endpoints, + base::AnyBase, + chrono::{DateTime, FixedOffset}, + object::{kind::ImageType, Tombstone}, + primitives::OneOrMany, + unparsed::Unparsed, }; -use activitystreams_ext::Ext2; -use anyhow::Context; use lemmy_api_common::blocking; +use lemmy_apub_lib::{ + values::{MediaTypeHtml, MediaTypeMarkdown}, + verify_domains_match, +}; use lemmy_db_queries::{ApubObject, DbPool}; use lemmy_db_schema::{ naive_now, source::person::{Person as DbPerson, PersonForm}, }; use lemmy_utils::{ - location_info, - settings::structs::Settings, - utils::{check_slurs, check_slurs_opt, convert_datetime}, + utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html}, LemmyError, }; use lemmy_websocket::LemmyContext; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use url::Url; +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] +pub enum UserTypes { + Person, + Service, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Person { + #[serde(rename = "@context")] + context: OneOrMany, + #[serde(rename = "type")] + kind: UserTypes, + id: Url, + /// username, set at account creation and can never be changed + preferred_username: String, + /// displayname (can be changed at any time) + name: Option, + content: Option, + media_type: Option, + source: Option, + /// user avatar + icon: Option, + /// user banner + image: Option, + matrix_user_id: Option, + inbox: Url, + /// mandatory field in activitypub, currently empty in lemmy + outbox: Url, + endpoints: Endpoints, + public_key: PublicKey, + published: DateTime, + updated: Option>, + #[serde(flatten)] + unparsed: Unparsed, +} + +// TODO: can generate this with a derive macro +impl Person { + pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { + verify_domains_match(&self.id, expected_domain)?; + Ok(&self.id) + } +} + #[async_trait::async_trait(?Send)] impl ToApub for DbPerson { - type ApubType = PersonExt; + type ApubType = Person; - async fn to_apub(&self, _pool: &DbPool) -> Result { - let object = Object::::new_none_type(); - let mut actor = Actor(object); + async fn to_apub(&self, _pool: &DbPool) -> Result { let kind = if self.bot_account { UserTypes::Service } else { UserTypes::Person }; - actor.set_kind(kind); - let mut person = ApObject::new(actor); + let source = self.bio.clone().map(|bio| Source { + content: bio, + media_type: MediaTypeMarkdown::Markdown, + }); + let icon = self.avatar.clone().map(|url| ImageObject { + kind: ImageType::Image, + url: url.into(), + }); + let image = self.banner.clone().map(|url| ImageObject { + kind: ImageType::Image, + url: url.into(), + }); - person - .set_many_contexts(lemmy_context()) - .set_id(self.actor_id.to_owned().into_inner()) - .set_published(convert_datetime(self.published)); - - if let Some(u) = self.updated { - person.set_updated(convert_datetime(u)); - } - - if let Some(avatar_url) = &self.avatar { - let mut image = Image::new(); - image.set_url::(avatar_url.to_owned().into()); - person.set_icon(image.into_any_base()?); - } - - if let Some(banner_url) = &self.banner { - let mut image = Image::new(); - image.set_url::(banner_url.to_owned().into()); - person.set_image(image.into_any_base()?); - } - - if let Some(bio) = &self.bio { - set_content_and_source(&mut person, bio)?; - } - - // In apub, the "name" is a display name - if let Some(i) = self.display_name.to_owned() { - person.set_name(i); - } - - let mut ap_actor = ApActor::new(self.inbox_url.clone().into(), person); - ap_actor - .set_preferred_username(self.name.to_owned()) - .set_outbox(self.get_outbox_url()?) - .set_endpoints(Endpoints { - shared_inbox: Some(self.get_shared_inbox_or_inbox_url()), + let person = Person { + context: lemmy_context(), + kind, + id: self.actor_id.to_owned().into_inner(), + preferred_username: self.name.clone(), + name: self.display_name.clone(), + content: self.bio.as_ref().map(|b| markdown_to_html(b)), + media_type: self.bio.as_ref().map(|_| MediaTypeHtml::Html), + source, + icon, + image, + matrix_user_id: self.matrix_user_id.clone(), + published: convert_datetime(self.published), + outbox: self.get_outbox_url()?, + endpoints: Endpoints { + shared_inbox: self.shared_inbox_url.clone().map(|s| s.into()), ..Default::default() - }); - - let person_ext = PersonExtension::new(self.matrix_user_id.to_owned())?; - Ok(Ext2::new(ap_actor, person_ext, self.get_public_key_ext()?)) + }, + public_key: self.get_public_key()?, + updated: self.updated.map(convert_datetime), + unparsed: Default::default(), + inbox: self.inbox_url.clone().into(), + }; + Ok(person) } fn to_tombstone(&self) -> Result { unimplemented!() @@ -98,118 +132,54 @@ impl ToApub for DbPerson { #[async_trait::async_trait(?Send)] impl FromApub for DbPerson { - type ApubType = PersonExt; + type ApubType = Person; async fn from_apub( - person: &PersonExt, + person: &Person, context: &LemmyContext, - expected_domain: Url, - request_counter: &mut i32, - mod_action_allowed: bool, - ) -> Result { - let person_id = person.id_unchecked().context(location_info!())?.to_owned(); - let domain = person_id.domain().context(location_info!())?; - if domain == Settings::get().hostname { - let person = blocking(context.pool(), move |conn| { - DbPerson::read_from_apub_id(conn, &person_id.into()) - }) - .await??; - Ok(person) - } else { - let person_form = PersonForm::from_apub( - person, - context, - expected_domain, - request_counter, - mod_action_allowed, - ) - .await?; - let person = blocking(context.pool(), move |conn| { - DbPerson::upsert(conn, &person_form) - }) - .await??; - Ok(person) - } - } -} - -#[async_trait::async_trait(?Send)] -impl FromApubToForm for PersonForm { - async fn from_apub( - person: &PersonExt, - _context: &LemmyContext, - expected_domain: Url, + expected_domain: &Url, _request_counter: &mut i32, - _mod_action_allowed: bool, - ) -> Result { - let avatar = match person.icon() { - Some(any_image) => Some( - Image::from_any_base(any_image.as_one().context(location_info!())?.clone())? - .context(location_info!())? - .url() - .context(location_info!())? - .as_single_xsd_any_uri() - .map(|url| url.to_owned()), - ), - None => None, + ) -> Result { + let actor_id = Some(person.id(expected_domain)?.clone().into()); + let name = person.preferred_username.clone(); + let display_name: Option = person.name.clone(); + let bio = person.source.clone().map(|s| s.content); + let shared_inbox = person.endpoints.shared_inbox.clone().map(|s| s.into()); + let bot_account = match person.kind { + UserTypes::Person => false, + UserTypes::Service => true, }; - let banner = match person.image() { - Some(any_image) => Some( - Image::from_any_base(any_image.as_one().context(location_info!())?.clone()) - .context(location_info!())? - .context(location_info!())? - .url() - .context(location_info!())? - .as_single_xsd_any_uri() - .map(|url| url.to_owned()), - ), - None => None, - }; - - let name: String = person - .inner - .preferred_username() - .context(location_info!())? - .to_string(); - let display_name: Option = person - .name() - .map(|n| n.one()) - .flatten() - .map(|n| n.to_owned().xsd_string()) - .flatten(); - let bio = get_source_markdown_value(person)?; - let shared_inbox = person - .inner - .endpoints()? - .map(|e| e.shared_inbox) - .flatten() - .map(|s| s.to_owned().into()); - check_slurs(&name)?; check_slurs_opt(&display_name)?; check_slurs_opt(&bio)?; + check_is_apub_id_valid(&person.id, false)?; - Ok(PersonForm { + let person_form = PersonForm { name, display_name: Some(display_name), banned: None, deleted: None, - avatar: avatar.map(|o| o.map(|i| i.into())), - banner: banner.map(|o| o.map(|i| i.into())), - published: person.inner.published().map(|u| u.to_owned().naive_local()), - updated: person.updated().map(|u| u.to_owned().naive_local()), - actor_id: Some(check_object_domain(person, expected_domain, false)?), + avatar: Some(person.icon.clone().map(|i| i.url.into())), + banner: Some(person.image.clone().map(|i| i.url.into())), + published: Some(person.published.naive_local()), + updated: person.updated.map(|u| u.clone().naive_local()), + actor_id, bio: Some(bio), local: Some(false), admin: Some(false), - bot_account: Some(person.inner.is_kind(&UserTypes::Service)), + bot_account: Some(bot_account), private_key: None, - public_key: Some(Some(person.ext_two.public_key.to_owned().public_key_pem)), + public_key: Some(Some(person.public_key.public_key_pem.clone())), last_refreshed_at: Some(naive_now()), - inbox_url: Some(person.inner.inbox()?.to_owned().into()), + inbox_url: Some(person.inbox.to_owned().into()), shared_inbox_url: Some(shared_inbox), - matrix_user_id: Some(person.ext_one.matrix_user_id.to_owned()), + matrix_user_id: Some(person.matrix_user_id.clone()), + }; + let person = blocking(context.pool(), move |conn| { + DbPerson::upsert(conn, &person_form) }) + .await??; + Ok(person) } } diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 2fdf8d67b..43a019d16 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -2,7 +2,7 @@ use crate::{ activities::{extract_community, verify_person_in_community}, extensions::context::lemmy_context, fetcher::person::get_or_fetch_and_upsert_person, - objects::{create_tombstone, FromApub, Source, ToApub}, + objects::{create_tombstone, FromApub, ImageObject, Source, ToApub}, ActorType, }; use activitystreams::{ @@ -37,15 +37,17 @@ use lemmy_utils::{ }; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use url::Url; +#[skip_serializing_none] #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Page { #[serde(rename = "@context")] context: OneOrMany, r#type: PageType, - pub(crate) id: Url, + id: Url, pub(crate) attributed_to: Url, to: [Url; 2], name: String, @@ -63,14 +65,15 @@ pub struct Page { unparsed: Unparsed, } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ImageObject { - content: ImageType, - url: Url, -} - impl Page { + pub(crate) fn id_unchecked(&self) -> &Url { + &self.id + } + pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { + verify_domains_match(&self.id, expected_domain)?; + Ok(&self.id) + } + /// Only mods can change the post's stickied/locked status. So if either of these is changed from /// the current value, it is a mod action and needs to be verified as such. /// @@ -126,7 +129,7 @@ impl ToApub for Post { media_type: MediaTypeMarkdown::Markdown, }); let image = self.thumbnail_url.clone().map(|thumb| ImageObject { - content: ImageType::Image, + kind: ImageType::Image, url: thumb.into(), }); @@ -169,10 +172,17 @@ impl FromApub for Post { async fn from_apub( page: &Page, context: &LemmyContext, - _expected_domain: Url, + expected_domain: &Url, request_counter: &mut i32, - _mod_action_allowed: bool, ) -> Result { + // We can't verify the domain in case of mod action, because the mod may be on a different + // instance from the post author. + let ap_id = if page.is_mod_action(context.pool()).await? { + page.id_unchecked() + } else { + page.id(expected_domain)? + }; + let ap_id = Some(ap_id.clone().into()); let creator = get_or_fetch_and_upsert_person(&page.attributed_to, context, request_counter).await?; let community = extract_community(&page.to, context, request_counter).await?; @@ -205,7 +215,7 @@ impl FromApub for Post { embed_description, embed_html, thumbnail_url: pictrs_thumbnail.map(|u| u.into()), - ap_id: Some(page.id.clone().into()), + ap_id, local: Some(false), }; Ok(blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??) diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 2b284a2da..02cf12eba 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -24,15 +24,17 @@ use lemmy_db_schema::source::{ use lemmy_utils::{utils::convert_datetime, LemmyError}; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use url::Url; +#[skip_serializing_none] #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Note { #[serde(rename = "@context")] context: OneOrMany, r#type: NoteType, - pub(crate) id: Url, + id: Url, pub(crate) attributed_to: Url, to: Url, content: String, @@ -45,6 +47,14 @@ pub struct Note { } impl Note { + pub(crate) fn id_unchecked(&self) -> &Url { + &self.id + } + pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> { + verify_domains_match(&self.id, expected_domain)?; + Ok(&self.id) + } + pub(crate) async fn verify( &self, context: &LemmyContext, @@ -107,10 +117,10 @@ impl FromApub for PrivateMessage { async fn from_apub( note: &Note, context: &LemmyContext, - _expected_domain: Url, + expected_domain: &Url, request_counter: &mut i32, - _mod_action_allowed: bool, ) -> Result { + let ap_id = Some(note.id(expected_domain)?.clone().into()); let creator = get_or_fetch_and_upsert_person(¬e.attributed_to, context, request_counter).await?; let recipient = get_or_fetch_and_upsert_person(¬e.to, context, request_counter).await?; @@ -123,7 +133,7 @@ impl FromApub for PrivateMessage { updated: note.updated.map(|u| u.to_owned().naive_local()), deleted: None, read: None, - ap_id: Some(note.id.clone().into()), + ap_id, local: Some(false), }; Ok( diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index 13fc4025d..df73d5e8f 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -39,7 +39,7 @@ pub trait ActivityHandler { ) -> Result<(), LemmyError>; async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError>; diff --git a/crates/apub_lib_derive/src/lib.rs b/crates/apub_lib_derive/src/lib.rs index f8750680a..d35454a37 100644 --- a/crates/apub_lib_derive/src/lib.rs +++ b/crates/apub_lib_derive/src/lib.rs @@ -92,7 +92,7 @@ pub fn derive_activity_handler(input: proc_macro::TokenStream) -> proc_macro::To } } async fn receive( - &self, + self, context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> {