X-Git-Url: http://these/git/?a=blobdiff_plain;f=crates%2Fapub%2Fsrc%2Factivities%2Fcommunity%2Fannounce.rs;h=6eb23f8da07bca1b3c4be5f414b4dc927ca1d305;hb=21a87ebaf2e5c038594eb70ef58bd51826259529;hp=a86317b52067d7f3323cf7680e10230e19a71f64;hpb=e756e85da762acbf973a335619622530ca174038;p=lemmy.git diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index a86317b5..6eb23f8d 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -1,122 +1,181 @@ use crate::{ - activities::{generate_activity_id, send_lemmy_activity, verify_activity, verify_is_public}, + activities::{ + generate_activity_id, + send_lemmy_activity, + verify_is_public, + verify_person_in_community, + }, activity_lists::AnnouncableActivities, - http::{is_activity_already_known, ActivityCommonFields}, - insert_activity, + insert_received_activity, objects::community::ApubCommunity, - protocol::activities::community::announce::AnnounceActivity, + protocol::{ + activities::community::announce::{AnnounceActivity, RawAnnouncableActivities}, + Id, + IdOrNestedObject, + InCommunity, + }, }; -use activitystreams::{activity::kind::AnnounceType, public}; -use lemmy_apub_lib::{ - data::Data, - object_id::ObjectId, - traits::{ActivityHandler, ActorType}, +use activitypub_federation::{ + config::Data, + kinds::{activity::AnnounceType, public}, + traits::{ActivityHandler, Actor}, }; -use lemmy_utils::LemmyError; -use lemmy_websocket::LemmyContext; - -#[async_trait::async_trait(?Send)] -pub(crate) trait GetCommunity { - async fn get_community( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result; +use lemmy_api_common::context::LemmyContext; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use serde_json::Value; +use url::Url; + +#[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(()) + } + + #[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: AnnouncableActivities, + object: RawAnnouncableActivities, community: &ApubCommunity, - context: &LemmyContext, + context: &Data, ) -> Result { Ok(AnnounceActivity { - actor: ObjectId::new(community.actor_id()), + actor: community.id().into(), to: vec![public()], - object, + 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(), )?, - unparsed: Default::default(), }) } + #[tracing::instrument(skip_all)] pub async fn send( - object: AnnouncableActivities, + object: RawAnnouncableActivities, community: &ApubCommunity, - context: &LemmyContext, + context: &Data, ) -> Result<(), LemmyError> { let announce = AnnounceActivity::new(object.clone(), community, context)?; let inboxes = community.get_follower_inboxes(context).await?; - send_lemmy_activity( - context, - &announce, - &announce.id, - community, - inboxes.clone(), - false, - ) - .await?; - - // Pleroma (and likely Mastodon) can't handle activities like Announce/Create/Page. So for - // compatibility to allow them to follow Lemmy communities, we also send Announce/Page and - // Announce/Note (for new and updated posts/comments). - use AnnouncableActivities::*; - let object = match object { - CreateOrUpdatePost(c) => Page(c.object), - CreateOrUpdateComment(c) => Note(c.object), - _ => return Ok(()), - }; - let announce_compat = AnnounceActivity::new(object, community, context)?; - send_lemmy_activity( - context, - &announce_compat, - &announce_compat.id, - community, - inboxes, - false, - ) - .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 { type DataType = LemmyContext; - async fn verify( - &self, - context: &Data, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { + 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)?; - verify_activity(&self.id, self.actor.inner(), &context.settings())?; - self.object.verify(context, request_counter).await?; Ok(()) } - async fn receive( - self, - context: &Data, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - // TODO: this can probably be implemented in a cleaner way - match self.object { - // Dont insert these into activities table, as they are not activities. - AnnouncableActivities::Page(_) | AnnouncableActivities::Note(_) => {} - _ => { - let object_value = serde_json::to_value(&self.object)?; - let object_data: ActivityCommonFields = serde_json::from_value(object_value.to_owned())?; - - if is_activity_already_known(context.pool(), &object_data.id).await? { - return Ok(()); - } - insert_activity(&object_data.id, object_value, false, true, context.pool()).await?; - } + #[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)?; } - 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 try_from(value: AnnouncableActivities) -> Result { + serde_json::from_value(serde_json::to_value(value)?) } }