]> Untitled Git - lemmy.git/blob - crates/apub/src/protocol/objects/page.rs
Merge pull request #1918 from LemmyNet/fix-smithereen-webfinger
[lemmy.git] / crates / apub / src / protocol / objects / page.rs
1 use crate::{
2   objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
3   protocol::{ImageObject, Source},
4 };
5 use activitystreams::{object::kind::PageType, unparsed::Unparsed};
6 use anyhow::anyhow;
7 use chrono::{DateTime, FixedOffset};
8 use lemmy_apub_lib::{
9   data::Data,
10   object_id::ObjectId,
11   traits::{ActivityHandler, ApubObject},
12   values::MediaTypeHtml,
13 };
14 use lemmy_utils::LemmyError;
15 use lemmy_websocket::LemmyContext;
16 use serde::{Deserialize, Serialize};
17 use serde_with::skip_serializing_none;
18 use url::Url;
19
20 #[skip_serializing_none]
21 #[derive(Clone, Debug, Deserialize, Serialize)]
22 #[serde(rename_all = "camelCase")]
23 pub struct Page {
24   pub(crate) r#type: PageType,
25   pub(crate) id: ObjectId<ApubPost>,
26   pub(crate) attributed_to: ObjectId<ApubPerson>,
27   pub(crate) to: Vec<Url>,
28   #[serde(default)]
29   pub(crate) cc: Vec<Url>,
30   pub(crate) name: String,
31   pub(crate) content: Option<String>,
32   pub(crate) media_type: Option<MediaTypeHtml>,
33   pub(crate) source: Option<Source>,
34   pub(crate) url: Option<Url>,
35   pub(crate) image: Option<ImageObject>,
36   pub(crate) comments_enabled: Option<bool>,
37   pub(crate) sensitive: Option<bool>,
38   pub(crate) stickied: Option<bool>,
39   pub(crate) published: Option<DateTime<FixedOffset>>,
40   pub(crate) updated: Option<DateTime<FixedOffset>>,
41   #[serde(flatten)]
42   pub(crate) unparsed: Unparsed,
43 }
44
45 impl Page {
46   /// Only mods can change the post's stickied/locked status. So if either of these is changed from
47   /// the current value, it is a mod action and needs to be verified as such.
48   ///
49   /// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]].
50   pub(crate) async fn is_mod_action(&self, context: &LemmyContext) -> Result<bool, LemmyError> {
51     let old_post = ObjectId::<ApubPost>::new(self.id.clone())
52       .dereference_local(context)
53       .await;
54
55     let is_mod_action = if let Ok(old_post) = old_post {
56       self.stickied != Some(old_post.stickied) || self.comments_enabled != Some(!old_post.locked)
57     } else {
58       false
59     };
60     Ok(is_mod_action)
61   }
62
63   pub(crate) async fn extract_community(
64     &self,
65     context: &LemmyContext,
66     request_counter: &mut i32,
67   ) -> Result<ApubCommunity, LemmyError> {
68     let mut to_iter = self.to.iter();
69     loop {
70       if let Some(cid) = to_iter.next() {
71         let cid = ObjectId::new(cid.clone());
72         if let Ok(c) = cid.dereference(context, request_counter).await {
73           break Ok(c);
74         }
75       } else {
76         return Err(anyhow!("No community found in cc").into());
77       }
78     }
79   }
80 }
81
82 // Used for community outbox, so that it can be compatible with Pleroma/Mastodon.
83 #[async_trait::async_trait(?Send)]
84 impl ActivityHandler for Page {
85   type DataType = LemmyContext;
86   async fn verify(
87     &self,
88     data: &Data<Self::DataType>,
89     request_counter: &mut i32,
90   ) -> Result<(), LemmyError> {
91     ApubPost::verify(self, self.id.inner(), data, request_counter).await
92   }
93   async fn receive(
94     self,
95     data: &Data<Self::DataType>,
96     request_counter: &mut i32,
97   ) -> Result<(), LemmyError> {
98     ApubPost::from_apub(self, data, request_counter).await?;
99     Ok(())
100   }
101 }