]> Untitled Git - lemmy.git/blob - crates/apub/src/activities/create_or_update/comment.rs
Accept comments with hashtags from Friendica (#2236)
[lemmy.git] / crates / apub / src / activities / create_or_update / comment.rs
1 use crate::{
2   activities::{
3     check_community_deleted_or_removed,
4     community::{announce::GetCommunity, send_activity_in_community},
5     create_or_update::get_comment_notif_recipients,
6     generate_activity_id,
7     verify_activity,
8     verify_is_public,
9     verify_person_in_community,
10   },
11   activity_lists::AnnouncableActivities,
12   mentions::MentionOrValue,
13   objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
14   protocol::activities::{create_or_update::comment::CreateOrUpdateComment, CreateOrUpdateType},
15 };
16 use activitystreams_kinds::public;
17 use lemmy_api_common::{blocking, check_post_deleted_or_removed};
18 use lemmy_apub_lib::{
19   data::Data,
20   object_id::ObjectId,
21   traits::{ActivityHandler, ActorType, ApubObject},
22   verify::verify_domains_match,
23 };
24 use lemmy_db_schema::{
25   source::{
26     comment::{CommentLike, CommentLikeForm},
27     community::Community,
28     post::Post,
29   },
30   traits::{Crud, Likeable},
31 };
32 use lemmy_utils::LemmyError;
33 use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud};
34
35 impl CreateOrUpdateComment {
36   #[tracing::instrument(skip(comment, actor, kind, context))]
37   pub async fn send(
38     comment: ApubComment,
39     actor: &ApubPerson,
40     kind: CreateOrUpdateType,
41     context: &LemmyContext,
42     request_counter: &mut i32,
43   ) -> Result<(), LemmyError> {
44     // TODO: might be helpful to add a comment method to retrieve community directly
45     let post_id = comment.post_id;
46     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
47     let community_id = post.community_id;
48     let community: ApubCommunity = blocking(context.pool(), move |conn| {
49       Community::read(conn, community_id)
50     })
51     .await??
52     .into();
53
54     let id = generate_activity_id(
55       kind.clone(),
56       &context.settings().get_protocol_and_hostname(),
57     )?;
58     let note = comment.into_apub(context).await?;
59
60     let create_or_update = CreateOrUpdateComment {
61       actor: ObjectId::new(actor.actor_id()),
62       to: vec![public()],
63       cc: note.cc.clone(),
64       tag: note.tag.clone(),
65       object: note,
66       kind,
67       id: id.clone(),
68       unparsed: Default::default(),
69     };
70
71     let tagged_users: Vec<ObjectId<ApubPerson>> = create_or_update
72       .tag
73       .iter()
74       .filter_map(|t| {
75         if let MentionOrValue::Mention(t) = t {
76           Some(t)
77         } else {
78           None
79         }
80       })
81       .map(|t| t.href.clone())
82       .map(ObjectId::new)
83       .collect();
84     let mut inboxes = vec![];
85     for t in tagged_users {
86       let person = t
87         .dereference(context, context.client(), request_counter)
88         .await?;
89       inboxes.push(person.shared_inbox_or_inbox_url());
90     }
91
92     let activity = AnnouncableActivities::CreateOrUpdateComment(create_or_update);
93     send_activity_in_community(activity, &id, actor, &community, inboxes, context).await
94   }
95 }
96
97 #[async_trait::async_trait(?Send)]
98 impl ActivityHandler for CreateOrUpdateComment {
99   type DataType = LemmyContext;
100
101   #[tracing::instrument(skip_all)]
102   async fn verify(
103     &self,
104     context: &Data<LemmyContext>,
105     request_counter: &mut i32,
106   ) -> Result<(), LemmyError> {
107     verify_is_public(&self.to, &self.cc)?;
108     let post = self.object.get_parents(context, request_counter).await?.0;
109     let community = self.get_community(context, request_counter).await?;
110
111     verify_activity(&self.id, self.actor.inner(), &context.settings())?;
112     verify_person_in_community(&self.actor, &community, context, request_counter).await?;
113     verify_domains_match(self.actor.inner(), self.object.id.inner())?;
114     check_community_deleted_or_removed(&community)?;
115     check_post_deleted_or_removed(&post)?;
116
117     ApubComment::verify(&self.object, self.actor.inner(), context, request_counter).await?;
118     Ok(())
119   }
120
121   #[tracing::instrument(skip_all)]
122   async fn receive(
123     self,
124     context: &Data<LemmyContext>,
125     request_counter: &mut i32,
126   ) -> Result<(), LemmyError> {
127     let comment = ApubComment::from_apub(self.object, context, request_counter).await?;
128
129     // author likes their own comment by default
130     let like_form = CommentLikeForm {
131       comment_id: comment.id,
132       post_id: comment.post_id,
133       person_id: comment.creator_id,
134       score: 1,
135     };
136     blocking(context.pool(), move |conn: &'_ _| {
137       CommentLike::like(conn, &like_form)
138     })
139     .await??;
140
141     let do_send_email = self.kind == CreateOrUpdateType::Create;
142     let recipients = get_comment_notif_recipients(
143       &self.actor,
144       &comment,
145       do_send_email,
146       context,
147       request_counter,
148     )
149     .await?;
150     let notif_type = match self.kind {
151       CreateOrUpdateType::Create => UserOperationCrud::CreateComment,
152       CreateOrUpdateType::Update => UserOperationCrud::EditComment,
153     };
154     send_comment_ws_message(
155       comment.id, notif_type, None, None, None, recipients, context,
156     )
157     .await?;
158     Ok(())
159   }
160 }
161
162 #[async_trait::async_trait(?Send)]
163 impl GetCommunity for CreateOrUpdateComment {
164   #[tracing::instrument(skip_all)]
165   async fn get_community(
166     &self,
167     context: &LemmyContext,
168     request_counter: &mut i32,
169   ) -> Result<ApubCommunity, LemmyError> {
170     let post = self.object.get_parents(context, request_counter).await?.0;
171     let community = blocking(context.pool(), move |conn| {
172       Community::read(conn, post.community_id)
173     })
174     .await??;
175     Ok(community.into())
176   }
177 }