]> Untitled Git - lemmy.git/commitdiff
Merge pull request #1678 from LemmyNet/rewrite-post
authorNutomic <me@nutomic.com>
Sat, 31 Jul 2021 07:10:56 +0000 (07:10 +0000)
committerGitHub <noreply@github.com>
Sat, 31 Jul 2021 07:10:56 +0000 (07:10 +0000)
Rewrite apub post (de)serialization using structs (ref #1657)

49 files changed:
Cargo.lock
crates/api/src/post.rs
crates/api_crud/src/post/create.rs
crates/api_crud/src/post/update.rs
crates/apub/src/activities/comment/create.rs
crates/apub/src/activities/comment/remove.rs
crates/apub/src/activities/comment/undo_remove.rs
crates/apub/src/activities/comment/update.rs
crates/apub/src/activities/community/add_mod.rs
crates/apub/src/activities/community/announce.rs
crates/apub/src/activities/community/block_user.rs
crates/apub/src/activities/community/mod.rs
crates/apub/src/activities/community/undo_block_user.rs
crates/apub/src/activities/community/update.rs
crates/apub/src/activities/deletion/delete.rs
crates/apub/src/activities/deletion/undo_delete.rs
crates/apub/src/activities/mod.rs
crates/apub/src/activities/post/create.rs
crates/apub/src/activities/post/update.rs
crates/apub/src/activities/removal/remove.rs
crates/apub/src/activities/removal/undo_remove.rs
crates/apub/src/activities/send/comment.rs
crates/apub/src/activities/send/community.rs
crates/apub/src/activities/send/mod.rs
crates/apub/src/activities/send/person.rs
crates/apub/src/activities/send/post.rs
crates/apub/src/activities/send/private_message.rs
crates/apub/src/activities/voting/dislike.rs
crates/apub/src/activities/voting/like.rs
crates/apub/src/activities/voting/undo_dislike.rs
crates/apub/src/activities/voting/undo_like.rs
crates/apub/src/activity_queue.rs
crates/apub/src/extensions/context.rs
crates/apub/src/extensions/mod.rs
crates/apub/src/extensions/page_extension.rs [deleted file]
crates/apub/src/fetcher/objects.rs
crates/apub/src/fetcher/search.rs
crates/apub/src/http/community.rs
crates/apub/src/http/person.rs
crates/apub/src/lib.rs
crates/apub/src/objects/comment.rs
crates/apub/src/objects/community.rs
crates/apub/src/objects/mod.rs
crates/apub/src/objects/person.rs
crates/apub/src/objects/post.rs
crates/apub/src/objects/private_message.rs
crates/apub_lib/Cargo.toml
crates/apub_lib/src/lib.rs
crates/apub_lib/src/values/mod.rs [new file with mode: 0644]

index 53e3e0071e56f6a122714b536f2f970c86c2b7ba..ffa6f79c9e21954f44749f690f5831561fa131a3 100644 (file)
@@ -1760,6 +1760,7 @@ dependencies = [
  "lemmy_utils",
  "lemmy_websocket",
  "serde",
+ "serde_json",
  "url",
 ]
 
index 4a55b7e05b876a06f27a5db07bfe2410ec317ec6..84e0c849e0dde837f92e2af6af732a6167a61081 100644 (file)
@@ -9,7 +9,7 @@ use lemmy_api_common::{
   mark_post_as_read,
   post::*,
 };
-use lemmy_apub::{ApubLikeableType, ApubObjectType};
+use lemmy_apub::{activities::post::update::UpdatePost, ApubLikeableType};
 use lemmy_db_queries::{source::post::Post_, Crud, Likeable, Saveable};
 use lemmy_db_schema::source::{moderator::*, post::*};
 use lemmy_db_views::post_view::PostView;
@@ -140,9 +140,7 @@ impl Perform for LockPost {
     blocking(context.pool(), move |conn| ModLockPost::create(conn, &form)).await??;
 
     // apub updates
-    updated_post
-      .send_update(&local_user_view.person, context)
-      .await?;
+    UpdatePost::send(&updated_post, &local_user_view.person, context).await?;
 
     // Refetch the post
     let post_id = data.post_id;
@@ -214,9 +212,7 @@ impl Perform for StickyPost {
 
     // Apub updates
     // TODO stickied should pry work like locked for ease of use
-    updated_post
-      .send_update(&local_user_view.person, context)
-      .await?;
+    UpdatePost::send(&updated_post, &local_user_view.person, context).await?;
 
     // Refetch the post
     let post_id = data.post_id;
index dc99f3ecf4f81052bbbe203691e6d7512b9442ac..b2034a36741aa3c40fe3ffad0192567316e62b16 100644 (file)
@@ -7,7 +7,12 @@ use lemmy_api_common::{
   mark_post_as_read,
   post::*,
 };
-use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
+use lemmy_apub::{
+  activities::post::create::CreatePost as CreateApubPost,
+  generate_apub_endpoint,
+  ApubLikeableType,
+  EndpointType,
+};
 use lemmy_db_queries::{source::post::Post_, Crud, Likeable};
 use lemmy_db_schema::source::post::*;
 use lemmy_db_views::post_view::PostView;
@@ -82,9 +87,7 @@ impl PerformCrud for CreatePost {
     .await?
     .map_err(|_| ApiError::err("couldnt_create_post"))?;
 
-    updated_post
-      .send_create(&local_user_view.person, context)
-      .await?;
+    CreateApubPost::send(&updated_post, &local_user_view.person, context).await?;
 
     // They like their own post by default
     let person_id = local_user_view.person.id;
index 5166f21278d34a4710f7a7451a3439afa61a02e4..6b2780376e3e81d3080aa2fef8f6b406931a599e 100644 (file)
@@ -1,7 +1,7 @@
 use crate::PerformCrud;
 use actix_web::web::Data;
 use lemmy_api_common::{blocking, check_community_ban, get_local_user_view_from_jwt, post::*};
-use lemmy_apub::ApubObjectType;
+use lemmy_apub::activities::post::update::UpdatePost;
 use lemmy_db_queries::{source::post::Post_, Crud, DeleteableOrRemoveable};
 use lemmy_db_schema::{naive_now, source::post::*};
 use lemmy_db_views::post_view::PostView;
@@ -89,9 +89,7 @@ impl PerformCrud for EditPost {
     };
 
     // Send apub update
-    updated_post
-      .send_update(&local_user_view.person, context)
-      .await?;
+    UpdatePost::send(&updated_post, &local_user_view.person, context).await?;
 
     let post_id = data.post_id;
     let mut post_view = blocking(context.pool(), move |conn| {
index 84dab107132b3b92d0c7fdbc8627158a7d8dd24c..0e9472194246a6b64b80c3f66072a15e950271ba 100644 (file)
@@ -1,14 +1,21 @@
 use crate::{
   activities::{
     comment::{get_notif_recipients, send_websocket_message},
+    extract_community,
     verify_activity,
     verify_person_in_community,
   },
   objects::FromApub,
+  ActorType,
   NoteExt,
 };
 use activitystreams::{activity::kind::CreateType, base::BaseExt};
-use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{
+  values::PublicUrl,
+  verify_domains_match_opt,
+  ActivityCommonFields,
+  ActivityHandler,
+};
 use lemmy_db_schema::source::comment::Comment;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::{LemmyContext, UserOperationCrud};
@@ -33,8 +40,16 @@ impl ActivityHandler for CreateComment {
     context: &LemmyContext,
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
+    let community = extract_community(&self.cc, context, request_counter).await?;
+
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(
+      &self.common.actor,
+      &community.actor_id(),
+      context,
+      request_counter,
+    )
+    .await?;
     verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
     // TODO: should add a check that the correct community is in cc (probably needs changes to
     //       comment deserialization)
index d60e3f80444d347a98a890e34330f075ca7fb35b..5702f9fa76facfedf955a5343d3d0b169e481881 100644 (file)
@@ -1,8 +1,16 @@
-use crate::activities::{comment::send_websocket_message, verify_mod_action};
+use crate::{
+  activities::{comment::send_websocket_message, verify_mod_action},
+  check_is_apub_id_valid,
+  fetcher::objects::get_or_fetch_and_insert_comment,
+};
 use activitystreams::activity::kind::RemoveType;
 use lemmy_api_common::blocking;
-use crate::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment};
-use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl};
+use lemmy_apub_lib::{
+  values::PublicUrl,
+  verify_domains_match,
+  ActivityCommonFields,
+  ActivityHandlerNew,
+};
 use lemmy_db_queries::source::comment::Comment_;
 use lemmy_db_schema::source::comment::Comment;
 use lemmy_utils::LemmyError;
index f3ebdf3851ab2d4ef5b203c7634d22146a26ce6b..07aa67119b0c31d425271b5f29491e75de44e8bb 100644 (file)
@@ -1,11 +1,19 @@
-use crate::activities::{
-  comment::{remove::RemoveComment, send_websocket_message},
-  verify_mod_action,
+use crate::{
+  activities::{
+    comment::{remove::RemoveComment, send_websocket_message},
+    verify_mod_action,
+  },
+  check_is_apub_id_valid,
+  fetcher::objects::get_or_fetch_and_insert_comment,
 };
 use activitystreams::activity::kind::UndoType;
 use lemmy_api_common::blocking;
-use crate::{check_is_apub_id_valid, fetcher::objects::get_or_fetch_and_insert_comment};
-use lemmy_apub_lib::{verify_domains_match, ActivityCommonFields, ActivityHandlerNew, PublicUrl};
+use lemmy_apub_lib::{
+  values::PublicUrl,
+  verify_domains_match,
+  ActivityCommonFields,
+  ActivityHandlerNew,
+};
 use lemmy_db_queries::source::comment::Comment_;
 use lemmy_db_schema::source::comment::Comment;
 use lemmy_utils::LemmyError;
index 142656f506e41dce0f4a9725a0363629244f3a71..c0d0148138084edbf19c2aba715ead8dc0faad97 100644 (file)
@@ -1,14 +1,21 @@
 use crate::{
   activities::{
     comment::{get_notif_recipients, send_websocket_message},
+    extract_community,
     verify_activity,
     verify_person_in_community,
   },
   objects::FromApub,
+  ActorType,
   NoteExt,
 };
 use activitystreams::{activity::kind::UpdateType, base::BaseExt};
-use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{
+  values::PublicUrl,
+  verify_domains_match_opt,
+  ActivityCommonFields,
+  ActivityHandler,
+};
 use lemmy_db_schema::source::comment::Comment;
 use lemmy_utils::LemmyError;
 use lemmy_websocket::{LemmyContext, UserOperationCrud};
@@ -33,8 +40,16 @@ impl ActivityHandler for UpdateComment {
     context: &LemmyContext,
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
+    let community = extract_community(&self.cc, context, request_counter).await?;
+
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(
+      &self.common.actor,
+      &community.actor_id(),
+      context,
+      request_counter,
+    )
+    .await?;
     verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
     Ok(())
   }
index 2785856c131e53919fd59906623d763d335c6b60..0dcd9818b5a9966e8c94823c4aa2e65e386a28bf 100644 (file)
@@ -10,7 +10,7 @@ use crate::{
 };
 use activitystreams::{activity::kind::AddType, base::AnyBase};
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::{source::community::CommunityModerator_, Joinable};
 use lemmy_db_schema::source::community::{CommunityModerator, CommunityModeratorForm};
 use lemmy_utils::LemmyError;
@@ -38,7 +38,7 @@ impl ActivityHandler for AddMod {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
     verify_add_remove_moderator_target(&self.target, self.cc[0].clone())?;
     Ok(())
index 301ccc64840574e0bcce1a9e839b96677cfa7700..bc72d80fe91ae15b6287f256700007078b3c69e5 100644 (file)
@@ -4,12 +4,14 @@ use crate::{
     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,
@@ -24,11 +26,16 @@ use crate::{
       undo_like::UndoLikePostOrComment,
     },
   },
+  activity_queue::send_activity_new,
+  extensions::context::lemmy_context,
   http::is_activity_already_known,
   insert_activity,
+  ActorType,
+  CommunityType,
 };
 use activitystreams::activity::kind::AnnounceType;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+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};
@@ -66,6 +73,38 @@ pub struct AnnounceActivity {
   common: ActivityCommonFields,
 }
 
+impl AnnounceActivity {
+  pub async fn send(
+    object: AnnouncableActivities,
+    community: &Community,
+    additional_inboxes: Vec<Url>,
+    context: &LemmyContext,
+  ) -> 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
+  }
+}
+
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for AnnounceActivity {
   async fn verify(
index c20652b02c85f61c6b584a4894564735cfe416d5..34909b2e6086904264c906d152fa3f3acebe6d8c 100644 (file)
@@ -4,7 +4,7 @@ use crate::{
 };
 use activitystreams::activity::kind::BlockType;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::{Bannable, Followable};
 use lemmy_db_schema::source::community::{
   CommunityFollower,
@@ -36,7 +36,7 @@ impl ActivityHandler for BlockUserFromCommunity {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
     Ok(())
   }
index 81152d926e4d19f12e4b93358992bca7ea44c1c8..62b39c6e8ce0a087955730240fec5f04fccbc71c 100644 (file)
@@ -1,8 +1,11 @@
+use crate::{check_is_apub_id_valid, CommunityType};
+use itertools::Itertools;
 use lemmy_api_common::{blocking, community::CommunityResponse};
-use lemmy_db_schema::CommunityId;
+use lemmy_db_schema::{source::community::Community, CommunityId};
 use lemmy_db_views_actor::community_view::CommunityView;
-use lemmy_utils::LemmyError;
+use lemmy_utils::{settings::structs::Settings, LemmyError};
 use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext};
+use url::Url;
 
 pub mod add_mod;
 pub mod announce;
@@ -33,3 +36,23 @@ pub(crate) async fn send_websocket_message<
 
   Ok(())
 }
+
+async fn list_community_follower_inboxes(
+  community: &Community,
+  additional_inboxes: Vec<Url>,
+  context: &LemmyContext,
+) -> Result<Vec<Url>, LemmyError> {
+  Ok(
+    vec![
+      community.get_follower_inboxes(context.pool()).await?,
+      additional_inboxes,
+    ]
+    .iter()
+    .flatten()
+    .unique()
+    .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname()))
+    .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok())
+    .map(|inbox| inbox.to_owned())
+    .collect(),
+  )
+}
index 025c498a6d4fff01908b7b8916d6856457166bd0..53b665db15fd210e918580a97d3f1cdf1381081f 100644 (file)
@@ -9,7 +9,7 @@ use crate::{
 };
 use activitystreams::activity::kind::UndoType;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::Bannable;
 use lemmy_db_schema::source::community::{CommunityPersonBan, CommunityPersonBanForm};
 use lemmy_utils::LemmyError;
@@ -36,7 +36,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
     self.object.verify(context, request_counter).await?;
     Ok(())
index cfee29da74399c381383926783f7fba472ebd110..fd0bf2d456a76c3fe70dcaaa5dcc3b0390456ce3 100644 (file)
@@ -10,7 +10,7 @@ use crate::{
 };
 use activitystreams::activity::kind::UpdateType;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::{ApubObject, Crud};
 use lemmy_db_schema::source::community::{Community, CommunityForm};
 use lemmy_utils::LemmyError;
@@ -39,7 +39,7 @@ impl ActivityHandler for UpdateCommunity {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
     Ok(())
   }
index f0f4185a7b744d8e109dabed9b9c3c3db48f0fbe..cabfcce8659fb0f847cf04480a22d219273bdc1d 100644 (file)
@@ -18,7 +18,7 @@ use crate::{
 };
 use activitystreams::activity::kind::DeleteType;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::{
   source::{comment::Comment_, community::Community_, post::Post_},
   Crud,
@@ -64,7 +64,8 @@ impl ActivityHandler for DeletePostCommentOrCommunity {
     }
     // deleting a post or comment
     else {
-      verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?;
+      verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
+        .await?;
       let object_creator =
         get_post_or_comment_actor_id(&self.object, context, request_counter).await?;
       verify_urls_match(&self.common.actor, &object_creator)?;
@@ -83,7 +84,7 @@ impl ActivityHandler for DeletePostCommentOrCommunity {
     if let Ok(community) = object_community {
       if community.local {
         // repeat these checks just to be sure
-        verify_person_in_community(&self.common().actor, &self.cc, context, request_counter)
+        verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
           .await?;
         verify_mod_action(&self.common.actor, self.object.clone(), context).await?;
         let mod_ =
index 1de5ca587ead823f4e331b99c1a74ed36030a7bf..ea70e5f5e76486a744dd959416ce0281a457d1da 100644 (file)
@@ -18,7 +18,7 @@ use crate::{
 };
 use activitystreams::activity::kind::UndoType;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_};
 use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
 use lemmy_utils::LemmyError;
@@ -54,7 +54,8 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity {
     }
     // restoring a post or comment
     else {
-      verify_person_in_community(&self.common().actor, &self.cc, context, request_counter).await?;
+      verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
+        .await?;
       verify_urls_match(&self.common.actor, &self.object.common().actor)?;
     }
     Ok(())
@@ -71,7 +72,7 @@ impl ActivityHandler for UndoDeletePostCommentOrCommunity {
     if let Ok(community) = object_community {
       if community.local {
         // repeat these checks just to be sure
-        verify_person_in_community(&self.common().actor, &self.cc, context, request_counter)
+        verify_person_in_community(&self.common().actor, &self.cc[0], context, request_counter)
           .await?;
         verify_mod_action(&self.common.actor, self.object.object.clone(), context).await?;
         let mod_ =
index d9f3dcddef0843a7fe433598b7ab475a66b3f582..af133167f1ee6223394835d825c38124b681dd0a 100644 (file)
@@ -13,9 +13,10 @@ use lemmy_db_schema::{
   DbUrl,
 };
 use lemmy_db_views_actor::community_view::CommunityView;
-use lemmy_utils::LemmyError;
+use lemmy_utils::{settings::structs::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
-use url::Url;
+use url::{ParseError, Url};
+use uuid::Uuid;
 
 pub mod comment;
 pub mod community;
@@ -41,27 +42,34 @@ async fn verify_person(
   Ok(())
 }
 
-/// Fetches the person and community to verify their type, then checks if person is banned from site
-/// or community.
-async fn verify_person_in_community(
-  person_id: &Url,
+pub(crate) async fn extract_community(
   cc: &[Url],
   context: &LemmyContext,
   request_counter: &mut i32,
 ) -> Result<Community, LemmyError> {
-  let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
   let mut cc_iter = cc.iter();
-  let community: Community = loop {
+  loop {
     if let Some(cid) = cc_iter.next() {
       if let Ok(c) = get_or_fetch_and_upsert_community(cid, context, request_counter).await {
-        break c;
+        break Ok(c);
       }
     } else {
       return Err(anyhow!("No community found in cc").into());
     }
-  };
-  check_community_or_site_ban(&person, community.id, context.pool()).await?;
-  Ok(community)
+  }
+}
+
+/// Fetches the person and community to verify their type, then checks if person is banned from site
+/// or community.
+async fn verify_person_in_community(
+  person_id: &Url,
+  community_id: &Url,
+  context: &LemmyContext,
+  request_counter: &mut i32,
+) -> Result<(), LemmyError> {
+  let community = get_or_fetch_and_upsert_community(community_id, context, request_counter).await?;
+  let person = get_or_fetch_and_upsert_person(person_id, context, request_counter).await?;
+  check_community_or_site_ban(&person, community.id, context.pool()).await
 }
 
 /// Simply check that the url actually refers to a valid group.
@@ -80,13 +88,16 @@ fn verify_activity(common: &ActivityCommonFields) -> Result<(), LemmyError> {
   Ok(())
 }
 
-async fn verify_mod_action(
+/// Verify that the actor is a community mod. This check is only run if the community is local,
+/// because in case of remote communities, admins can also perform mod actions. As admin status
+/// is not federated, we cant verify their actions remotely.
+pub(crate) async fn verify_mod_action(
   actor_id: &Url,
-  activity_cc: Url,
+  community: Url,
   context: &LemmyContext,
 ) -> Result<(), LemmyError> {
   let community = blocking(context.pool(), move |conn| {
-    Community::read_from_apub_id(conn, &activity_cc.into())
+    Community::read_from_apub_id(conn, &community.into())
   })
   .await??;
 
@@ -120,3 +131,18 @@ fn verify_add_remove_moderator_target(target: &Url, community: Url) -> Result<()
   }
   Ok(())
 }
+
+/// 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) -> Result<Url, ParseError>
+where
+  T: ToString,
+{
+  let id = format!(
+    "{}/activities/{}/{}",
+    Settings::get().get_protocol_and_hostname(),
+    kind.to_string().to_lowercase(),
+    Uuid::new_v4()
+  );
+  Url::parse(&id)
+}
index a2ccf4ce5c418b09a063b54c2ae8a843fc8d8337..3dd8ce5540134cd8305dfed05f55fd5a8a02c813 100644 (file)
@@ -1,13 +1,30 @@
 use crate::{
-  activities::{post::send_websocket_message, verify_activity, verify_person_in_community},
+  activities::{
+    community::announce::AnnouncableActivities,
+    extract_community,
+    generate_activity_id,
+    post::send_websocket_message,
+    verify_activity,
+    verify_person_in_community,
+  },
+  activity_queue::send_to_community_new,
+  extensions::context::lemmy_context,
   fetcher::person::get_or_fetch_and_upsert_person,
-  objects::FromApub,
+  objects::{post::Page, FromApub, ToApub},
   ActorType,
-  PageExt,
 };
-use activitystreams::{activity::kind::CreateType, base::BaseExt};
-use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
-use lemmy_db_schema::source::post::Post;
+use activitystreams::activity::kind::CreateType;
+use anyhow::anyhow;
+use lemmy_api_common::blocking;
+use lemmy_apub_lib::{
+  values::PublicUrl,
+  verify_domains_match,
+  verify_urls_match,
+  ActivityCommonFields,
+  ActivityHandler,
+};
+use lemmy_db_queries::Crud;
+use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
 use lemmy_utils::LemmyError;
 use lemmy_websocket::{LemmyContext, UserOperationCrud};
 use url::Url;
@@ -16,14 +33,40 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct CreatePost {
   to: PublicUrl,
-  object: PageExt,
-  cc: Vec<Url>,
-  #[serde(rename = "type")]
-  kind: CreateType,
+  object: Page,
+  cc: [Url; 1],
+  r#type: CreateType,
   #[serde(flatten)]
   common: ActivityCommonFields,
 }
 
+impl CreatePost {
+  pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
+    let community_id = post.community_id;
+    let community = blocking(context.pool(), move |conn| {
+      Community::read(conn, community_id)
+    })
+    .await??;
+
+    let id = generate_activity_id(CreateType::Create)?;
+    let create = CreatePost {
+      to: PublicUrl::Public,
+      object: post.to_apub(context.pool()).await?,
+      cc: [community.actor_id()],
+      r#type: Default::default(),
+      common: ActivityCommonFields {
+        context: lemmy_context(),
+        id: id.clone(),
+        actor: actor.actor_id(),
+        unparsed: Default::default(),
+      },
+    };
+
+    let activity = AnnouncableActivities::CreatePost(create);
+    send_to_community_new(activity, &id, actor, &community, vec![], context).await
+  }
+}
+
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for CreatePost {
   async fn verify(
@@ -31,9 +74,23 @@ impl ActivityHandler for CreatePost {
     context: &LemmyContext,
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
+    let community = extract_community(&self.cc, context, request_counter).await?;
+    let community_id = &community.actor_id();
+
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
-    verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+    verify_person_in_community(&self.common.actor, community_id, context, request_counter).await?;
+    verify_domains_match(&self.common.actor, &self.object.id)?;
+    verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
+    // Check that the post isnt locked or stickied, as that isnt possible for newly created posts.
+    // However, when fetching a remote post we generate a new create activity with the current
+    // locked/stickied value, so this check may fail. So only check if its a local community,
+    // because then we will definitely receive all create and update activities separately.
+    let is_stickied_or_locked =
+      self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
+    if community.local && is_stickied_or_locked {
+      return Err(anyhow!("New post cannot be stickied or locked").into());
+    }
+    self.object.verify(context, request_counter).await?;
     Ok(())
   }
 
index 13456dcd8ce761ae20733a63abbd33c060eb44ca..f5cd07b9f69e6576b905be8c5856b039af103b0a 100644 (file)
@@ -1,24 +1,24 @@
 use crate::{
   activities::{
+    community::announce::AnnouncableActivities,
+    generate_activity_id,
     post::send_websocket_message,
     verify_activity,
     verify_mod_action,
     verify_person_in_community,
   },
-  objects::{FromApub, FromApubToForm},
+  activity_queue::send_to_community_new,
+  extensions::context::lemmy_context,
+  fetcher::community::get_or_fetch_and_upsert_community,
+  objects::{post::Page, FromApub, ToApub},
   ActorType,
-  PageExt,
 };
-use activitystreams::{activity::kind::UpdateType, base::BaseExt};
-use anyhow::Context;
+use activitystreams::activity::kind::UpdateType;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{verify_domains_match_opt, ActivityCommonFields, ActivityHandler, PublicUrl};
-use lemmy_db_queries::ApubObject;
-use lemmy_db_schema::{
-  source::post::{Post, PostForm},
-  DbUrl,
-};
-use lemmy_utils::{location_info, LemmyError};
+use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
+use lemmy_db_queries::Crud;
+use lemmy_db_schema::source::{community::Community, person::Person, post::Post};
+use lemmy_utils::LemmyError;
 use lemmy_websocket::{LemmyContext, UserOperationCrud};
 use url::Url;
 
@@ -26,14 +26,39 @@ use url::Url;
 #[serde(rename_all = "camelCase")]
 pub struct UpdatePost {
   to: PublicUrl,
-  object: PageExt,
-  cc: Vec<Url>,
-  #[serde(rename = "type")]
-  kind: UpdateType,
+  object: Page,
+  cc: [Url; 1],
+  r#type: UpdateType,
   #[serde(flatten)]
   common: ActivityCommonFields,
 }
 
+impl UpdatePost {
+  pub async fn send(post: &Post, actor: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
+    let community_id = post.community_id;
+    let community = blocking(context.pool(), move |conn| {
+      Community::read(conn, community_id)
+    })
+    .await??;
+
+    let id = generate_activity_id(UpdateType::Update)?;
+    let update = UpdatePost {
+      to: PublicUrl::Public,
+      object: post.to_apub(context.pool()).await?,
+      cc: [community.actor_id()],
+      r#type: Default::default(),
+      common: ActivityCommonFields {
+        context: lemmy_context(),
+        id: id.clone(),
+        actor: actor.actor_id(),
+        unparsed: Default::default(),
+      },
+    };
+    let activity = AnnouncableActivities::UpdatePost(update);
+    send_to_community_new(activity, &id, actor, &community, vec![], context).await
+  }
+}
+
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for UpdatePost {
   async fn verify(
@@ -41,34 +66,19 @@ impl ActivityHandler for UpdatePost {
     context: &LemmyContext,
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
-    verify_activity(self.common())?;
-    let community =
-      verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    let community_id = get_or_fetch_and_upsert_community(&self.cc[0], context, request_counter)
+      .await?
+      .actor_id();
+    let is_mod_action = self.object.is_mod_action(context.pool()).await?;
 
-    let temp_post = PostForm::from_apub(
-      &self.object,
-      context,
-      self.common.actor.clone(),
-      request_counter,
-      true,
-    )
-    .await?;
-    let post_id: DbUrl = temp_post.ap_id.context(location_info!())?;
-    let old_post = blocking(context.pool(), move |conn| {
-      Post::read_from_apub_id(conn, &post_id)
-    })
-    .await??;
-    let stickied = temp_post.stickied.context(location_info!())?;
-    let locked = temp_post.locked.context(location_info!())?;
-    // community mod changed locked/sticky status
-    if (stickied != old_post.stickied) || (locked != old_post.locked) {
-      verify_mod_action(&self.common.actor, community.actor_id(), context).await?;
-    }
-    // user edited their own post
-    else {
-      verify_domains_match_opt(&self.common.actor, self.object.id_unchecked())?;
+    verify_activity(self.common())?;
+    verify_person_in_community(&self.common.actor, &community_id, context, request_counter).await?;
+    if is_mod_action {
+      verify_mod_action(&self.common.actor, community_id, context).await?;
+    } else {
+      verify_urls_match(&self.common.actor, &self.object.attributed_to)?;
     }
-
+    self.object.verify(context, request_counter).await?;
     Ok(())
   }
 
index a30f23327ffedaaade7a4e49e931b849eca56e7b..053ddadf8a2b55a272887e979613aa99b9c7c2c3 100644 (file)
@@ -19,7 +19,7 @@ use crate::{
 use activitystreams::{activity::kind::RemoveType, base::AnyBase};
 use anyhow::anyhow;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::{
   source::{comment::Comment_, community::Community_, post::Post_},
   Joinable,
@@ -64,13 +64,13 @@ impl ActivityHandler for RemovePostCommentCommunityOrMod {
     }
     // removing community mod
     else if let Some(target) = &self.target {
-      verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+      verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
       verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
       verify_add_remove_moderator_target(target, self.cc[0].clone())?;
     }
     // removing a post or comment
     else {
-      verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+      verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
       verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
     }
     Ok(())
index 997d527d8de9e108ead71553da5546f61b54c298..db4518b27de52a3ebb623093e807efda69457db3 100644 (file)
@@ -17,7 +17,7 @@ use crate::{
 use activitystreams::activity::kind::UndoType;
 use anyhow::anyhow;
 use lemmy_api_common::blocking;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_db_queries::source::{comment::Comment_, community::Community_, post::Post_};
 use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post};
 use lemmy_utils::LemmyError;
@@ -52,7 +52,7 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity {
     }
     // removing a post or comment
     else {
-      verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+      verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
       verify_mod_action(&self.common.actor, self.cc[0].clone(), context).await?;
     }
     self.object.verify(context, request_counter).await?;
index b93f9e1c9b90d2350194321746cffc766f8e495f..47fc2a3e048322e1dac7ce442441dc33ad2703ef 100644 (file)
@@ -1,5 +1,5 @@
 use crate::{
-  activities::send::generate_activity_id,
+  activities::generate_activity_id,
   activity_queue::{send_comment_mentions, send_to_community},
   extensions::context::lemmy_context,
   fetcher::person::get_or_fetch_and_upsert_person,
@@ -64,7 +64,7 @@ impl ApubObjectType for Comment {
       note.into_any_base()?,
     );
     create
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(CreateType::Create)?)
       .set_to(public())
       .set_many_ccs(maa.ccs.to_owned())
@@ -97,7 +97,7 @@ impl ApubObjectType for Comment {
       note.into_any_base()?,
     );
     update
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UpdateType::Update)?)
       .set_to(public())
       .set_many_ccs(maa.ccs.to_owned())
@@ -124,7 +124,7 @@ impl ApubObjectType for Comment {
       self.ap_id.to_owned().into_inner(),
     );
     delete
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -153,7 +153,7 @@ impl ApubObjectType for Comment {
       self.ap_id.to_owned().into_inner(),
     );
     delete
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -164,7 +164,7 @@ impl ApubObjectType for Comment {
       delete.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -188,7 +188,7 @@ impl ApubObjectType for Comment {
       self.ap_id.to_owned().into_inner(),
     );
     remove
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -217,7 +217,7 @@ impl ApubObjectType for Comment {
       self.ap_id.to_owned().into_inner(),
     );
     remove
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -228,7 +228,7 @@ impl ApubObjectType for Comment {
       remove.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -255,7 +255,7 @@ impl ApubLikeableType for Comment {
       self.ap_id.to_owned().into_inner(),
     );
     like
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(LikeType::Like)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -279,7 +279,7 @@ impl ApubLikeableType for Comment {
       self.ap_id.to_owned().into_inner(),
     );
     dislike
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DislikeType::Dislike)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -307,7 +307,7 @@ impl ApubLikeableType for Comment {
       self.ap_id.to_owned().into_inner(),
     );
     like
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DislikeType::Dislike)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -318,7 +318,7 @@ impl ApubLikeableType for Comment {
       like.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
index bf7514797591f8f6914850fced3764b5b3449ea7..fbc61fc05b232c209901cd3443b1a5befe70f272 100644 (file)
@@ -1,5 +1,5 @@
 use crate::{
-  activities::send::generate_activity_id,
+  activities::generate_activity_id,
   activity_queue::{send_activity_single_dest, send_to_community, send_to_community_followers},
   check_is_apub_id_valid,
   extensions::context::lemmy_context,
@@ -98,7 +98,7 @@ impl CommunityType for Community {
       follow.into_any_base()?,
     );
     accept
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(AcceptType::Accept)?)
       .set_to(person.actor_id());
 
@@ -117,7 +117,7 @@ impl CommunityType for Community {
         self.to_apub(context.pool()).await?.into_any_base()?,
       );
       update
-        .set_many_contexts(lemmy_context()?)
+        .set_many_contexts(lemmy_context())
         .set_id(generate_activity_id(UpdateType::Update)?)
         .set_to(public())
         .set_many_ccs(vec![self.actor_id()]);
@@ -134,7 +134,7 @@ impl CommunityType for Community {
     if self.local {
       let mut delete = Delete::new(self.actor_id(), self.actor_id());
       delete
-        .set_many_contexts(lemmy_context()?)
+        .set_many_contexts(lemmy_context())
         .set_id(generate_activity_id(DeleteType::Delete)?)
         .set_to(public())
         .set_many_ccs(vec![self.followers_url()]);
@@ -145,7 +145,7 @@ impl CommunityType for Community {
     else {
       let mut delete = Delete::new(mod_.actor_id(), self.actor_id());
       delete
-        .set_many_contexts(lemmy_context()?)
+        .set_many_contexts(lemmy_context())
         .set_id(generate_activity_id(DeleteType::Delete)?)
         .set_to(public())
         .set_many_ccs(vec![self.actor_id()]);
@@ -163,14 +163,14 @@ impl CommunityType for Community {
     if self.local {
       let mut delete = Delete::new(self.actor_id(), self.actor_id());
       delete
-        .set_many_contexts(lemmy_context()?)
+        .set_many_contexts(lemmy_context())
         .set_id(generate_activity_id(DeleteType::Delete)?)
         .set_to(public())
         .set_many_ccs(vec![self.followers_url()]);
 
       let mut undo = Undo::new(self.actor_id(), delete.into_any_base()?);
       undo
-        .set_many_contexts(lemmy_context()?)
+        .set_many_contexts(lemmy_context())
         .set_id(generate_activity_id(UndoType::Undo)?)
         .set_to(public())
         .set_many_ccs(vec![self.followers_url()]);
@@ -181,14 +181,14 @@ impl CommunityType for Community {
     else {
       let mut delete = Delete::new(mod_.actor_id(), self.actor_id());
       delete
-        .set_many_contexts(lemmy_context()?)
+        .set_many_contexts(lemmy_context())
         .set_id(generate_activity_id(DeleteType::Delete)?)
         .set_to(public())
         .set_many_ccs(vec![self.actor_id()]);
 
       let mut undo = Undo::new(mod_.actor_id(), delete.into_any_base()?);
       undo
-        .set_many_contexts(lemmy_context()?)
+        .set_many_contexts(lemmy_context())
         .set_id(generate_activity_id(UndoType::Undo)?)
         .set_to(public())
         .set_many_ccs(vec![self.actor_id()]);
@@ -202,7 +202,7 @@ impl CommunityType for Community {
   async fn send_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> {
     let mut remove = Remove::new(self.actor_id(), self.actor_id());
     remove
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
       .set_many_ccs(vec![self.followers_url()]);
@@ -215,7 +215,7 @@ impl CommunityType for Community {
   async fn send_undo_remove(&self, context: &LemmyContext) -> Result<(), LemmyError> {
     let mut remove = Remove::new(self.actor_id(), self.actor_id());
     remove
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
       .set_many_ccs(vec![self.followers_url()]);
@@ -223,7 +223,7 @@ impl CommunityType for Community {
     // Undo that fake activity
     let mut undo = Undo::new(self.actor_id(), remove.into_any_base()?);
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(LikeType::Like)?)
       .set_to(public())
       .set_many_ccs(vec![self.followers_url()]);
@@ -267,7 +267,7 @@ impl CommunityType for Community {
     }
     let mut announce = Announce::new(self.actor_id(), activity);
     announce
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(AnnounceType::Announce)?)
       .set_to(public())
       .set_many_ccs(ccs);
@@ -306,7 +306,7 @@ impl CommunityType for Community {
   ) -> Result<(), LemmyError> {
     let mut add = Add::new(actor.actor_id(), added_mod.actor_id());
     add
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(AddType::Add)?)
       .set_to(public())
       .set_many_ccs(vec![self.actor_id()])
@@ -324,7 +324,7 @@ impl CommunityType for Community {
   ) -> Result<(), LemmyError> {
     let mut remove = Remove::new(actor.actor_id(), removed_mod.actor_id());
     remove
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
       .set_many_ccs(vec![self.actor_id()])
@@ -342,7 +342,7 @@ impl CommunityType for Community {
   ) -> Result<(), LemmyError> {
     let mut block = Block::new(actor.actor_id(), blocked_user.actor_id());
     block
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(BlockType::Block)?)
       .set_to(public())
       .set_many_ccs(vec![self.actor_id()]);
@@ -359,7 +359,7 @@ impl CommunityType for Community {
   ) -> Result<(), LemmyError> {
     let mut block = Block::new(actor.actor_id(), unblocked_user.actor_id());
     block
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(BlockType::Block)?)
       .set_to(public())
       .set_many_ccs(vec![self.actor_id()]);
@@ -367,7 +367,7 @@ impl CommunityType for Community {
     // Undo that fake activity
     let mut undo = Undo::new(actor.actor_id(), block.into_any_base()?);
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(public())
       .set_many_ccs(vec![self.actor_id()]);
index 10dd8a26398b73fbf4ec48c9efc4b1e67d6f42c7..65135bddaca339269d6b2f719b5294fa82d14e95 100644 (file)
@@ -1,24 +1,5 @@
-use lemmy_utils::settings::structs::Settings;
-use url::{ParseError, Url};
-use uuid::Uuid;
-
 pub(crate) mod comment;
 pub(crate) mod community;
 pub(crate) mod person;
 pub(crate) mod post;
 pub(crate) mod private_message;
-
-/// 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) -> Result<Url, ParseError>
-where
-  T: ToString,
-{
-  let id = format!(
-    "{}/activities/{}/{}",
-    Settings::get().get_protocol_and_hostname(),
-    kind.to_string().to_lowercase(),
-    Uuid::new_v4()
-  );
-  Url::parse(&id)
-}
index b1fc0cd2bf2e85cae6fba2d29e947b6dd2d5490b..b7b43c433d9d9eb2e2ccaa4c4d04c0cfdb117e4c 100644 (file)
@@ -1,5 +1,5 @@
 use crate::{
-  activities::send::generate_activity_id,
+  activities::generate_activity_id,
   activity_queue::send_activity_single_dest,
   extensions::context::lemmy_context,
   ActorType,
@@ -78,7 +78,7 @@ impl UserType for Person {
 
     let mut follow = Follow::new(self.actor_id(), community.actor_id());
     follow
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(FollowType::Follow)?)
       .set_to(community.actor_id());
 
@@ -99,7 +99,7 @@ impl UserType for Person {
 
     let mut follow = Follow::new(self.actor_id(), community.actor_id());
     follow
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(FollowType::Follow)?)
       .set_to(community.actor_id());
 
@@ -109,7 +109,7 @@ impl UserType for Person {
       follow.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(community.actor_id());
 
index c51d6f2d6b27eb3b0671e44888f03542b51dd0d2..677e7845b99d44ef756778893c7d785fa2d75585 100644 (file)
@@ -1,22 +1,19 @@
 use crate::{
-  activities::send::generate_activity_id,
+  activities::generate_activity_id,
   activity_queue::send_to_community,
   extensions::context::lemmy_context,
-  objects::ToApub,
   ActorType,
   ApubLikeableType,
   ApubObjectType,
 };
 use activitystreams::{
   activity::{
-    kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
-    Create,
+    kind::{DeleteType, DislikeType, LikeType, RemoveType, UndoType},
     Delete,
     Dislike,
     Like,
     Remove,
     Undo,
-    Update,
   },
   prelude::*,
   public,
@@ -29,52 +26,20 @@ use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl ApubObjectType for Post {
-  /// Send out information about a newly created post, to the followers of the community.
-  async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
-    let page = self.to_apub(context.pool()).await?;
-
-    let community_id = self.community_id;
-    let community = blocking(context.pool(), move |conn| {
-      Community::read(conn, community_id)
-    })
-    .await??;
-
-    let mut create = Create::new(
-      creator.actor_id.to_owned().into_inner(),
-      page.into_any_base()?,
-    );
-    create
-      .set_many_contexts(lemmy_context()?)
-      .set_id(generate_activity_id(CreateType::Create)?)
-      .set_to(public())
-      .set_many_ccs(vec![community.actor_id()]);
-
-    send_to_community(create, creator, &community, None, context).await?;
-    Ok(())
+  async fn send_create(
+    &self,
+    _creator: &Person,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
   }
 
-  /// Send out information about an edited post, to the followers of the community.
-  async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
-    let page = self.to_apub(context.pool()).await?;
-
-    let community_id = self.community_id;
-    let community = blocking(context.pool(), move |conn| {
-      Community::read(conn, community_id)
-    })
-    .await??;
-
-    let mut update = Update::new(
-      creator.actor_id.to_owned().into_inner(),
-      page.into_any_base()?,
-    );
-    update
-      .set_many_contexts(lemmy_context()?)
-      .set_id(generate_activity_id(UpdateType::Update)?)
-      .set_to(public())
-      .set_many_ccs(vec![community.actor_id()]);
-
-    send_to_community(update, creator, &community, None, context).await?;
-    Ok(())
+  async fn send_update(
+    &self,
+    _creator: &Person,
+    _context: &LemmyContext,
+  ) -> Result<(), LemmyError> {
+    unimplemented!()
   }
 
   async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
@@ -89,7 +54,7 @@ impl ApubObjectType for Post {
       self.ap_id.to_owned().into_inner(),
     );
     delete
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -114,7 +79,7 @@ impl ApubObjectType for Post {
       self.ap_id.to_owned().into_inner(),
     );
     delete
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -125,7 +90,7 @@ impl ApubObjectType for Post {
       delete.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -146,7 +111,7 @@ impl ApubObjectType for Post {
       self.ap_id.to_owned().into_inner(),
     );
     remove
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -171,7 +136,7 @@ impl ApubObjectType for Post {
       self.ap_id.to_owned().into_inner(),
     );
     remove
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(RemoveType::Remove)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -182,7 +147,7 @@ impl ApubObjectType for Post {
       remove.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -206,7 +171,7 @@ impl ApubLikeableType for Post {
       self.ap_id.to_owned().into_inner(),
     );
     like
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(LikeType::Like)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -227,7 +192,7 @@ impl ApubLikeableType for Post {
       self.ap_id.to_owned().into_inner(),
     );
     dislike
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DislikeType::Dislike)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -252,7 +217,7 @@ impl ApubLikeableType for Post {
       self.ap_id.to_owned().into_inner(),
     );
     like
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(LikeType::Like)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
@@ -263,7 +228,7 @@ impl ApubLikeableType for Post {
       like.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(public())
       .set_many_ccs(vec![community.actor_id()]);
index e5a30585b9a4447eb5cb602984139b651d75078c..67d0afcd31051066964cce2344c2b27c6443e784 100644 (file)
@@ -1,5 +1,5 @@
 use crate::{
-  activities::send::generate_activity_id,
+  activities::generate_activity_id,
   activity_queue::send_activity_single_dest,
   extensions::context::lemmy_context,
   objects::ToApub,
@@ -38,7 +38,7 @@ impl ApubObjectType for PrivateMessage {
     );
 
     create
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(CreateType::Create)?)
       .set_to(recipient.actor_id());
 
@@ -59,7 +59,7 @@ impl ApubObjectType for PrivateMessage {
       note.into_any_base()?,
     );
     update
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UpdateType::Update)?)
       .set_to(recipient.actor_id());
 
@@ -77,7 +77,7 @@ impl ApubObjectType for PrivateMessage {
       self.ap_id.to_owned().into_inner(),
     );
     delete
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(recipient.actor_id());
 
@@ -99,7 +99,7 @@ impl ApubObjectType for PrivateMessage {
       self.ap_id.to_owned().into_inner(),
     );
     delete
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(DeleteType::Delete)?)
       .set_to(recipient.actor_id());
 
@@ -109,7 +109,7 @@ impl ApubObjectType for PrivateMessage {
       delete.into_any_base()?,
     );
     undo
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(generate_activity_id(UndoType::Undo)?)
       .set_to(recipient.actor_id());
 
index 18d72f39422ecb291a17de1a2e5d49386394d440..54b673008aa65037dad43a5609365bc8b57d0254 100644 (file)
@@ -4,7 +4,7 @@ use crate::activities::{
   voting::receive_like_or_dislike,
 };
 use activitystreams::activity::kind::DislikeType;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 use url::Url;
@@ -29,7 +29,7 @@ impl ActivityHandler for DislikePostOrComment {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     Ok(())
   }
 
index ca899d3d3742d0aabd9681ca8985a47ec94025e1..90f29c427a63a194147c30f563f960dfe90c69a0 100644 (file)
@@ -4,7 +4,7 @@ use crate::activities::{
   voting::receive_like_or_dislike,
 };
 use activitystreams::activity::kind::LikeType;
-use lemmy_apub_lib::{ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, ActivityCommonFields, ActivityHandler};
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 use url::Url;
@@ -29,7 +29,7 @@ impl ActivityHandler for LikePostOrComment {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     Ok(())
   }
 
index 11871e79168cbe3c09d0e90f12275cebb0a3ab53..13e3e1f7685ab4df46b72681ef876d8cfcbe7de0 100644 (file)
@@ -4,7 +4,7 @@ use crate::activities::{
   voting::{dislike::DislikePostOrComment, receive_undo_like_or_dislike},
 };
 use activitystreams::activity::kind::UndoType;
-use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 use url::Url;
@@ -29,7 +29,7 @@ impl ActivityHandler for UndoDislikePostOrComment {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     verify_urls_match(&self.common.actor, &self.object.common().actor)?;
     self.object.verify(context, request_counter).await?;
     Ok(())
index 07c3c4709d049d5d80fb6787553bef64dbeb276b..ab15da70b31694ceb3e224f4a94d2f3fcc94cd68 100644 (file)
@@ -4,7 +4,7 @@ use crate::activities::{
   voting::{like::LikePostOrComment, receive_undo_like_or_dislike},
 };
 use activitystreams::activity::kind::UndoType;
-use lemmy_apub_lib::{verify_urls_match, ActivityCommonFields, ActivityHandler, PublicUrl};
+use lemmy_apub_lib::{values::PublicUrl, verify_urls_match, ActivityCommonFields, ActivityHandler};
 use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 use url::Url;
@@ -29,7 +29,7 @@ impl ActivityHandler for UndoLikePostOrComment {
     request_counter: &mut i32,
   ) -> Result<(), LemmyError> {
     verify_activity(self.common())?;
-    verify_person_in_community(&self.common.actor, &self.cc, context, request_counter).await?;
+    verify_person_in_community(&self.common.actor, &self.cc[0], context, request_counter).await?;
     verify_urls_match(&self.common.actor, &self.object.common().actor)?;
     self.object.verify(context, request_counter).await?;
     Ok(())
index 22b88d148b40d13a07a1fe63001d4f36b799114a..f4f8ce90a62e5941d83a00dde78447c9054fcbee 100644 (file)
@@ -1,4 +1,5 @@
 use crate::{
+  activities::community::announce::{AnnouncableActivities, AnnounceActivity},
   check_is_apub_id_valid,
   extensions::signatures::sign_and_send,
   insert_activity,
@@ -24,7 +25,7 @@ use itertools::Itertools;
 use lemmy_db_schema::source::{community::Community, person::Person};
 use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
 use lemmy_websocket::LemmyContext;
-use log::{debug, warn};
+use log::{debug, info, warn};
 use reqwest::Client;
 use serde::{Deserialize, Serialize};
 use std::{collections::BTreeMap, env, fmt::Debug, future::Future, pin::Pin};
@@ -171,6 +172,80 @@ where
   Ok(())
 }
 
+pub(crate) async fn send_to_community_new(
+  activity: AnnouncableActivities,
+  activity_id: &Url,
+  actor: &dyn ActorType,
+  community: &Community,
+  additional_inboxes: Vec<Url>,
+  context: &LemmyContext,
+) -> Result<(), LemmyError> {
+  // if this is a local community, we need to do an announce from the community instead
+  if community.local {
+    insert_activity(activity_id, activity.clone(), true, false, context.pool()).await?;
+    AnnounceActivity::send(activity, community, additional_inboxes, context).await?;
+  } else {
+    let mut inboxes = additional_inboxes;
+    inboxes.push(community.get_shared_inbox_or_inbox_url());
+    send_activity_new(context, &activity, activity_id, actor, inboxes, false).await?;
+  }
+
+  Ok(())
+}
+
+pub(crate) async fn send_activity_new<T>(
+  context: &LemmyContext,
+  activity: &T,
+  activity_id: &Url,
+  actor: &dyn ActorType,
+  inboxes: Vec<Url>,
+  sensitive: bool,
+) -> Result<(), LemmyError>
+where
+  T: Serialize,
+{
+  if !Settings::get().federation().enabled || inboxes.is_empty() {
+    return Ok(());
+  }
+
+  info!("Sending activity {}", activity_id.to_string());
+
+  // Don't send anything to ourselves
+  // TODO: this should be a debug assert
+  let hostname = Settings::get().get_hostname_without_port()?;
+  let inboxes: Vec<&Url> = inboxes
+    .iter()
+    .filter(|i| i.domain().expect("valid inbox url") != hostname)
+    .collect();
+
+  let serialised_activity = serde_json::to_string(&activity)?;
+
+  insert_activity(
+    activity_id,
+    serialised_activity.clone(),
+    true,
+    sensitive,
+    context.pool(),
+  )
+  .await?;
+
+  for i in inboxes {
+    let message = SendActivityTask {
+      activity: serialised_activity.to_owned(),
+      inbox: i.to_owned(),
+      actor_id: actor.actor_id(),
+      private_key: actor.private_key().context(location_info!())?,
+    };
+    if env::var("LEMMY_TEST_SEND_SYNC").is_ok() {
+      do_send(message, &Client::default()).await?;
+    } else {
+      context.activity_queue.queue::<SendActivityTask>(message)?;
+    }
+  }
+
+  Ok(())
+}
+
 /// Create new `SendActivityTasks`, which will deliver the given activity to inboxes, as well as
 /// handling signing and retrying failed deliveres.
 ///
index 08491dcbfd0491277d0e5383b23b8627761758c4..1d6f83bd74df9519d3454355cf506aeb18abc897 100644 (file)
@@ -1,9 +1,8 @@
-use activitystreams::{base::AnyBase, context};
-use lemmy_utils::LemmyError;
+use activitystreams::{base::AnyBase, context, primitives::OneOrMany};
 use serde_json::json;
 use url::Url;
 
-pub fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
+pub fn lemmy_context() -> OneOrMany<AnyBase> {
   let context_ext = AnyBase::from_arbitrary_json(json!(
   {
     "sc": "http://schema.org#",
@@ -19,10 +18,11 @@ pub fn lemmy_context() -> Result<Vec<AnyBase>, LemmyError> {
       "type": "sc:Text",
       "id": "as:alsoKnownAs"
     },
-  }))?;
-  Ok(vec![
+  }))
+  .expect("parse context");
+  OneOrMany::from(vec![
     AnyBase::from(context()),
     context_ext,
-    AnyBase::from(Url::parse("https://w3id.org/security/v1")?),
+    AnyBase::from(Url::parse("https://w3id.org/security/v1").expect("parse context")),
   ])
 }
index 19e37894d616bf5afbeb2fe422f24fdcb537e2ab..781e89e6ec5541147730426a9b4df1f0eabba328 100644 (file)
@@ -1,5 +1,4 @@
 pub mod context;
 pub(crate) mod group_extension;
-pub(crate) mod page_extension;
 pub(crate) mod person_extension;
 pub mod signatures;
diff --git a/crates/apub/src/extensions/page_extension.rs b/crates/apub/src/extensions/page_extension.rs
deleted file mode 100644 (file)
index 752fa2b..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-use activitystreams::unparsed::UnparsedMutExt;
-use activitystreams_ext::UnparsedExtension;
-use serde::{Deserialize, Serialize};
-
-/// Activitystreams extension to allow (de)serializing additional Post fields
-/// `comemnts_enabled` (called 'locked' in Lemmy),
-/// `sensitive` (called 'nsfw') and `stickied`.
-#[derive(Clone, Debug, Default, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct PageExtension {
-  pub comments_enabled: Option<bool>,
-  pub sensitive: Option<bool>,
-  pub stickied: Option<bool>,
-}
-
-impl<U> UnparsedExtension<U> for PageExtension
-where
-  U: UnparsedMutExt,
-{
-  type Error = serde_json::Error;
-
-  fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
-    Ok(PageExtension {
-      comments_enabled: unparsed_mut.remove("commentsEnabled")?,
-      sensitive: unparsed_mut.remove("sensitive")?,
-      stickied: unparsed_mut.remove("stickied")?,
-    })
-  }
-
-  fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
-    unparsed_mut.insert("commentsEnabled", self.comments_enabled)?;
-    unparsed_mut.insert("sensitive", self.sensitive)?;
-    unparsed_mut.insert("stickied", self.stickied)?;
-    Ok(())
-  }
-}
index af8a59f764233bcd8ec7e912342798f99949aa69..fd94f6499575a02df3ce1fedb6e61e301bbc8325 100644 (file)
@@ -1,8 +1,7 @@
 use crate::{
   fetcher::fetch::fetch_remote_object,
-  objects::FromApub,
+  objects::{post::Page, FromApub},
   NoteExt,
-  PageExt,
   PostOrComment,
 };
 use anyhow::anyhow;
@@ -35,7 +34,7 @@ pub async fn get_or_fetch_and_insert_post(
     Err(NotFound {}) => {
       debug!("Fetching and creating remote post: {}", post_ap_id);
       let page =
-        fetch_remote_object::<PageExt>(context.client(), post_ap_id, recursion_counter).await?;
+        fetch_remote_object::<Page>(context.client(), post_ap_id, recursion_counter).await?;
       let post = Post::from_apub(
         &page,
         context,
index 5a09fd4307532e09d71b66d02592b07a4715b360..69076c15132ad533b407c8410975f1363defb8db 100644 (file)
@@ -6,11 +6,10 @@ use crate::{
     is_deleted,
   },
   find_object_by_id,
-  objects::FromApub,
+  objects::{post::Page, FromApub},
   GroupExt,
   NoteExt,
   Object,
-  PageExt,
   PersonExt,
 };
 use activitystreams::base::BaseExt;
@@ -46,7 +45,7 @@ use url::Url;
 enum SearchAcceptedObjects {
   Person(Box<PersonExt>),
   Group(Box<GroupExt>),
-  Page(Box<PageExt>),
+  Page(Box<Page>),
   Comment(Box<NoteExt>),
 }
 
index 3173bca12c75432b0bb4201b738590a112b2b966..6b264fad6aadca549a6abf9e0228e69d175639f0 100644 (file)
@@ -81,7 +81,7 @@ pub(crate) async fn get_apub_community_followers(
 
   let mut collection = UnorderedCollection::new();
   collection
-    .set_many_contexts(lemmy_context()?)
+    .set_many_contexts(lemmy_context())
     .set_id(community.followers_url.into())
     .set_total_items(community_followers.len() as u64);
   Ok(create_apub_response(&collection))
@@ -112,7 +112,7 @@ pub(crate) async fn get_apub_community_outbox(
   let mut collection = OrderedCollection::new();
   collection
     .set_many_items(activities)
-    .set_many_contexts(lemmy_context()?)
+    .set_many_contexts(lemmy_context())
     .set_id(community.get_outbox_url()?)
     .set_total_items(len as u64);
   Ok(create_apub_response(&collection))
@@ -130,7 +130,7 @@ pub(crate) async fn get_apub_community_inbox(
   let mut collection = OrderedCollection::new();
   collection
     .set_id(community.inbox_url.into())
-    .set_many_contexts(lemmy_context()?);
+    .set_many_contexts(lemmy_context());
   Ok(create_apub_response(&collection))
 }
 
@@ -162,6 +162,6 @@ pub(crate) async fn get_apub_community_moderators(
     .set_id(generate_moderators_url(&community.actor_id)?.into())
     .set_total_items(moderators.len() as u64)
     .set_many_items(moderators)
-    .set_many_contexts(lemmy_context()?);
+    .set_many_contexts(lemmy_context());
   Ok(create_apub_response(&collection))
 }
index 2c96aafd8fca32a71648178dc69f4110ed28c7bb..42f25bb629788c6f5e0fc0c8e039bbf6cbd6f54f 100644 (file)
@@ -71,7 +71,7 @@ pub(crate) async fn get_apub_person_outbox(
   let mut collection = OrderedCollection::new();
   collection
     .set_many_items(Vec::<Url>::new())
-    .set_many_contexts(lemmy_context()?)
+    .set_many_contexts(lemmy_context())
     .set_id(person.get_outbox_url()?)
     .set_total_items(0_u64);
   Ok(create_apub_response(&collection))
@@ -89,6 +89,6 @@ pub(crate) async fn get_apub_person_inbox(
   let mut collection = OrderedCollection::new();
   collection
     .set_id(person.inbox_url.into())
-    .set_many_contexts(lemmy_context()?);
+    .set_many_contexts(lemmy_context());
   Ok(create_apub_response(&collection))
 }
index 1b1e3d37f690a1a53a6b92c0487757c53e3f0342..94da33cc6145bf1c07ece417e75a8a80852a09a6 100644 (file)
@@ -11,7 +11,6 @@ pub mod objects;
 use crate::{
   extensions::{
     group_extension::GroupExtension,
-    page_extension::PageExtension,
     person_extension::PersonExtension,
     signatures::{PublicKey, PublicKeyExtension},
   },
@@ -21,9 +20,9 @@ use activitystreams::{
   activity::Follow,
   actor,
   base::AnyBase,
-  object::{ApObject, AsObject, Note, ObjectExt, Page},
+  object::{ApObject, AsObject, Note, ObjectExt},
 };
-use activitystreams_ext::{Ext1, Ext2};
+use activitystreams_ext::Ext2;
 use anyhow::{anyhow, Context};
 use diesel::NotFound;
 use lemmy_api_common::blocking;
@@ -54,8 +53,6 @@ pub type GroupExt =
 type PersonExt =
   Ext2<actor::ApActor<ApObject<actor::Actor<UserTypes>>>, PersonExtension, PublicKeyExtension>;
 pub type SiteExt = actor::ApActor<ApObject<actor::Service>>;
-/// Activitystreams type for post
-pub type PageExt = Ext1<ApObject<Page>, PageExtension>;
 pub type NoteExt = ApObject<Note>;
 
 #[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq)]
index 7b181eff4e9a326ffdf83a322b5ff7346d286308..8501ea2a73d9ac59888413cf98f74e25840a432d 100644 (file)
@@ -69,7 +69,7 @@ impl ToApub for Comment {
 
     comment
       // Not needed when the Post is embedded in a collection (like for community outbox)
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(self.ap_id.to_owned().into_inner())
       .set_published(convert_datetime(self.published))
       // NOTE: included community id for compatibility with lemmy v0.9.9
index 34a15793b9195aaa5d11101f4244af0f5ed20987..f9948588d5e9b6b322680ee1d3f8ee35fcf2f31d 100644 (file)
@@ -55,7 +55,7 @@ impl ToApub for Community {
 
     let mut group = ApObject::new(Group::new());
     group
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(self.actor_id.to_owned().into())
       .set_name(self.title.to_owned())
       .set_published(convert_datetime(self.published))
index c87fc043c97ed16311ef23193a5ff2d926e6b0e7..7191a4a1b0e9fbab401b1f3dbe4a84a04f333775 100644 (file)
@@ -12,6 +12,7 @@ use activitystreams::{
 use anyhow::{anyhow, Context};
 use chrono::NaiveDateTime;
 use lemmy_api_common::blocking;
+use lemmy_apub_lib::values::MediaTypeMarkdown;
 use lemmy_db_queries::{ApubObject, Crud, DbPool};
 use lemmy_db_schema::{CommunityId, DbUrl};
 use lemmy_utils::{
@@ -70,6 +71,13 @@ pub trait FromApubToForm<ApubType> {
     Self: Sized;
 }
 
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Source {
+  content: String,
+  media_type: MediaTypeMarkdown,
+}
+
 /// Updated is actually the deletion time
 fn create_tombstone<T>(
   deleted: bool,
index a508f4e088e872bb785dce1a3f659514a9527500..29935ec074adda9d3c16c3a3d9301b75f3234363 100644 (file)
@@ -50,7 +50,7 @@ impl ToApub for DbPerson {
     let mut person = ApObject::new(actor);
 
     person
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(self.actor_id.to_owned().into_inner())
       .set_published(convert_datetime(self.published));
 
index 19bfe8aabae631405bf32a71b2286bef86f68a39..a7a6cfe93e542a39e2c5a59be929f7a66d1ed001 100644 (file)
@@ -1,30 +1,26 @@
 use crate::{
-  check_is_apub_id_valid,
-  extensions::{context::lemmy_context, page_extension::PageExtension},
+  activities::extract_community,
+  extensions::context::lemmy_context,
   fetcher::person::get_or_fetch_and_upsert_person,
-  get_community_from_to_or_cc,
-  objects::{
-    check_object_domain,
-    check_object_for_community_or_site_ban,
-    create_tombstone,
-    get_object_from_apub,
-    get_source_markdown_value,
-    set_content_and_source,
-    FromApub,
-    FromApubToForm,
-    ToApub,
-  },
-  PageExt,
+  objects::{create_tombstone, FromApub, Source, ToApub},
 };
 use activitystreams::{
-  object::{kind::PageType, ApObject, Image, Page, Tombstone},
-  prelude::*,
+  base::AnyBase,
+  object::{
+    kind::{ImageType, PageType},
+    Tombstone,
+  },
+  primitives::OneOrMany,
   public,
+  unparsed::Unparsed,
 };
-use activitystreams_ext::Ext1;
-use anyhow::Context;
+use chrono::{DateTime, FixedOffset};
 use lemmy_api_common::blocking;
-use lemmy_db_queries::{Crud, DbPool};
+use lemmy_apub_lib::{
+  values::{MediaTypeHtml, MediaTypeMarkdown},
+  verify_domains_match,
+};
+use lemmy_db_queries::{ApubObject, Crud, DbPool};
 use lemmy_db_schema::{
   self,
   source::{
@@ -34,66 +30,117 @@ use lemmy_db_schema::{
   },
 };
 use lemmy_utils::{
-  location_info,
   request::fetch_iframely_and_pictrs_data,
-  utils::{check_slurs, convert_datetime, remove_slurs},
+  utils::{check_slurs, convert_datetime, markdown_to_html, remove_slurs},
   LemmyError,
 };
 use lemmy_websocket::LemmyContext;
 use url::Url;
 
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Page {
+  #[serde(rename = "@context")]
+  context: OneOrMany<AnyBase>,
+  r#type: PageType,
+  pub(crate) id: Url,
+  pub(crate) attributed_to: Url,
+  to: [Url; 2],
+  name: String,
+  content: Option<String>,
+  media_type: MediaTypeHtml,
+  source: Option<Source>,
+  url: Option<Url>,
+  image: Option<ImageObject>,
+  pub(crate) comments_enabled: Option<bool>,
+  sensitive: Option<bool>,
+  pub(crate) stickied: Option<bool>,
+  published: DateTime<FixedOffset>,
+  updated: Option<DateTime<FixedOffset>>,
+
+  // unparsed fields
+  #[serde(flatten)]
+  unparsed: Unparsed,
+}
+
+#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ImageObject {
+  content: ImageType,
+  url: Url,
+}
+
+impl Page {
+  /// Only mods can change the post's stickied/locked status. So if either of these is changed from
+  /// the current value, it is a mod action and needs to be verified as such.
+  ///
+  /// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]].
+  pub(crate) async fn is_mod_action(&self, pool: &DbPool) -> Result<bool, LemmyError> {
+    let post_id = self.id.clone();
+    let old_post = blocking(pool, move |conn| {
+      Post::read_from_apub_id(conn, &post_id.into())
+    })
+    .await?;
+
+    let is_mod_action = if let Ok(old_post) = old_post {
+      self.stickied != Some(old_post.stickied) || self.comments_enabled != Some(!old_post.locked)
+    } else {
+      false
+    };
+    Ok(is_mod_action)
+  }
+
+  pub(crate) async fn verify(
+    &self,
+    _context: &LemmyContext,
+    _request_counter: &mut i32,
+  ) -> Result<(), LemmyError> {
+    check_slurs(&self.name)?;
+    verify_domains_match(&self.attributed_to, &self.id)?;
+    Ok(())
+  }
+}
+
 #[async_trait::async_trait(?Send)]
 impl ToApub for Post {
-  type ApubType = PageExt;
+  type ApubType = Page;
 
   // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
-  async fn to_apub(&self, pool: &DbPool) -> Result<PageExt, LemmyError> {
-    let mut page = ApObject::new(Page::new());
-
+  async fn to_apub(&self, pool: &DbPool) -> Result<Page, LemmyError> {
     let creator_id = self.creator_id;
     let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
-
     let community_id = self.community_id;
     let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??;
 
-    page
-      // Not needed when the Post is embedded in a collection (like for community outbox)
-      // TODO: need to set proper context defining sensitive/commentsEnabled fields
-      // https://git.asonix.dog/Aardwolf/activitystreams/issues/5
-      .set_many_contexts(lemmy_context()?)
-      .set_id(self.ap_id.to_owned().into_inner())
-      .set_name(self.name.to_owned())
-      // `summary` field for compatibility with lemmy v0.9.9 and older,
-      // TODO: remove this after some time
-      .set_summary(self.name.to_owned())
-      .set_published(convert_datetime(self.published))
-      .set_many_tos(vec![community.actor_id.into_inner(), public()])
-      .set_attributed_to(creator.actor_id.into_inner());
-
-    if let Some(body) = &self.body {
-      set_content_and_source(&mut page, body)?;
-    }
-
-    if let Some(url) = &self.url {
-      page.set_url::<Url>(url.to_owned().into());
-    }
-
-    if let Some(thumbnail_url) = &self.thumbnail_url {
-      let mut image = Image::new();
-      image.set_url::<Url>(thumbnail_url.to_owned().into());
-      page.set_image(image.into_any_base()?);
-    }
-
-    if let Some(u) = self.updated {
-      page.set_updated(convert_datetime(u));
-    }
-
-    let ext = PageExtension {
+    let source = self.body.clone().map(|body| Source {
+      content: body,
+      media_type: MediaTypeMarkdown::Markdown,
+    });
+    let image = self.thumbnail_url.clone().map(|thumb| ImageObject {
+      content: ImageType::Image,
+      url: thumb.into(),
+    });
+
+    let page = Page {
+      context: lemmy_context(),
+      r#type: PageType::Page,
+      id: self.ap_id.clone().into(),
+      attributed_to: creator.actor_id.into(),
+      to: [community.actor_id.into(), public()],
+      name: self.name.clone(),
+      content: self.body.as_ref().map(|b| markdown_to_html(b)),
+      media_type: MediaTypeHtml::Html,
+      source,
+      url: self.url.clone().map(|u| u.into()),
+      image,
       comments_enabled: Some(!self.locked),
       sensitive: Some(self.nsfw),
       stickied: Some(self.stickied),
+      published: convert_datetime(self.published),
+      updated: self.updated.map(convert_datetime),
+      unparsed: Default::default(),
     };
-    Ok(Ext1::new(page, ext))
+    Ok(page)
   }
 
   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
@@ -108,136 +155,48 @@ impl ToApub for Post {
 
 #[async_trait::async_trait(?Send)]
 impl FromApub for Post {
-  type ApubType = PageExt;
+  type ApubType = Page;
 
-  /// Converts a `PageExt` to `PostForm`.
-  ///
-  /// If the post's community or creator are not known locally, these are also fetched.
   async fn from_apub(
-    page: &PageExt,
+    page: &Page,
     context: &LemmyContext,
-    expected_domain: Url,
+    _expected_domain: Url,
     request_counter: &mut i32,
-    mod_action_allowed: bool,
+    _mod_action_allowed: bool,
   ) -> Result<Post, LemmyError> {
-    let post: Post = get_object_from_apub(
-      page,
-      context,
-      expected_domain,
-      request_counter,
-      mod_action_allowed,
-    )
-    .await?;
-    check_object_for_community_or_site_ban(page, post.community_id, context, request_counter)
-      .await?;
-    Ok(post)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl FromApubToForm<PageExt> for PostForm {
-  async fn from_apub(
-    page: &PageExt,
-    context: &LemmyContext,
-    expected_domain: Url,
-    request_counter: &mut i32,
-    mod_action_allowed: bool,
-  ) -> Result<PostForm, LemmyError> {
-    let community = get_community_from_to_or_cc(page, context, request_counter).await?;
-    let ap_id = if mod_action_allowed {
-      let id = page.id_unchecked().context(location_info!())?;
-      check_is_apub_id_valid(id, community.local)?;
-      id.to_owned().into()
-    } else {
-      check_object_domain(page, expected_domain, community.local)?
-    };
-    let ext = &page.ext_one;
-    let creator_actor_id = page
-      .inner
-      .attributed_to()
-      .as_ref()
-      .context(location_info!())?
-      .as_single_xsd_any_uri()
-      .context(location_info!())?;
-
     let creator =
-      get_or_fetch_and_upsert_person(creator_actor_id, context, request_counter).await?;
-
-    let thumbnail_url: Option<Url> = match &page.inner.image() {
-      Some(any_image) => Image::from_any_base(
-        any_image
-          .to_owned()
-          .as_one()
-          .context(location_info!())?
-          .to_owned(),
-      )?
-      .context(location_info!())?
-      .url()
-      .context(location_info!())?
-      .as_single_xsd_any_uri()
-      .map(|url| url.to_owned()),
-      None => None,
-    };
-    let url = page
-      .inner
-      .url()
-      .map(|u| u.as_single_xsd_any_uri())
-      .flatten()
-      .map(|u| u.to_owned());
+      get_or_fetch_and_upsert_person(&page.attributed_to, context, request_counter).await?;
+    let community = extract_community(&page.to, context, request_counter).await?;
 
+    let thumbnail_url: Option<Url> = page.image.clone().map(|i| i.url);
     let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
-      if let Some(url) = &url {
+      if let Some(url) = &page.url {
         fetch_iframely_and_pictrs_data(context.client(), Some(url)).await
       } else {
         (None, None, None, thumbnail_url)
       };
 
-    let name = page
-      .inner
-      .name()
-      // The following is for compatibility with lemmy v0.9.9 and older
-      // TODO: remove it after some time (along with the map above)
-      .or_else(|| page.inner.summary())
-      .context(location_info!())?
-      .as_single_xsd_string()
-      .context(location_info!())?
-      .to_string();
-    let body = get_source_markdown_value(page)?;
-
-    // TODO: expected_domain is wrong in this case, because it simply takes the domain of the actor
-    //       maybe we need to take id_unchecked() if the activity is from community to user?
-    //       why did this work before? -> i dont think it did?
-    //       -> try to make expected_domain optional and set it null if it is a mod action
-
-    check_slurs(&name)?;
-    let body_slurs_removed = body.map(|b| remove_slurs(&b));
-    Ok(PostForm {
-      name,
-      url: url.map(|u| u.into()),
+    let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content));
+    let form = PostForm {
+      name: page.name.clone(),
+      url: page.url.clone().map(|u| u.into()),
       body: body_slurs_removed,
       creator_id: creator.id,
       community_id: community.id,
       removed: None,
-      locked: ext.comments_enabled.map(|e| !e),
-      published: page
-        .inner
-        .published()
-        .as_ref()
-        .map(|u| u.to_owned().naive_local()),
-      updated: page
-        .inner
-        .updated()
-        .as_ref()
-        .map(|u| u.to_owned().naive_local()),
+      locked: page.comments_enabled.map(|e| !e),
+      published: Some(page.published.naive_local()),
+      updated: page.updated.map(|u| u.naive_local()),
       deleted: None,
-      nsfw: ext.sensitive,
-      stickied: ext.stickied,
+      nsfw: page.sensitive,
+      stickied: page.stickied,
       embed_title: iframely_title,
       embed_description: iframely_description,
       embed_html: iframely_html,
       thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
-      ap_id: Some(ap_id),
+      ap_id: Some(page.id.clone().into()),
       local: Some(false),
-    })
+    };
+    Ok(blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??)
   }
 }
index 6bb4820f4cabcde25d4dce59ecd85e5ec6c81d25..624123ecf0cdd54c7fec76d820632c972dc9945f 100644 (file)
@@ -42,7 +42,7 @@ impl ToApub for PrivateMessage {
     let recipient = blocking(pool, move |conn| Person::read(conn, recipient_id)).await??;
 
     private_message
-      .set_many_contexts(lemmy_context()?)
+      .set_many_contexts(lemmy_context())
       .set_id(self.ap_id.to_owned().into_inner())
       .set_published(convert_datetime(self.published))
       .set_to(recipient.actor_id.into_inner())
index 327670b504a1c5fe67a89df9a60758c7376768f8..6f8c841701ef4068085cceabb9feb89d1a69fa60 100644 (file)
@@ -12,3 +12,4 @@ activitystreams-ext = "0.1.0-alpha.2"
 serde = { version = "1.0.123", features = ["derive"] }
 async-trait = "0.1.42"
 url = { version = "2.2.1", features = ["serde"] }
+serde_json = { version = "1.0.64", features = ["preserve_order"] }
index 66bba9f43de200f50a2bf69e3c1bc56cbf31dcf6..13fc4025d8047cd96a42fe052705a1c428de929b 100644 (file)
@@ -1,3 +1,5 @@
+pub mod values;
+
 use activitystreams::{
   base::AnyBase,
   error::DomainError,
@@ -9,18 +11,12 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 use url::Url;
 
-#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
-pub enum PublicUrl {
-  #[serde(rename = "https://www.w3.org/ns/activitystreams#Public")]
-  Public,
-}
-
 #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct ActivityCommonFields {
   #[serde(rename = "@context")]
   pub context: OneOrMany<AnyBase>,
-  id: Url,
+  pub id: Url,
   pub actor: Url,
 
   // unparsed fields
diff --git a/crates/apub_lib/src/values/mod.rs b/crates/apub_lib/src/values/mod.rs
new file mode 100644 (file)
index 0000000..2c9f65f
--- /dev/null
@@ -0,0 +1,61 @@
+//! The enums here serve to limit a json string value to a single, hardcoded value which can be
+//! verified at compilation time. When using it as the type of a struct field, the struct can only
+//! be constructed or deserialized if the field has the exact same value.
+//!
+//! If we used String as the field type, any value would be accepted, and we would have to check
+//! manually at runtime that it contains the expected value.
+//!
+//! The enums in [`activitystreams::activity::kind`] work in the same way, and can be used to
+//! distinguish different activity types.
+//!
+//! In the example below, `MyObject` can only be constructed or
+//! deserialized if `media_type` is `text/markdown`, but not if it is `text/html`.
+//!
+//! ```
+//! use lemmy_apub_lib::values::MediaTypeMarkdown;
+//! use serde_json::from_str;
+//! use serde::{Deserialize, Serialize};
+//!
+//! #[derive(Deserialize, Serialize)]
+//! struct MyObject {
+//!   content: String,
+//!   media_type: MediaTypeMarkdown,
+//! }
+//!
+//! let markdown_json = r#"{"content": "**test**", "media_type": "text/markdown"}"#;
+//! let from_markdown = from_str::<MyObject>(markdown_json);
+//! assert!(from_markdown.is_ok());
+//!
+//! let markdown_html = r#"{"content": "<b>test</b>", "media_type": "text/html"}"#;
+//! let from_html = from_str::<MyObject>(markdown_html);
+//! assert!(from_html.is_err());
+//! ```
+
+use serde::{Deserialize, Serialize};
+
+/// The identifier used to address activities to the public.
+///
+/// <https://www.w3.org/TR/activitypub/#public-addressing>
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub enum PublicUrl {
+  #[serde(rename = "https://www.w3.org/ns/activitystreams#Public")]
+  Public,
+}
+
+/// Media type for markdown text.
+///
+/// <https://www.iana.org/assignments/media-types/media-types.xhtml>
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub enum MediaTypeMarkdown {
+  #[serde(rename = "text/markdown")]
+  Markdown,
+}
+
+/// Media type for HTML text/
+///
+/// <https://www.iana.org/assignments/media-types/media-types.xhtml>
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub enum MediaTypeHtml {
+  #[serde(rename = "text/html")]
+  Html,
+}