2 activities::send::generate_activity_id,
3 activity_queue::{send_comment_mentions, send_to_community},
4 extensions::context::lemmy_context,
5 fetcher::user::get_or_fetch_and_upsert_user,
11 use activitystreams::{
13 kind::{CreateType, DeleteType, DislikeType, LikeType, RemoveType, UndoType, UpdateType},
28 use itertools::Itertools;
29 use lemmy_db_queries::{Crud, DbPool};
30 use lemmy_db_schema::source::{comment::Comment, community::Community, post::Post, user::User_};
31 use lemmy_structs::{blocking, WebFingerResponse};
33 request::{retry, RecvError},
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
48 async fn send_create(&self, creator: &User_, 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, 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: &User_, 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, context).await?;
108 send_comment_mentions(&creator, maa.inboxes, update, context).await?;
112 async fn send_delete(&self, creator: &User_, 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, 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, context).await?;
176 async fn send_remove(&self, mod_: &User_, 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, context).await?;
200 async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
201 let post_id = self.post_id;
202 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
204 let community_id = post.community_id;
205 let community = blocking(context.pool(), move |conn| {
206 Community::read(conn, community_id)
210 // Generate a fake delete activity, with the correct object
211 let mut remove = Remove::new(
212 mod_.actor_id.to_owned().into_inner(),
213 self.ap_id.to_owned().into_inner(),
216 .set_many_contexts(lemmy_context()?)
217 .set_id(generate_activity_id(RemoveType::Remove)?)
219 .set_many_ccs(vec![community.actor_id()]);
221 // Undo that fake activity
222 let mut undo = Undo::new(
223 mod_.actor_id.to_owned().into_inner(),
224 remove.into_any_base()?,
227 .set_many_contexts(lemmy_context()?)
228 .set_id(generate_activity_id(UndoType::Undo)?)
230 .set_many_ccs(vec![community.actor_id()]);
232 send_to_community(undo, &mod_, &community, context).await?;
237 #[async_trait::async_trait(?Send)]
238 impl ApubLikeableType for Comment {
239 async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
240 let post_id = self.post_id;
241 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
243 let community_id = post.community_id;
244 let community = blocking(context.pool(), move |conn| {
245 Community::read(conn, community_id)
249 let mut like = Like::new(
250 creator.actor_id.to_owned().into_inner(),
251 self.ap_id.to_owned().into_inner(),
254 .set_many_contexts(lemmy_context()?)
255 .set_id(generate_activity_id(LikeType::Like)?)
257 .set_many_ccs(vec![community.actor_id()]);
259 send_to_community(like, &creator, &community, context).await?;
263 async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
264 let post_id = self.post_id;
265 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
267 let community_id = post.community_id;
268 let community = blocking(context.pool(), move |conn| {
269 Community::read(conn, community_id)
273 let mut dislike = Dislike::new(
274 creator.actor_id.to_owned().into_inner(),
275 self.ap_id.to_owned().into_inner(),
278 .set_many_contexts(lemmy_context()?)
279 .set_id(generate_activity_id(DislikeType::Dislike)?)
281 .set_many_ccs(vec![community.actor_id()]);
283 send_to_community(dislike, &creator, &community, context).await?;
287 async fn send_undo_like(
290 context: &LemmyContext,
291 ) -> Result<(), LemmyError> {
292 let post_id = self.post_id;
293 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
295 let community_id = post.community_id;
296 let community = blocking(context.pool(), move |conn| {
297 Community::read(conn, community_id)
301 let mut like = Like::new(
302 creator.actor_id.to_owned().into_inner(),
303 self.ap_id.to_owned().into_inner(),
306 .set_many_contexts(lemmy_context()?)
307 .set_id(generate_activity_id(DislikeType::Dislike)?)
309 .set_many_ccs(vec![community.actor_id()]);
311 // Undo that fake activity
312 let mut undo = Undo::new(
313 creator.actor_id.to_owned().into_inner(),
314 like.into_any_base()?,
317 .set_many_contexts(lemmy_context()?)
318 .set_id(generate_activity_id(UndoType::Undo)?)
320 .set_many_ccs(vec![community.actor_id()]);
322 send_to_community(undo, &creator, &community, context).await?;
327 struct MentionsAndAddresses {
333 impl MentionsAndAddresses {
334 fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
338 .map(|t| t.to_owned().into_any_base())
339 .collect::<Result<Vec<AnyBase>, Error>>()
343 /// This takes a comment, and builds a list of to_addresses, inboxes,
344 /// and mention tags, so they know where to be sent to.
345 /// Addresses are the users / addresses that go in the cc field.
346 async fn collect_non_local_mentions(
348 community: &Community,
349 context: &LemmyContext,
350 ) -> Result<MentionsAndAddresses, LemmyError> {
351 let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
352 let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
353 // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
354 let mut inboxes = vec![parent_creator.get_shared_inbox_url()?];
356 // Add the mention tag
357 let mut tags = Vec::new();
359 // Get the user IDs for any mentions
360 let mentions = scrape_text_for_mentions(&comment.content)
362 // Filter only the non-local ones
363 .filter(|m| !m.is_local())
364 .collect::<Vec<MentionData>>();
366 for mention in &mentions {
367 // TODO should it be fetching it every time?
368 if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
369 debug!("mention actor_id: {}", actor_id);
370 addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
372 let mention_user = get_or_fetch_and_upsert_user(&actor_id, context, &mut 0).await?;
373 inboxes.push(mention_user.get_shared_inbox_url()?);
375 let mut mention_tag = Mention::new();
376 mention_tag.set_href(actor_id).set_name(mention.full_name());
377 tags.push(mention_tag);
381 let inboxes = inboxes.into_iter().unique().collect();
383 Ok(MentionsAndAddresses {
390 /// Returns the apub ID of the user this comment is responding to. Meaning, in case this is a
391 /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
392 async fn get_comment_parent_creator(pool: &DbPool, comment: &Comment) -> Result<User_, LemmyError> {
393 let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
395 blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
396 parent_comment.creator_id
398 let parent_post_id = comment.post_id;
399 let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
400 parent_post.creator_id
402 Ok(blocking(pool, move |conn| User_::read(conn, parent_creator_id)).await??)
405 /// Turns a user id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
407 async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
408 let fetch_url = format!(
409 "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
410 Settings::get().get_protocol_string(),
415 debug!("Fetching webfinger url: {}", &fetch_url);
417 let response = retry(|| client.get(&fetch_url).send()).await?;
419 let res: WebFingerResponse = response
422 .map_err(|e| RecvError(e.to_string()))?;
427 .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
428 .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
432 .ok_or_else(|| anyhow!("No href found.").into())