Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/apub/activities/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ serde_with.workspace = true
enum_delegate = "0.2.0"
either = { workspace = true }
lemmy_diesel_utils = { workspace = true }
md5 = "0.8.0"
Comment thread
Nutomic marked this conversation as resolved.
Outdated

[dev-dependencies]

Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/block/block_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl BlockUser {
kind: BlockType::Block,
remove_data,
summary: Some(reason),
id: generate_activity_id(BlockType::Block, context)?,
id: generate_activity_id(BlockType::Block, None, context)?,
end_time: expires,
audience: target.as_ref().right().map(|c| c.ap_id.clone().into()),
})
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/block/undo_block_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(UndoType::Undo, None, context)?;
let undo = UndoBlockUser {
actor: mod_.id().clone().into(),
to,
Expand Down
14 changes: 9 additions & 5 deletions crates/apub/activities/src/community/announce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,19 @@ impl AnnounceActivity {
pub fn new(
object: RawAnnouncableActivities,
community: &ApubCommunity,
object_id: Option<&Url>,
context: &Data<LemmyContext>,
) -> LemmyResult<AnnounceActivity> {
let inner_kind = object
.other
.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)?,
Expand All @@ -111,7 +115,7 @@ impl AnnounceActivity {
community: &ApubCommunity,
context: &Data<LemmyContext>,
) -> 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);
Copy link
Copy Markdown
Author

@uOJackDu uOJackDu Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we want the activity id for Announce activities to be the hash in the outbox, and a random UUID in the sent_activity table? For Create activities, both are the same.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same activity must always have the same ID.

send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?;

Expand All @@ -122,14 +126,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(AnnounceType::Announce, None, 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(())
Expand Down
4 changes: 2 additions & 2 deletions crates/apub/activities/src/community/collection_add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl CollectionAdd {
actor: &ApubPerson,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let id = generate_activity_id(AddType::Add, context)?;
let id = generate_activity_id(AddType::Add, None, context)?;
let add = CollectionAdd {
actor: actor.id().clone().into(),
to: generate_to(community)?,
Expand All @@ -69,7 +69,7 @@ impl CollectionAdd {
actor: &ApubPerson,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let id = generate_activity_id(AddType::Add, context)?;
let id = generate_activity_id(AddType::Add, None, context)?;
let add = CollectionAdd {
actor: actor.id().clone().into(),
to: generate_to(community)?,
Expand Down
4 changes: 2 additions & 2 deletions crates/apub/activities/src/community/collection_remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl CollectionRemove {
actor: &ApubPerson,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let id = generate_activity_id(RemoveType::Remove, context)?;
let id = generate_activity_id(RemoveType::Remove, None, context)?;
let remove = CollectionRemove {
actor: actor.id().clone().into(),
to: generate_to(community)?,
Expand All @@ -66,7 +66,7 @@ impl CollectionRemove {
actor: &ApubPerson,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let id = generate_activity_id(RemoveType::Remove, context)?;
let id = generate_activity_id(RemoveType::Remove, None, context)?;
let remove = CollectionRemove {
actor: actor.id().clone().into(),
to: generate_to(community)?,
Expand Down
4 changes: 2 additions & 2 deletions crates/apub/activities/src/community/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ pub(crate) async fn send_lock(
context: Data<LemmyContext>,
) -> 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(LockType::Lock, None, &context)?;
let community_id = community.ap_id.inner().clone();
let ap_id = match object {
PostOrComment::Left(p) => p.ap_id.clone(),
Expand All @@ -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(UndoType::Undo, None, &context)?;
let undo = UndoLockPageOrNote {
actor: lock.actor.clone(),
to: generate_to(&community)?,
Expand Down
4 changes: 2 additions & 2 deletions crates/apub/activities/src/community/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl Report {
context: &Data<LemmyContext>,
) -> LemmyResult<Self> {
let kind = FlagType::Flag;
let id = generate_activity_id(kind.clone(), context)?;
let id = generate_activity_id(kind.clone(), None, context)?;
Ok(Report {
actor: actor.id().clone().into(),
to: [receiver.id().clone().into()],
Expand Down Expand Up @@ -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?;
}
Expand Down
4 changes: 2 additions & 2 deletions crates/apub/activities/src/community/resolve_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl ResolveReport {
context: Data<LemmyContext>,
) -> LemmyResult<()> {
let kind = ResolveType::Resolve;
let id = generate_activity_id(kind.clone(), &context)?;
let id = generate_activity_id(kind.clone(), None, &context)?;
let object = Report::new(&object_id, report_creator, receiver, None, &context)?;
let resolve = ResolveReport {
actor: actor.id().clone().into(),
Expand Down Expand Up @@ -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?;
}
Expand Down
4 changes: 2 additions & 2 deletions crates/apub/activities/src/community/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(UpdateType::Update, None, &context)?;
let update = Update {
actor: actor.id().clone().into(),
to: generate_to(&community)?,
Expand Down Expand Up @@ -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(UpdateType::Update, None, &context)?;
let update = Update {
actor: actor.id().clone().into(),
to: vec![multi.ap_id.clone().into(), public()],
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/create_or_update/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl CreateOrUpdateNote {
.await?
.into();

let id = generate_activity_id(kind.clone(), &context)?;
let id = generate_activity_id(kind.clone(), None, &context)?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let id = generate_activity_id(kind.clone(), None, &context)?;
let id = generate_activity_id(kind.clone(), Some(comment.ap_id), &context)?;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use the timestamps here for Update as well?

let note = ApubComment(comment).into_json(&context).await?;

let create_or_update = CreateOrUpdateNote {
Expand Down
19 changes: 17 additions & 2 deletions crates/apub/activities/src/create_or_update/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ impl CreateOrUpdatePage {
actor: &ApubPerson,
community: &ApubCommunity,
kind: CreateOrUpdateType,
object_id: Option<&Url>,
context: &Data<LemmyContext>,
) -> LemmyResult<CreateOrUpdatePage> {
let id = generate_activity_id(kind.clone(), context)?;
let id = generate_activity_id(kind.clone(), object_id, context)?;
Ok(CreateOrUpdatePage {
actor: actor.id().clone().into(),
to: generate_to(community)?,
Expand All @@ -70,8 +71,22 @@ impl CreateOrUpdatePage {
.await?
.into();

// get object_id for activity id generation
let post_ap_id = (*post.ap_id.0).clone();
let object_id = match kind {
// for Create, use the post's ap id
CreateOrUpdateType::Create => Some(&post_ap_id),
// for Update, use a timestamp to ensure each Update activity is unique
CreateOrUpdateType::Update => {
let timestamp = post.updated_at.unwrap_or(post.published_at); // use the latest timestamp
let mut seed_url = post_ap_id;
seed_url.set_fragment(Some(&timestamp.to_rfc3339()));
Some(&seed_url.clone())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

}
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private message also needs the same logic. Best generate_activity_id() to take an optional timestamp param (or make a separate method), to avoid duplicate code. Theres nothing wrong with hashing the timestamp for create activities as well so it will be simpler.

And you dont need to change anything in this file, it only needs to be in CreateOrUpdatePage::new


let create_or_update =
CreateOrUpdatePage::new(post.into(), &person, &community, kind, &context).await?;
CreateOrUpdatePage::new(post.into(), &person, &community, kind, object_id, &context).await?;
let inboxes = tagged_user_inboxes(&create_or_update.object.tag, &context).await?;
let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
send_activity_in_community(activity, &person, &community, inboxes, false, &context).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ 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)?;
let id = generate_activity_id(kind.clone(), None, &context)?;
let create_or_update = CreateOrUpdatePrivateMessage {
id: id.clone(),
actor: actor.id().clone().into(),
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/deletion/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl Delete {
with_replies: Option<bool>,
context: &Data<LemmyContext>,
) -> LemmyResult<Delete> {
let id = generate_activity_id(DeleteType::Delete, context)?;
let id = generate_activity_id(DeleteType::Delete, None, context)?;
let cc: Option<Url> = community.map(|c| c.ap_id.clone().into());
Ok(Delete {
actor: actor.ap_id.clone().into(),
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/deletion/undo_delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl UndoDelete {
context,
)?;

let id = generate_activity_id(UndoType::Undo, context)?;
let id = generate_activity_id(UndoType::Undo, None, context)?;
let cc: Option<Url> = community.map(|c| c.ap_id.clone().into());
Ok(UndoDelete {
actor: actor.ap_id.clone().into(),
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/following/accept.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(AcceptType::Accept, None, context)?,
};
let inbox = ActivitySendTargets::to_inbox(person.shared_inbox_or_inbox());
send_lemmy_activity(context, accept, &target, inbox, true).await
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/following/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(FollowType::Follow, None, context)?,
})
}

Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/following/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(FollowType::Follow, None, context)?,
};
if accepted {
AcceptFollow::send(follow, context).await
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/following/reject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(RejectType::Reject, None, 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
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/following/undo_follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(UndoType::Undo, None, context)?,
};
let inbox = ActivitySendTargets::to_inbox(target.shared_inbox_or_inbox());
send_lemmy_activity(context, undo, actor, inbox, true).await
Expand Down
38 changes: 29 additions & 9 deletions crates/apub/activities/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,30 +82,50 @@ pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Lemmy

/// 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<T>(kind: T, context: &LemmyContext) -> Result<Url, ParseError>
fn generate_activity_id<T>(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid passing None in so many places, you could do:

fn generate_activity_id_with_object_id(kind, context) {
  generate_activity_id(kind, None, context)
}

fn generate_activity_id(kind, object_id, context) {
  generate_activity_id(kind, context)
}

kind: T,
object_id: Option<&Url>,
context: &LemmyContext,
) -> Result<Url, ParseError>
where
T: ToString,
{
let id = format!(
"{}/activities/{}/{}",
&context.settings().get_protocol_and_hostname(),
kind.to_string().to_lowercase(),
Uuid::new_v4()
);
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());
Uuid::from_bytes(md5::compute(input).0).to_string()
} else {
Uuid::new_v4().to_string()
};

let id = format!("{}/activities/{}/{}", hostname, kind_str, uuid_str);
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,
object_id: Option<&Url>,
) -> Result<Url, ParseError> {
let inner_kind_str = inner_kind.to_lowercase();

let uuid_str = if let Some(o) = object_id {
// add "announce:" in front to avoid collision with generate_activity_id
let input = format!("announce:{}:{}", inner_kind_str, o.as_str());
Uuid::from_bytes(md5::compute(input).0).to_string()
} 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()
inner_kind_str,
uuid_str
);
Url::parse(&id)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/voting/undo_vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(UndoType::Undo, None, context)?,
audience: Some(community.ap_id.clone().into()),
})
}
Expand Down
2 changes: 1 addition & 1 deletion crates/apub/activities/src/voting/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(kind, None, context)?,
audience: Some(community.ap_id.clone().into()),
})
}
Expand Down
Loading