X-Git-Url: http://these/git/?a=blobdiff_plain;f=crates%2Fapub%2Fsrc%2Factivities%2Fcommunity%2Fannounce.rs;h=6eb23f8da07bca1b3c4be5f414b4dc927ca1d305;hb=21a87ebaf2e5c038594eb70ef58bd51826259529;hp=bc72d80fe91ae15b6287f256700007078b3c69e5;hpb=bb7750d8ee8616854acdcf28c563d41b360efdce;p=lemmy.git diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index bc72d80f..6eb23f8d 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -1,143 +1,181 @@ use crate::{ activities::{ - comment::{create::CreateComment, update::UpdateComment}, - community::{ - add_mod::AddMod, - block_user::BlockUserFromCommunity, - list_community_follower_inboxes, - undo_block_user::UndoBlockUserFromCommunity, - }, - deletion::{ - delete::DeletePostCommentOrCommunity, - undo_delete::UndoDeletePostCommentOrCommunity, - }, generate_activity_id, - post::{create::CreatePost, update::UpdatePost}, - removal::{ - remove::RemovePostCommentCommunityOrMod, - undo_remove::UndoRemovePostCommentOrCommunity, - }, - verify_activity, - verify_community, - voting::{ - dislike::DislikePostOrComment, - like::LikePostOrComment, - undo_dislike::UndoDislikePostOrComment, - undo_like::UndoLikePostOrComment, - }, + send_lemmy_activity, + verify_is_public, + verify_person_in_community, }, - activity_queue::send_activity_new, - extensions::context::lemmy_context, - http::is_activity_already_known, - insert_activity, - ActorType, - CommunityType, + activity_lists::AnnouncableActivities, + insert_received_activity, + objects::community::ApubCommunity, + protocol::{ + activities::community::announce::{AnnounceActivity, RawAnnouncableActivities}, + Id, + IdOrNestedObject, + InCommunity, + }, +}; +use activitypub_federation::{ + config::Data, + kinds::{activity::AnnounceType, public}, + traits::{ActivityHandler, Actor}, }; -use activitystreams::activity::kind::AnnounceType; -use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler}; -use lemmy_db_schema::source::community::Community; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; -use serde::{Deserialize, Serialize}; +use lemmy_api_common::context::LemmyContext; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use serde_json::Value; use url::Url; -#[derive(Clone, Debug, Deserialize, Serialize, ActivityHandler)] -#[serde(untagged)] -pub enum AnnouncableActivities { - CreateComment(CreateComment), - UpdateComment(UpdateComment), - CreatePost(CreatePost), - UpdatePost(UpdatePost), - LikePostOrComment(LikePostOrComment), - DislikePostOrComment(DislikePostOrComment), - UndoLikePostOrComment(UndoLikePostOrComment), - UndoDislikePostOrComment(UndoDislikePostOrComment), - DeletePostCommentOrCommunity(DeletePostCommentOrCommunity), - UndoDeletePostCommentOrCommunity(UndoDeletePostCommentOrCommunity), - RemovePostCommentCommunityOrMod(RemovePostCommentCommunityOrMod), - UndoRemovePostCommentOrCommunity(UndoRemovePostCommentOrCommunity), - BlockUserFromCommunity(BlockUserFromCommunity), - UndoBlockUserFromCommunity(UndoBlockUserFromCommunity), - AddMod(AddMod), -} +#[async_trait::async_trait] +impl ActivityHandler for RawAnnouncableActivities { + type DataType = LemmyContext; + type Error = LemmyError; + + fn id(&self) -> &Url { + &self.id + } + + fn actor(&self) -> &Url { + &self.actor + } + + #[tracing::instrument(skip_all)] + async fn verify(&self, _data: &Data) -> Result<(), Self::Error> { + Ok(()) + } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct AnnounceActivity { - to: PublicUrl, - object: AnnouncableActivities, - cc: Vec, - #[serde(rename = "type")] - kind: AnnounceType, - #[serde(flatten)] - common: ActivityCommonFields, + #[tracing::instrument(skip_all)] + async fn receive(self, data: &Data) -> Result<(), Self::Error> { + let activity: AnnouncableActivities = self.clone().try_into()?; + // This is only for sending, not receiving so we reject it. + if let AnnouncableActivities::Page(_) = activity { + return Err(LemmyErrorType::CannotReceivePage)?; + } + + // verify and receive activity + activity.verify(data).await?; + activity.clone().receive(data).await?; + + // if activity is in a community, send to followers + let community = activity.community(data).await; + if let Ok(community) = community { + if community.local { + let actor_id = activity.actor().clone().into(); + verify_person_in_community(&actor_id, &community, data).await?; + AnnounceActivity::send(self, &community, data).await?; + } + } + Ok(()) + } } impl AnnounceActivity { + pub(crate) fn new( + object: RawAnnouncableActivities, + community: &ApubCommunity, + context: &Data, + ) -> Result { + Ok(AnnounceActivity { + actor: community.id().into(), + to: vec![public()], + object: IdOrNestedObject::NestedObject(object), + cc: vec![community.followers_url.clone().into()], + kind: AnnounceType::Announce, + id: generate_activity_id( + &AnnounceType::Announce, + &context.settings().get_protocol_and_hostname(), + )?, + }) + } + + #[tracing::instrument(skip_all)] pub async fn send( - object: AnnouncableActivities, - community: &Community, - additional_inboxes: Vec, - context: &LemmyContext, + object: RawAnnouncableActivities, + community: &ApubCommunity, + context: &Data, ) -> Result<(), LemmyError> { - let announce = AnnounceActivity { - to: PublicUrl::Public, - object, - cc: vec![community.followers_url()], - kind: AnnounceType::Announce, - common: ActivityCommonFields { - context: lemmy_context(), - id: generate_activity_id(AnnounceType::Announce)?, - actor: community.actor_id(), - unparsed: Default::default(), - }, - }; - let inboxes = list_community_follower_inboxes(community, additional_inboxes, context).await?; - send_activity_new( - context, - &announce, - &announce.common.id, - community, - inboxes, - false, - ) - .await + let announce = AnnounceActivity::new(object.clone(), community, context)?; + let inboxes = community.get_follower_inboxes(context).await?; + send_lemmy_activity(context, announce, community, inboxes.clone(), false).await?; + + // Pleroma and Mastodon can't handle activities like Announce/Create/Page. So for + // compatibility, we also send Announce/Page so that they can follow Lemmy communities. + let object_parsed = object.try_into()?; + if let AnnouncableActivities::CreateOrUpdatePost(c) = object_parsed { + // 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.settings().get_protocol_and_hostname(), + )?, + actor: c.actor.clone().into_inner(), + other: serde_json::to_value(c.object)? + .as_object() + .expect("is object") + .clone(), + }; + let announce_compat = AnnounceActivity::new(announcable_page, community, context)?; + send_lemmy_activity(context, announce_compat, community, inboxes, false).await?; + } + Ok(()) } } -#[async_trait::async_trait(?Send)] +#[async_trait::async_trait] impl ActivityHandler for AnnounceActivity { - async fn verify( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - verify_activity(self.common())?; - verify_community(&self.common.actor, context, request_counter).await?; - self.object.verify(context, request_counter).await?; + type DataType = LemmyContext; + type Error = LemmyError; + + fn id(&self) -> &Url { + &self.id + } + + fn actor(&self) -> &Url { + self.actor.inner() + } + + #[tracing::instrument(skip_all)] + async fn verify(&self, context: &Data) -> Result<(), LemmyError> { + insert_received_activity(&self.id, context).await?; + verify_is_public(&self.to, &self.cc)?; Ok(()) } - async fn receive( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - if is_activity_already_known(context.pool(), self.object.common().id_unchecked()).await? { - return Ok(()); + #[tracing::instrument(skip_all)] + async fn receive(self, context: &Data) -> Result<(), LemmyError> { + let object: AnnouncableActivities = self.object.object(context).await?.try_into()?; + // This is only for sending, not receiving so we reject it. + if let AnnouncableActivities::Page(_) = object { + return Err(LemmyErrorType::CannotReceivePage)?; } - insert_activity( - self.object.common().id_unchecked(), - self.object.clone(), - false, - true, - context.pool(), - ) - .await?; - self.object.receive(context, request_counter).await + + // verify here in order to avoid fetching the object twice over http + object.verify(context).await?; + object.receive(context).await + } +} + +impl Id for RawAnnouncableActivities { + fn object_id(&self) -> &Url { + ActivityHandler::id(self) + } +} + +impl TryFrom for AnnouncableActivities { + type Error = serde_json::error::Error; + + fn try_from(value: RawAnnouncableActivities) -> Result { + let mut map = value.other.clone(); + map.insert("id".to_string(), Value::String(value.id.to_string())); + map.insert("actor".to_string(), Value::String(value.actor.to_string())); + serde_json::from_value(Value::Object(map)) } +} + +impl TryFrom for RawAnnouncableActivities { + type Error = serde_json::error::Error; - fn common(&self) -> &ActivityCommonFields { - &self.common + fn try_from(value: AnnouncableActivities) -> Result { + serde_json::from_value(serde_json::to_value(value)?) } }