feat(activitypub-base): broadcast_like/undo_like + LikeActivity inbox handler

This commit is contained in:
2026-05-15 04:52:03 +02:00
parent 0cf34184d9
commit e691b20a05
2 changed files with 166 additions and 10 deletions

View File

@@ -340,6 +340,105 @@ impl ActivityPubService {
Ok(())
}
/// Send a Like activity to a single inbox.
pub async fn broadcast_like_to_inbox(
&self,
liker_user_id: uuid::Uuid,
object_ap_id: url::Url,
author_inbox_url: url::Url,
) -> anyhow::Result<()> {
let data = self.federation_config.to_request_data();
let local_actor = get_local_actor(liker_user_id, &data)
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;
// Deterministic ID so Undo(Like) can reference the same activity.
let like_id = url::Url::parse(&format!(
"{}/activities/like/{}",
self.base_url,
uuid::Uuid::new_v5(
&uuid::Uuid::NAMESPACE_URL,
format!("{}/{}", liker_user_id, object_ap_id).as_bytes(),
)
))?;
let like = crate::activities::LikeActivity {
id: like_id,
kind: Default::default(),
actor: ObjectId::from(local_actor.ap_id.clone()),
object: object_ap_id,
};
let sends = SendActivityTask::prepare(
&WithContext::new_default(like),
&local_actor,
vec![author_inbox_url],
&data,
)
.await?;
let failures = send_with_retry(sends, &data).await;
if !failures.is_empty() {
tracing::warn!(
count = failures.len(),
"some Like deliveries failed permanently"
);
}
Ok(())
}
/// Send an Undo(Like) activity to a single inbox.
pub async fn broadcast_undo_like_to_inbox(
&self,
liker_user_id: uuid::Uuid,
object_ap_id: url::Url,
author_inbox_url: url::Url,
) -> anyhow::Result<()> {
let data = self.federation_config.to_request_data();
let local_actor = get_local_actor(liker_user_id, &data)
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;
// Reconstruct the same deterministic like ID.
let like_id = url::Url::parse(&format!(
"{}/activities/like/{}",
self.base_url,
uuid::Uuid::new_v5(
&uuid::Uuid::NAMESPACE_URL,
format!("{}/{}", liker_user_id, object_ap_id).as_bytes(),
)
))?;
let undo_id = activity_url(&self.base_url).map_err(|e| anyhow::anyhow!("{e}"))?;
let undo = crate::activities::UndoActivity {
id: undo_id,
kind: Default::default(),
actor: ObjectId::from(local_actor.ap_id.clone()),
object: serde_json::json!({
"type": "Like",
"id": like_id.to_string(),
"actor": local_actor.ap_id.to_string(),
"object": object_ap_id.to_string(),
}),
};
let sends = SendActivityTask::prepare(
&WithContext::new_default(undo),
&local_actor,
vec![author_inbox_url],
&data,
)
.await?;
let failures = send_with_retry(sends, &data).await;
if !failures.is_empty() {
tracing::warn!(
count = failures.len(),
"some Undo(Like) deliveries failed permanently"
);
}
Ok(())
}
/// Resolve a `@user@domain` handle to a `DbActor` over HTTPS directly.
/// The library's `webfinger_resolve_actor` tries HTTP first in debug mode, which breaks
/// on servers that don't redirect HTTP → HTTPS.
@@ -1393,22 +1492,32 @@ impl domain::ports::OutboundFederationPort for ActivityPubService {
async fn broadcast_like(
&self,
_liker_user_id: &domain::value_objects::UserId,
_object_ap_id: &str,
_author_inbox_url: &str,
liker_user_id: &domain::value_objects::UserId,
object_ap_id: &str,
author_inbox_url: &str,
) -> Result<(), domain::errors::DomainError> {
// TODO: implement Like activity broadcasting
Ok(())
let object = url::Url::parse(object_ap_id)
.map_err(|e| domain::errors::DomainError::Internal(e.to_string()))?;
let inbox = url::Url::parse(author_inbox_url)
.map_err(|e| domain::errors::DomainError::Internal(e.to_string()))?;
self.broadcast_like_to_inbox(liker_user_id.as_uuid(), object, inbox)
.await
.map_err(|e| domain::errors::DomainError::Internal(e.to_string()))
}
async fn broadcast_undo_like(
&self,
_liker_user_id: &domain::value_objects::UserId,
_object_ap_id: &str,
_author_inbox_url: &str,
liker_user_id: &domain::value_objects::UserId,
object_ap_id: &str,
author_inbox_url: &str,
) -> Result<(), domain::errors::DomainError> {
// TODO: implement Undo(Like) activity broadcasting
Ok(())
let object = url::Url::parse(object_ap_id)
.map_err(|e| domain::errors::DomainError::Internal(e.to_string()))?;
let inbox = url::Url::parse(author_inbox_url)
.map_err(|e| domain::errors::DomainError::Internal(e.to_string()))?;
self.broadcast_undo_like_to_inbox(liker_user_id.as_uuid(), object, inbox)
.await
.map_err(|e| domain::errors::DomainError::Internal(e.to_string()))
}
}