diff --git a/.gitignore b/.gitignore index ec5ebb2345..aa79760566 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ docker/*.sql.xz .idea .vscode +# macOS +.DS_Store +**/.DS_Store + # local build files target env_setup.sh diff --git a/Cargo.lock b/Cargo.lock index 78f5e41450..180c3c87c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3528,6 +3528,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "sha2", "strum 0.28.0", "tracing", "url", diff --git a/crates/apub/activities/Cargo.toml b/crates/apub/activities/Cargo.toml index b43b0e038a..b19055d68b 100644 --- a/crates/apub/activities/Cargo.toml +++ b/crates/apub/activities/Cargo.toml @@ -50,6 +50,7 @@ serde_with.workspace = true enum_delegate = "0.2.0" either = { workspace = true } lemmy_diesel_utils = { workspace = true } +sha2 = "0.10.9" [dev-dependencies] diff --git a/crates/apub/activities/src/block/block_user.rs b/crates/apub/activities/src/block/block_user.rs index cc16b0a438..2fa50a76a9 100644 --- a/crates/apub/activities/src/block/block_user.rs +++ b/crates/apub/activities/src/block/block_user.rs @@ -5,7 +5,7 @@ use crate::{ block::{SiteOrCommunity, generate_cc}, check_community_deleted_or_removed, community::send_activity_in_community, - generate_activity_id, + generate_activity_id_with_object_id, protocol::block::block_user::BlockUser, send_lemmy_activity, }; @@ -57,7 +57,7 @@ impl BlockUser { kind: BlockType::Block, remove_data, summary: Some(reason), - id: generate_activity_id(BlockType::Block, context)?, + id: generate_activity_id_with_object_id(BlockType::Block, context)?, end_time: expires, audience: target.as_ref().right().map(|c| c.ap_id.clone().into()), }) diff --git a/crates/apub/activities/src/block/undo_block_user.rs b/crates/apub/activities/src/block/undo_block_user.rs index 4c7b319073..8060e75b04 100644 --- a/crates/apub/activities/src/block/undo_block_user.rs +++ b/crates/apub/activities/src/block/undo_block_user.rs @@ -4,7 +4,7 @@ use crate::{ activity_lists::AnnouncableActivities, block::{SiteOrCommunity, generate_cc}, community::send_activity_in_community, - generate_activity_id, + generate_activity_id_with_object_id, protocol::block::{block_user::BlockUser, undo_block_user::UndoBlockUser}, send_lemmy_activity, }; @@ -47,7 +47,7 @@ impl UndoBlockUser { let block = BlockUser::new(target, user, mod_, None, reason, None, context).await?; let to = to(target)?; - let id = generate_activity_id(UndoType::Undo, context)?; + let id = generate_activity_id_with_object_id(UndoType::Undo, context)?; let undo = UndoBlockUser { actor: mod_.id().clone().into(), to, diff --git a/crates/apub/activities/src/community/announce.rs b/crates/apub/activities/src/community/announce.rs index b61c8311d1..ca5ce8fbb1 100644 --- a/crates/apub/activities/src/community/announce.rs +++ b/crates/apub/activities/src/community/announce.rs @@ -1,6 +1,6 @@ use crate::{ activity_lists::AnnouncableActivities, - generate_activity_id, + generate_activity_id_with_object_id, generate_announce_activity_id, protocol::{ IdOrNestedObject, @@ -83,6 +83,7 @@ impl AnnounceActivity { pub fn new( object: RawAnnouncableActivities, community: &ApubCommunity, + object_id: Option<&Url>, context: &Data, ) -> LemmyResult { let inner_kind = object @@ -90,8 +91,11 @@ impl AnnounceActivity { .get("type") .and_then(serde_json::Value::as_str) .unwrap_or("other"); - let id = - generate_announce_activity_id(inner_kind, &context.settings().get_protocol_and_hostname())?; + let id = generate_announce_activity_id( + inner_kind, + &context.settings().get_protocol_and_hostname(), + object_id, + )?; Ok(AnnounceActivity { actor: community.id().clone().into(), to: generate_to(community)?, @@ -112,7 +116,7 @@ impl AnnounceActivity { community: &ApubCommunity, context: &Data, ) -> LemmyResult<()> { - let announce = AnnounceActivity::new(object.clone(), community, context)?; + let announce = AnnounceActivity::new(object.clone(), community, None, context)?; let inboxes = ActivitySendTargets::to_local_community_followers(community.id); send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?; @@ -123,14 +127,14 @@ impl AnnounceActivity { // Hack: need to convert Page into a format which can be sent as activity, which requires // adding actor field. let announcable_page = RawAnnouncableActivities { - id: generate_activity_id(AnnounceType::Announce, context)?, + id: generate_activity_id_with_object_id(AnnounceType::Announce, context)?, actor: c.actor.clone().into_inner(), other: serde_json::to_value(c.object)? .as_object() .ok_or(UntranslatedError::Unreachable)? .clone(), }; - let announce_compat = AnnounceActivity::new(announcable_page, community, context)?; + let announce_compat = AnnounceActivity::new(announcable_page, community, None, context)?; send_lemmy_activity(context, announce_compat, community, inboxes, false).await?; } Ok(()) diff --git a/crates/apub/activities/src/community/collection_add.rs b/crates/apub/activities/src/community/collection_add.rs index e9618d8c5d..81dd3d19a3 100644 --- a/crates/apub/activities/src/community/collection_add.rs +++ b/crates/apub/activities/src/community/collection_add.rs @@ -2,7 +2,7 @@ use crate::{ activity_lists::AnnouncableActivities, check_community_deleted_or_removed, community::send_activity_in_community, - generate_activity_id, + generate_activity_id_with_object_id, protocol::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove}, }; use activitypub_federation::{ @@ -46,7 +46,7 @@ impl CollectionAdd { actor: &ApubPerson, context: &Data, ) -> LemmyResult<()> { - let id = generate_activity_id(AddType::Add, context)?; + let id = generate_activity_id_with_object_id(AddType::Add, context)?; let add = CollectionAdd { actor: actor.id().clone().into(), to: generate_to(community)?, @@ -69,7 +69,7 @@ impl CollectionAdd { actor: &ApubPerson, context: &Data, ) -> LemmyResult<()> { - let id = generate_activity_id(AddType::Add, context)?; + let id = generate_activity_id_with_object_id(AddType::Add, context)?; let add = CollectionAdd { actor: actor.id().clone().into(), to: generate_to(community)?, diff --git a/crates/apub/activities/src/community/collection_remove.rs b/crates/apub/activities/src/community/collection_remove.rs index 1c3069f710..f3308f2c21 100644 --- a/crates/apub/activities/src/community/collection_remove.rs +++ b/crates/apub/activities/src/community/collection_remove.rs @@ -2,7 +2,7 @@ use crate::{ activity_lists::AnnouncableActivities, check_community_deleted_or_removed, community::send_activity_in_community, - generate_activity_id, + generate_activity_id_with_object_id, protocol::community::collection_remove::CollectionRemove, }; use activitypub_federation::{ @@ -44,7 +44,7 @@ impl CollectionRemove { actor: &ApubPerson, context: &Data, ) -> LemmyResult<()> { - let id = generate_activity_id(RemoveType::Remove, context)?; + let id = generate_activity_id_with_object_id(RemoveType::Remove, context)?; let remove = CollectionRemove { actor: actor.id().clone().into(), to: generate_to(community)?, @@ -67,7 +67,7 @@ impl CollectionRemove { actor: &ApubPerson, context: &Data, ) -> LemmyResult<()> { - let id = generate_activity_id(RemoveType::Remove, context)?; + let id = generate_activity_id_with_object_id(RemoveType::Remove, context)?; let remove = CollectionRemove { actor: actor.id().clone().into(), to: generate_to(community)?, diff --git a/crates/apub/activities/src/community/lock.rs b/crates/apub/activities/src/community/lock.rs index 85b3fa21e1..00aa7f4a31 100644 --- a/crates/apub/activities/src/community/lock.rs +++ b/crates/apub/activities/src/community/lock.rs @@ -3,7 +3,7 @@ use crate::{ activity_lists::AnnouncableActivities, check_community_deleted_or_removed, community::send_activity_in_community, - generate_activity_id, + generate_activity_id_with_object_id, post_or_comment_community, protocol::community::lock::{LockPageOrNote, LockType, UndoLockPageOrNote}, }; @@ -155,7 +155,7 @@ pub(crate) async fn send_lock( context: Data, ) -> LemmyResult<()> { let community: ApubCommunity = post_or_comment_community(&object, &context).await?.into(); - let id = generate_activity_id(LockType::Lock, &context)?; + let id = generate_activity_id_with_object_id(LockType::Lock, &context)?; let community_id = community.ap_id.inner().clone(); let ap_id = match object { PostOrComment::Left(p) => p.ap_id.clone(), @@ -175,7 +175,7 @@ pub(crate) async fn send_lock( let activity = if locked { AnnouncableActivities::Lock(lock) } else { - let id = generate_activity_id(UndoType::Undo, &context)?; + let id = generate_activity_id_with_object_id(UndoType::Undo, &context)?; let undo = UndoLockPageOrNote { actor: lock.actor.clone(), to: generate_to(&community)?, diff --git a/crates/apub/activities/src/community/report.rs b/crates/apub/activities/src/community/report.rs index b6592561bb..d8e9ffa0d9 100644 --- a/crates/apub/activities/src/community/report.rs +++ b/crates/apub/activities/src/community/report.rs @@ -2,7 +2,7 @@ use super::{local_community, report_inboxes}; use crate::{ activity_lists::AnnouncableActivities, check_community_deleted_or_removed, - generate_activity_id, + generate_activity_id_with_object_id, protocol::community::{ announce::AnnounceActivity, report::{Report, ReportObject}, @@ -57,7 +57,7 @@ impl Report { context: &Data, ) -> LemmyResult { let kind = FlagType::Flag; - let id = generate_activity_id(kind.clone(), context)?; + let id = generate_activity_id_with_object_id(kind.clone(), context)?; Ok(Report { actor: actor.id().clone().into(), to: [receiver.id().clone().into()], @@ -172,7 +172,7 @@ impl Activity for Report { // forward to remote mods let object_id = self.object.object_id(context).await?; let announce = AnnouncableActivities::Report(self); - let announce = AnnounceActivity::new(announce.try_into()?, community, context)?; + let announce = AnnounceActivity::new(announce.try_into()?, community, None, context)?; let inboxes = report_inboxes(object_id, &receiver, &actor, context).await?; send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?; } diff --git a/crates/apub/activities/src/community/resolve_report.rs b/crates/apub/activities/src/community/resolve_report.rs index 119c6fb41d..dd6c219131 100644 --- a/crates/apub/activities/src/community/resolve_report.rs +++ b/crates/apub/activities/src/community/resolve_report.rs @@ -1,7 +1,7 @@ use super::{local_community, report_inboxes, verify_mod_or_admin_action}; use crate::{ activity_lists::AnnouncableActivities, - generate_activity_id, + generate_activity_id_with_object_id, protocol::community::{ announce::AnnounceActivity, report::Report, @@ -47,7 +47,7 @@ impl ResolveReport { context: Data, ) -> LemmyResult<()> { let kind = ResolveType::Resolve; - let id = generate_activity_id(kind.clone(), &context)?; + let id = generate_activity_id_with_object_id(kind.clone(), &context)?; let object = Report::new(&object_id, report_creator, receiver, None, &context)?; let resolve = ResolveReport { actor: actor.id().clone().into(), @@ -106,7 +106,7 @@ impl Activity for ResolveReport { // forward to remote mods let object_id = self.object.object.object_id(context).await?; let announce = AnnouncableActivities::ResolveReport(self); - let announce = AnnounceActivity::new(announce.try_into()?, community, context)?; + let announce = AnnounceActivity::new(announce.try_into()?, community, None, context)?; let inboxes = report_inboxes(object_id, &receiver, &reporter, context).await?; send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?; } diff --git a/crates/apub/activities/src/community/update.rs b/crates/apub/activities/src/community/update.rs index 434f3526dd..f59f62bf22 100644 --- a/crates/apub/activities/src/community/update.rs +++ b/crates/apub/activities/src/community/update.rs @@ -1,7 +1,7 @@ use crate::{ check_community_deleted_or_removed, community::{AnnouncableActivities, send_activity_in_community}, - generate_activity_id, + generate_activity_id_with_object_id, protocol::community::update::Update, send_lemmy_activity, }; @@ -36,7 +36,7 @@ pub(crate) async fn send_update_community( ) -> LemmyResult<()> { let community: ApubCommunity = community.into(); let actor: ApubPerson = actor.into(); - let id = generate_activity_id(UpdateType::Update, &context)?; + let id = generate_activity_id_with_object_id(UpdateType::Update, &context)?; let update = Update { actor: actor.id().clone().into(), to: generate_to(&community)?, @@ -66,7 +66,7 @@ pub(crate) async fn send_update_multi_community( ) -> LemmyResult<()> { let multi: ApubMultiCommunity = multi.into(); let actor: ApubPerson = actor.into(); - let id = generate_activity_id(UpdateType::Update, &context)?; + let id = generate_activity_id_with_object_id(UpdateType::Update, &context)?; let update = Update { actor: actor.id().clone().into(), to: vec![multi.ap_id.clone().into(), public()], diff --git a/crates/apub/activities/src/create_or_update/comment.rs b/crates/apub/activities/src/create_or_update/comment.rs index f307100ee1..3a131fe5ed 100644 --- a/crates/apub/activities/src/create_or_update/comment.rs +++ b/crates/apub/activities/src/create_or_update/comment.rs @@ -55,7 +55,10 @@ impl CreateOrUpdateNote { .await? .into(); - let id = generate_activity_id(kind.clone(), &context)?; + // get object_id + let object_id = comment.activity_object_id(); + + let id = generate_activity_id(kind.clone(), Some(&object_id), &context)?; let note = ApubComment(comment).into_json(&context).await?; let create_or_update = CreateOrUpdateNote { diff --git a/crates/apub/activities/src/create_or_update/post.rs b/crates/apub/activities/src/create_or_update/post.rs index fe423048e8..c270592bc2 100644 --- a/crates/apub/activities/src/create_or_update/post.rs +++ b/crates/apub/activities/src/create_or_update/post.rs @@ -46,7 +46,9 @@ impl CreateOrUpdatePage { kind: CreateOrUpdateType, context: &Data, ) -> LemmyResult { - let id = generate_activity_id(kind.clone(), context)?; + // get object_id + let object_id = post.activity_object_id(); + let id = generate_activity_id(kind.clone(), Some(&object_id), context)?; Ok(CreateOrUpdatePage { actor: actor.id().clone().into(), to: generate_to(community)?, diff --git a/crates/apub/activities/src/create_or_update/private_message.rs b/crates/apub/activities/src/create_or_update/private_message.rs index 2b1e78c0e6..1a2adb0a9c 100644 --- a/crates/apub/activities/src/create_or_update/private_message.rs +++ b/crates/apub/activities/src/create_or_update/private_message.rs @@ -24,7 +24,11 @@ pub(crate) async fn send_create_or_update_pm( let actor: ApubPerson = pm_view.creator.into(); let recipient: ApubPerson = pm_view.recipient.into(); - let id = generate_activity_id(kind.clone(), &context)?; + // get object_id + let pm = &pm_view.private_message; + let object_id = pm.activity_object_id(); + + let id = generate_activity_id(kind.clone(), Some(&object_id), &context)?; let create_or_update = CreateOrUpdatePrivateMessage { id: id.clone(), actor: actor.id().clone().into(), diff --git a/crates/apub/activities/src/deletion/delete.rs b/crates/apub/activities/src/deletion/delete.rs index d530f2c50e..29b8e3d818 100644 --- a/crates/apub/activities/src/deletion/delete.rs +++ b/crates/apub/activities/src/deletion/delete.rs @@ -1,7 +1,7 @@ use crate::{ MOD_ACTION_DEFAULT_REASON, deletion::{DeletableObjects, receive_delete_action, verify_delete_activity}, - generate_activity_id, + generate_activity_id_with_object_id, protocol::{IdOrNestedObject, deletion::delete::Delete}, }; use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::Activity}; @@ -82,7 +82,7 @@ impl Delete { with_replies: Option, context: &Data, ) -> LemmyResult { - let id = generate_activity_id(DeleteType::Delete, context)?; + let id = generate_activity_id_with_object_id(DeleteType::Delete, context)?; let cc: Option = community.map(|c| c.ap_id.clone().into()); Ok(Delete { actor: actor.ap_id.clone().into(), diff --git a/crates/apub/activities/src/deletion/undo_delete.rs b/crates/apub/activities/src/deletion/undo_delete.rs index 0c9dea0fe6..decbde1e8e 100644 --- a/crates/apub/activities/src/deletion/undo_delete.rs +++ b/crates/apub/activities/src/deletion/undo_delete.rs @@ -1,6 +1,6 @@ use crate::{ deletion::{DeletableObjects, receive_delete_action, verify_delete_activity}, - generate_activity_id, + generate_activity_id_with_object_id, protocol::deletion::{delete::Delete, undo_delete::UndoDelete}, }; use activitypub_federation::{config::Data, kinds::activity::UndoType, traits::Activity}; @@ -72,7 +72,7 @@ impl UndoDelete { context, )?; - let id = generate_activity_id(UndoType::Undo, context)?; + let id = generate_activity_id_with_object_id(UndoType::Undo, context)?; let cc: Option = community.map(|c| c.ap_id.clone().into()); Ok(UndoDelete { actor: actor.ap_id.clone().into(), diff --git a/crates/apub/activities/src/following/accept.rs b/crates/apub/activities/src/following/accept.rs index bd8708b236..c90f016c72 100644 --- a/crates/apub/activities/src/following/accept.rs +++ b/crates/apub/activities/src/following/accept.rs @@ -1,6 +1,6 @@ use crate::{ check_community_deleted_or_removed, - generate_activity_id, + generate_activity_id_with_object_id, protocol::following::{accept::AcceptFollow, follow::Follow}, send_lemmy_activity, }; @@ -27,7 +27,7 @@ impl AcceptFollow { to: Some([person.id().clone().into()]), object: follow, kind: AcceptType::Accept, - id: generate_activity_id(AcceptType::Accept, context)?, + id: generate_activity_id_with_object_id(AcceptType::Accept, context)?, }; let inbox = ActivitySendTargets::to_inbox(person.shared_inbox_or_inbox()); send_lemmy_activity(context, accept, &target, inbox, true).await diff --git a/crates/apub/activities/src/following/follow.rs b/crates/apub/activities/src/following/follow.rs index 486a8a01ff..5a9bc1b992 100644 --- a/crates/apub/activities/src/following/follow.rs +++ b/crates/apub/activities/src/following/follow.rs @@ -1,6 +1,6 @@ use crate::{ check_community_deleted_or_removed, - generate_activity_id, + generate_activity_id_with_object_id, protocol::following::{accept::AcceptFollow, follow::Follow}, send_lemmy_activity, }; @@ -40,7 +40,7 @@ impl Follow { object: target.id().clone().into(), to: Some([target.id().clone().into()]), kind: FollowType::Follow, - id: generate_activity_id(FollowType::Follow, context)?, + id: generate_activity_id_with_object_id(FollowType::Follow, context)?, }) } diff --git a/crates/apub/activities/src/following/mod.rs b/crates/apub/activities/src/following/mod.rs index c5a7d715c7..851dded37b 100644 --- a/crates/apub/activities/src/following/mod.rs +++ b/crates/apub/activities/src/following/mod.rs @@ -1,4 +1,4 @@ -use super::{generate_activity_id, send_lemmy_activity}; +use super::{generate_activity_id_with_object_id, send_lemmy_activity}; use crate::protocol::following::{ accept::AcceptFollow, follow::Follow, @@ -51,7 +51,7 @@ pub async fn send_accept_or_reject_follow( to: Some([community.ap_id.clone().into()]), object: community.ap_id.into(), kind: FollowType::Follow, - id: generate_activity_id(FollowType::Follow, context)?, + id: generate_activity_id_with_object_id(FollowType::Follow, context)?, }; if accepted { AcceptFollow::send(follow, context).await diff --git a/crates/apub/activities/src/following/reject.rs b/crates/apub/activities/src/following/reject.rs index 84189ce981..5d379893be 100644 --- a/crates/apub/activities/src/following/reject.rs +++ b/crates/apub/activities/src/following/reject.rs @@ -1,7 +1,7 @@ use super::send_activity_from_user_or_community_or_multi; use crate::{ check_community_deleted_or_removed, - generate_activity_id, + generate_activity_id_with_object_id, protocol::following::{follow::Follow, reject::RejectFollow}, }; use activitypub_federation::{ @@ -27,7 +27,7 @@ impl RejectFollow { to: Some([person.id().clone().into()]), object: follow, kind: RejectType::Reject, - id: generate_activity_id(RejectType::Reject, context)?, + id: generate_activity_id_with_object_id(RejectType::Reject, context)?, }; let inbox = ActivitySendTargets::to_inbox(person.shared_inbox_or_inbox()); send_activity_from_user_or_community_or_multi(context, reject, user_or_community, inbox).await diff --git a/crates/apub/activities/src/following/undo_follow.rs b/crates/apub/activities/src/following/undo_follow.rs index 3a776c27d2..c84931f393 100644 --- a/crates/apub/activities/src/following/undo_follow.rs +++ b/crates/apub/activities/src/following/undo_follow.rs @@ -1,6 +1,6 @@ use crate::{ check_community_deleted_or_removed, - generate_activity_id, + generate_activity_id_with_object_id, protocol::following::{follow::Follow, undo_follow::UndoFollow}, send_lemmy_activity, }; @@ -39,7 +39,7 @@ impl UndoFollow { to: Some([target.id().clone().into()]), object, kind: UndoType::Undo, - id: generate_activity_id(UndoType::Undo, context)?, + id: generate_activity_id_with_object_id(UndoType::Undo, context)?, }; let inbox = ActivitySendTargets::to_inbox(target.shared_inbox_or_inbox()); send_lemmy_activity(context, undo, actor, inbox, true).await diff --git a/crates/apub/activities/src/lib.rs b/crates/apub/activities/src/lib.rs index 4a1c199dde..3ed43678de 100644 --- a/crates/apub/activities/src/lib.rs +++ b/crates/apub/activities/src/lib.rs @@ -46,8 +46,9 @@ use lemmy_db_views_site::SiteView; use lemmy_diesel_utils::traits::Crud; use lemmy_utils::error::{LemmyError, LemmyResult, UntranslatedError}; use serde::Serialize; +use sha2::{Digest, Sha256}; use tracing::info; -use url::{ParseError, Url}; +use url::Url; use uuid::Uuid; pub mod activity_lists; @@ -80,34 +81,67 @@ pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Lemmy } } +/// convenient function for generate_activity_id +fn generate_activity_id_with_object_id(kind: T, context: &LemmyContext) -> LemmyResult +where + T: ToString, +{ + generate_activity_id::(kind, None, context) +} + /// Generate a unique ID for an activity, in the format: /// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36` -fn generate_activity_id(kind: T, context: &LemmyContext) -> Result +fn generate_activity_id( + kind: T, + object_id: Option<&Url>, + context: &LemmyContext, +) -> LemmyResult where T: ToString, { - let id = format!( - "{}/activities/{}/{}", - &context.settings().get_protocol_and_hostname(), - kind.to_string().to_lowercase(), - Uuid::new_v4() - ); - Url::parse(&id) + let hostname = context.settings().get_protocol_and_hostname(); + let kind_str = kind.to_string().to_lowercase(); + + let uuid_str = if let Some(o) = object_id { + let input = format!("{}:{}", kind_str, o.as_str()); + // hash + let mut hasher = Sha256::new(); + hasher.update(input); + let digest = hasher.finalize(); // 32 bytes + format!("{:x}", digest) + } else { + Uuid::new_v4().to_string() + }; + + let id = format!("{}/activities/{}/{}", hostname, kind_str, uuid_str); + Ok(Url::parse(&id)?) } /// like generate_activity_id but also add the inner kind for easier debugging fn generate_announce_activity_id( inner_kind: &str, protocol_and_hostname: &str, -) -> Result { + object_id: Option<&Url>, +) -> LemmyResult { + let uuid_str = if let Some(o) = object_id { + let input = format!("announce:{}", o.as_str()); + // hash + let mut hasher = Sha256::new(); + hasher.update(input); + let digest = hasher.finalize(); + format!("{:x}", digest) + } else { + Uuid::new_v4().to_string() + }; + let id = format!( "{}/activities/{}/{}/{}", protocol_and_hostname, AnnounceType::Announce.to_string().to_lowercase(), inner_kind.to_lowercase(), - Uuid::new_v4() + uuid_str ); - Url::parse(&id) + Ok(Url::parse(&id)?) } async fn send_lemmy_activity( diff --git a/crates/apub/activities/src/voting/undo_vote.rs b/crates/apub/activities/src/voting/undo_vote.rs index 1887ad73d9..9c0dd31e82 100644 --- a/crates/apub/activities/src/voting/undo_vote.rs +++ b/crates/apub/activities/src/voting/undo_vote.rs @@ -1,6 +1,6 @@ use crate::{ check_community_deleted_or_removed, - generate_activity_id, + generate_activity_id_with_object_id, protocol::voting::{undo_vote::UndoVote, vote::Vote}, voting::{undo_vote_comment, undo_vote_post}, }; @@ -29,7 +29,7 @@ impl UndoVote { actor: actor.id().clone().into(), object: vote, kind: UndoType::Undo, - id: generate_activity_id(UndoType::Undo, context)?, + id: generate_activity_id_with_object_id(UndoType::Undo, context)?, audience: Some(community.ap_id.clone().into()), }) } diff --git a/crates/apub/activities/src/voting/vote.rs b/crates/apub/activities/src/voting/vote.rs index 20287551b3..337aceb471 100644 --- a/crates/apub/activities/src/voting/vote.rs +++ b/crates/apub/activities/src/voting/vote.rs @@ -1,6 +1,6 @@ use crate::{ check_community_deleted_or_removed, - generate_activity_id, + generate_activity_id_with_object_id, protocol::voting::vote::{Vote, VoteType}, voting::{undo_vote_comment, undo_vote_post, vote_comment, vote_post}, }; @@ -31,7 +31,7 @@ impl Vote { actor: actor.id().clone().into(), object: object_id, kind: kind.clone(), - id: generate_activity_id(kind, context)?, + id: generate_activity_id_with_object_id(kind, context)?, audience: Some(community.ap_id.clone().into()), }) } diff --git a/crates/apub/apub/src/collections/community_outbox.rs b/crates/apub/apub/src/collections/community_outbox.rs index e1b984c0eb..795511bef7 100644 --- a/crates/apub/apub/src/collections/community_outbox.rs +++ b/crates/apub/apub/src/collections/community_outbox.rs @@ -64,8 +64,11 @@ impl Collection for ApubCommunityOutbox { ) .await { - let announcable = AnnouncableActivities::CreateOrUpdatePost(create); - if let Ok(announce) = AnnounceActivity::new(announcable.try_into()?, owner, data) { + let announcable = AnnouncableActivities::CreateOrUpdatePost(create.clone()); + // create id is like https://lemmy.example.com/activities/create/{UUID} + if let Ok(announce) = + AnnounceActivity::new(announcable.try_into()?, owner, Some(create.id()), data) + { ordered_items.push(announce); } } diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 034427e847..2faad1a493 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -320,6 +320,14 @@ impl Comment { .await .with_lemmy_type(LemmyErrorType::NotFound) } + + /// Return the comment's ap_id with a timestamp fragment appended. + pub fn activity_object_id(&self) -> Url { + let timestamp = self.updated_at.unwrap_or(self.published_at); + let mut object_id = (*self.ap_id.0).clone(); + object_id.set_fragment(Some(×tamp.to_rfc3339())); + object_id + } } impl Crud for Comment { diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 1dc4742c95..e980c9f015 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -343,6 +343,14 @@ impl Post { } Ok(()) } + + /// Return the post's ap_id with a timestamp fragment appended. + pub fn activity_object_id(&self) -> Url { + let timestamp = self.updated_at.unwrap_or(self.published_at); + let mut object_id = (*self.ap_id.0).clone(); + object_id.set_fragment(Some(×tamp.to_rfc3339())); + object_id + } } impl Likeable for PostActions { diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index c1c81fe9e4..394ed1d653 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -109,6 +109,14 @@ impl PrivateMessage { self.deleted_by_recipient = false; } } + + /// Return the private message's ap_id with a timestamp fragment appended. + pub fn activity_object_id(&self) -> Url { + let timestamp = self.updated_at.unwrap_or(self.published_at); + let mut object_id = (*self.ap_id.0).clone(); + object_id.set_fragment(Some(×tamp.to_rfc3339())); + object_id + } } #[cfg(test)]