2 activities::send::generate_activity_id,
3 activity_queue::{send_comment_mentions, send_to_community},
4 extensions::context::lemmy_context,
5 fetcher::person::get_or_fetch_and_upsert_person,
11 use activitystreams::{
13 kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
28 use itertools::Itertools;
29 use lemmy_api_common::{blocking, WebFingerResponse};
30 use lemmy_db_queries::{Crud, DbPool};
31 use lemmy_db_schema::source::{comment::Comment, community::Community, person::Person, post::Post};
33 request::{retry, RecvError},
34 settings::structs::Settings,
35 utils::{scrape_text_for_mentions, MentionData},
38 use lemmy_websocket::LemmyContext;
41 use serde_json::Error;
44 #[async_trait::async_trait(?Send)]
45 impl ApubObjectType for Comment {
46 /// Send out information about a newly created comment, to the followers of the community and
47 /// mentioned persons.
48 async fn send_create(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
49 let note = self.to_apub(context.pool()).await?;
51 let post_id = self.post_id;
52 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
54 let community_id = post.community_id;
55 let community = blocking(context.pool(), move |conn| {
56 Community::read(conn, community_id)
60 let maa = collect_non_local_mentions(&self, &community, context).await?;
62 let mut create = Create::new(
63 creator.actor_id.to_owned().into_inner(),
64 note.into_any_base()?,
67 .set_many_contexts(lemmy_context()?)
68 .set_id(generate_activity_id(CreateType::Create)?)
70 .set_many_ccs(maa.ccs.to_owned())
71 // Set the mention tags
72 .set_many_tags(maa.get_tags()?);
74 send_to_community(create.clone(), &creator, &community, None, context).await?;
75 send_comment_mentions(&creator, maa.inboxes, create, context).await?;
79 /// Send out information about an edited post, to the followers of the community and mentioned
81 async fn send_update(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
82 let note = self.to_apub(context.pool()).await?;
84 let post_id = self.post_id;
85 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
87 let community_id = post.community_id;
88 let community = blocking(context.pool(), move |conn| {
89 Community::read(conn, community_id)
93 let maa = collect_non_local_mentions(&self, &community, context).await?;
95 let mut update = Update::new(
96 creator.actor_id.to_owned().into_inner(),
97 note.into_any_base()?,
100 .set_many_contexts(lemmy_context()?)
101 .set_id(generate_activity_id(UpdateType::Update)?)
103 .set_many_ccs(maa.ccs.to_owned())
104 // Set the mention tags
105 .set_many_tags(maa.get_tags()?);
107 send_to_community(update.clone(), &creator, &community, None, context).await?;
108 send_comment_mentions(&creator, maa.inboxes, update, context).await?;
112 async fn send_delete(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
113 let post_id = self.post_id;
114 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
116 let community_id = post.community_id;
117 let community = blocking(context.pool(), move |conn| {
118 Community::read(conn, community_id)
122 let mut delete = Delete::new(
123 creator.actor_id.to_owned().into_inner(),
124 self.ap_id.to_owned().into_inner(),
127 .set_many_contexts(lemmy_context()?)
128 .set_id(generate_activity_id(DeleteType::Delete)?)
130 .set_many_ccs(vec![community.actor_id()]);
132 send_to_community(delete, &creator, &community, None, context).await?;
136 async fn send_undo_delete(
139 context: &LemmyContext,
140 ) -> Result<(), LemmyError> {
141 let post_id = self.post_id;
142 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
144 let community_id = post.community_id;
145 let community = blocking(context.pool(), move |conn| {
146 Community::read(conn, community_id)
150 // Generate a fake delete activity, with the correct object
151 let mut delete = Delete::new(
152 creator.actor_id.to_owned().into_inner(),
153 self.ap_id.to_owned().into_inner(),
156 .set_many_contexts(lemmy_context()?)
157 .set_id(generate_activity_id(DeleteType::Delete)?)
159 .set_many_ccs(vec![community.actor_id()]);
161 // Undo that fake activity
162 let mut undo = Undo::new(
163 creator.actor_id.to_owned().into_inner(),
164 delete.into_any_base()?,
167 .set_many_contexts(lemmy_context()?)
168 .set_id(generate_activity_id(UndoType::Undo)?)
170 .set_many_ccs(vec![community.actor_id()]);
172 send_to_community(undo, &creator, &community, None, context).await?;
176 async fn send_remove(&self, mod_: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
177 let post_id = self.post_id;
178 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
180 let community_id = post.community_id;
181 let community = blocking(context.pool(), move |conn| {
182 Community::read(conn, community_id)
186 let mut remove = Remove::new(
187 mod_.actor_id.to_owned().into_inner(),
188 self.ap_id.to_owned().into_inner(),
191 .set_many_contexts(lemmy_context()?)
192 .set_id(generate_activity_id(RemoveType::Remove)?)
194 .set_many_ccs(vec![community.actor_id()]);
196 send_to_community(remove, &mod_, &community, None, context).await?;
200 async fn send_undo_remove(
203 context: &LemmyContext,
204 ) -> Result<(), LemmyError> {
205 let post_id = self.post_id;
206 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
208 let community_id = post.community_id;
209 let community = blocking(context.pool(), move |conn| {
210 Community::read(conn, community_id)
214 // Generate a fake delete activity, with the correct object
215 let mut remove = Remove::new(
216 mod_.actor_id.to_owned().into_inner(),
217 self.ap_id.to_owned().into_inner(),
220 .set_many_contexts(lemmy_context()?)
221 .set_id(generate_activity_id(RemoveType::Remove)?)
223 .set_many_ccs(vec![community.actor_id()]);
225 // Undo that fake activity
226 let mut undo = Undo::new(
227 mod_.actor_id.to_owned().into_inner(),
228 remove.into_any_base()?,
231 .set_many_contexts(lemmy_context()?)
232 .set_id(generate_activity_id(UndoType::Undo)?)
234 .set_many_ccs(vec![community.actor_id()]);
236 send_to_community(undo, &mod_, &community, None, context).await?;
241 #[async_trait::async_trait(?Send)]
242 impl ApubLikeableType for Comment {
243 async fn send_like(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
244 let post_id = self.post_id;
245 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
247 let community_id = post.community_id;
248 let community = blocking(context.pool(), move |conn| {
249 Community::read(conn, community_id)
253 let mut like = Like::new(
254 creator.actor_id.to_owned().into_inner(),
255 self.ap_id.to_owned().into_inner(),
258 .set_many_contexts(lemmy_context()?)
259 .set_id(generate_activity_id(LikeType::Like)?)
261 .set_many_ccs(vec![community.actor_id()]);
263 send_to_community(like, &creator, &community, None, context).await?;
267 async fn send_dislike(&self, creator: &Person, context: &LemmyContext) -> Result<(), LemmyError> {
268 let post_id = self.post_id;
269 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
271 let community_id = post.community_id;
272 let community = blocking(context.pool(), move |conn| {
273 Community::read(conn, community_id)
277 let mut dislike = Dislike::new(
278 creator.actor_id.to_owned().into_inner(),
279 self.ap_id.to_owned().into_inner(),
282 .set_many_contexts(lemmy_context()?)
283 .set_id(generate_activity_id(DislikeType::Dislike)?)
285 .set_many_ccs(vec![community.actor_id()]);
287 send_to_community(dislike, &creator, &community, None, context).await?;
291 async fn send_undo_like(
294 context: &LemmyContext,
295 ) -> Result<(), LemmyError> {
296 let post_id = self.post_id;
297 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
299 let community_id = post.community_id;
300 let community = blocking(context.pool(), move |conn| {
301 Community::read(conn, community_id)
305 let mut like = Like::new(
306 creator.actor_id.to_owned().into_inner(),
307 self.ap_id.to_owned().into_inner(),
310 .set_many_contexts(lemmy_context()?)
311 .set_id(generate_activity_id(DislikeType::Dislike)?)
313 .set_many_ccs(vec![community.actor_id()]);
315 // Undo that fake activity
316 let mut undo = Undo::new(
317 creator.actor_id.to_owned().into_inner(),
318 like.into_any_base()?,
321 .set_many_contexts(lemmy_context()?)
322 .set_id(generate_activity_id(UndoType::Undo)?)
324 .set_many_ccs(vec![community.actor_id()]);
326 send_to_community(undo, &creator, &community, None, context).await?;
331 struct MentionsAndAddresses {
337 impl MentionsAndAddresses {
338 fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
342 .map(|t| t.to_owned().into_any_base())
343 .collect::<Result<Vec<AnyBase>, Error>>()
347 /// This takes a comment, and builds a list of to_addresses, inboxes,
348 /// and mention tags, so they know where to be sent to.
349 /// Addresses are the persons / addresses that go in the cc field.
350 async fn collect_non_local_mentions(
352 community: &Community,
353 context: &LemmyContext,
354 ) -> Result<MentionsAndAddresses, LemmyError> {
355 let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
356 let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
357 // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
358 let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
360 // Add the mention tag
361 let mut tags = Vec::new();
363 // Get the person IDs for any mentions
364 let mentions = scrape_text_for_mentions(&comment.content)
366 // Filter only the non-local ones
367 .filter(|m| !m.is_local())
368 .collect::<Vec<MentionData>>();
370 for mention in &mentions {
371 // TODO should it be fetching it every time?
372 if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
373 debug!("mention actor_id: {}", actor_id);
374 addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
376 let mention_person = get_or_fetch_and_upsert_person(&actor_id, context, &mut 0).await?;
377 inboxes.push(mention_person.get_shared_inbox_or_inbox_url());
379 let mut mention_tag = Mention::new();
380 mention_tag.set_href(actor_id).set_name(mention.full_name());
381 tags.push(mention_tag);
385 let inboxes = inboxes.into_iter().unique().collect();
387 Ok(MentionsAndAddresses {
394 /// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a
395 /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
396 async fn get_comment_parent_creator(
399 ) -> Result<Person, LemmyError> {
400 let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
402 blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
403 parent_comment.creator_id
405 let parent_post_id = comment.post_id;
406 let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
407 parent_post.creator_id
409 Ok(blocking(pool, move |conn| Person::read(conn, parent_creator_id)).await??)
412 /// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
414 async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
415 let fetch_url = format!(
416 "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
417 Settings::get().get_protocol_string(),
422 debug!("Fetching webfinger url: {}", &fetch_url);
424 let response = retry(|| client.get(&fetch_url).send()).await?;
426 let res: WebFingerResponse = response
429 .map_err(|e| RecvError(e.to_string()))?;
434 .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
435 .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
439 .ok_or_else(|| anyhow!("No href found.").into())