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(creator.actor_id.to_owned(), note.into_any_base()?);
64 .set_many_contexts(lemmy_context()?)
65 .set_id(generate_activity_id(CreateType::Create)?)
67 .set_many_ccs(maa.ccs.to_owned())
68 // Set the mention tags
69 .set_many_tags(maa.get_tags()?);
71 send_to_community(create.clone(), &creator, &community, context).await?;
72 send_comment_mentions(&creator, maa.inboxes, create, context).await?;
76 /// Send out information about an edited post, to the followers of the community and mentioned
78 async fn send_update(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
79 let note = self.to_apub(context.pool()).await?;
81 let post_id = self.post_id;
82 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
84 let community_id = post.community_id;
85 let community = blocking(context.pool(), move |conn| {
86 Community::read(conn, community_id)
90 let maa = collect_non_local_mentions(&self, &community, context).await?;
92 let mut update = Update::new(creator.actor_id.to_owned(), note.into_any_base()?);
94 .set_many_contexts(lemmy_context()?)
95 .set_id(generate_activity_id(UpdateType::Update)?)
97 .set_many_ccs(maa.ccs.to_owned())
98 // Set the mention tags
99 .set_many_tags(maa.get_tags()?);
101 send_to_community(update.clone(), &creator, &community, context).await?;
102 send_comment_mentions(&creator, maa.inboxes, update, context).await?;
106 async fn send_delete(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
107 let post_id = self.post_id;
108 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
110 let community_id = post.community_id;
111 let community = blocking(context.pool(), move |conn| {
112 Community::read(conn, community_id)
116 let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
118 .set_many_contexts(lemmy_context()?)
119 .set_id(generate_activity_id(DeleteType::Delete)?)
121 .set_many_ccs(vec![community.actor_id()?]);
123 send_to_community(delete, &creator, &community, context).await?;
127 async fn send_undo_delete(
130 context: &LemmyContext,
131 ) -> Result<(), LemmyError> {
132 let post_id = self.post_id;
133 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
135 let community_id = post.community_id;
136 let community = blocking(context.pool(), move |conn| {
137 Community::read(conn, community_id)
141 // Generate a fake delete activity, with the correct object
142 let mut delete = Delete::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
144 .set_many_contexts(lemmy_context()?)
145 .set_id(generate_activity_id(DeleteType::Delete)?)
147 .set_many_ccs(vec![community.actor_id()?]);
149 // Undo that fake activity
150 let mut undo = Undo::new(creator.actor_id.to_owned(), delete.into_any_base()?);
152 .set_many_contexts(lemmy_context()?)
153 .set_id(generate_activity_id(UndoType::Undo)?)
155 .set_many_ccs(vec![community.actor_id()?]);
157 send_to_community(undo, &creator, &community, context).await?;
161 async fn send_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
162 let post_id = self.post_id;
163 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
165 let community_id = post.community_id;
166 let community = blocking(context.pool(), move |conn| {
167 Community::read(conn, community_id)
171 let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
173 .set_many_contexts(lemmy_context()?)
174 .set_id(generate_activity_id(RemoveType::Remove)?)
176 .set_many_ccs(vec![community.actor_id()?]);
178 send_to_community(remove, &mod_, &community, context).await?;
182 async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
183 let post_id = self.post_id;
184 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
186 let community_id = post.community_id;
187 let community = blocking(context.pool(), move |conn| {
188 Community::read(conn, community_id)
192 // Generate a fake delete activity, with the correct object
193 let mut remove = Remove::new(mod_.actor_id.to_owned(), Url::parse(&self.ap_id)?);
195 .set_many_contexts(lemmy_context()?)
196 .set_id(generate_activity_id(RemoveType::Remove)?)
198 .set_many_ccs(vec![community.actor_id()?]);
200 // Undo that fake activity
201 let mut undo = Undo::new(mod_.actor_id.to_owned(), remove.into_any_base()?);
203 .set_many_contexts(lemmy_context()?)
204 .set_id(generate_activity_id(UndoType::Undo)?)
206 .set_many_ccs(vec![community.actor_id()?]);
208 send_to_community(undo, &mod_, &community, context).await?;
213 #[async_trait::async_trait(?Send)]
214 impl ApubLikeableType for Comment {
215 async fn send_like(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
216 let post_id = self.post_id;
217 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
219 let community_id = post.community_id;
220 let community = blocking(context.pool(), move |conn| {
221 Community::read(conn, community_id)
225 let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
227 .set_many_contexts(lemmy_context()?)
228 .set_id(generate_activity_id(LikeType::Like)?)
230 .set_many_ccs(vec![community.actor_id()?]);
232 send_to_community(like, &creator, &community, context).await?;
236 async fn send_dislike(&self, creator: &User_, context: &LemmyContext) -> Result<(), LemmyError> {
237 let post_id = self.post_id;
238 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
240 let community_id = post.community_id;
241 let community = blocking(context.pool(), move |conn| {
242 Community::read(conn, community_id)
246 let mut dislike = Dislike::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
248 .set_many_contexts(lemmy_context()?)
249 .set_id(generate_activity_id(DislikeType::Dislike)?)
251 .set_many_ccs(vec![community.actor_id()?]);
253 send_to_community(dislike, &creator, &community, context).await?;
257 async fn send_undo_like(
260 context: &LemmyContext,
261 ) -> Result<(), LemmyError> {
262 let post_id = self.post_id;
263 let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
265 let community_id = post.community_id;
266 let community = blocking(context.pool(), move |conn| {
267 Community::read(conn, community_id)
271 let mut like = Like::new(creator.actor_id.to_owned(), Url::parse(&self.ap_id)?);
273 .set_many_contexts(lemmy_context()?)
274 .set_id(generate_activity_id(DislikeType::Dislike)?)
276 .set_many_ccs(vec![community.actor_id()?]);
278 // Undo that fake activity
279 let mut undo = Undo::new(creator.actor_id.to_owned(), like.into_any_base()?);
281 .set_many_contexts(lemmy_context()?)
282 .set_id(generate_activity_id(UndoType::Undo)?)
284 .set_many_ccs(vec![community.actor_id()?]);
286 send_to_community(undo, &creator, &community, context).await?;
291 struct MentionsAndAddresses {
297 impl MentionsAndAddresses {
298 fn get_tags(&self) -> Result<Vec<AnyBase>, Error> {
302 .map(|t| t.to_owned().into_any_base())
303 .collect::<Result<Vec<AnyBase>, Error>>()
307 /// This takes a comment, and builds a list of to_addresses, inboxes,
308 /// and mention tags, so they know where to be sent to.
309 /// Addresses are the users / addresses that go in the cc field.
310 async fn collect_non_local_mentions(
312 community: &Community,
313 context: &LemmyContext,
314 ) -> Result<MentionsAndAddresses, LemmyError> {
315 let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
316 let mut addressed_ccs = vec![community.actor_id()?, parent_creator.actor_id()?];
317 // Note: dont include community inbox here, as we send to it separately with `send_to_community()`
318 let mut inboxes = vec![parent_creator.get_shared_inbox_url()?];
320 // Add the mention tag
321 let mut tags = Vec::new();
323 // Get the user IDs for any mentions
324 let mentions = scrape_text_for_mentions(&comment.content)
326 // Filter only the non-local ones
327 .filter(|m| !m.is_local())
328 .collect::<Vec<MentionData>>();
330 for mention in &mentions {
331 // TODO should it be fetching it every time?
332 if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await {
333 debug!("mention actor_id: {}", actor_id);
334 addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
336 let mention_user = get_or_fetch_and_upsert_user(&actor_id, context, &mut 0).await?;
337 inboxes.push(mention_user.get_shared_inbox_url()?);
339 let mut mention_tag = Mention::new();
340 mention_tag.set_href(actor_id).set_name(mention.full_name());
341 tags.push(mention_tag);
345 let inboxes = inboxes.into_iter().unique().collect();
347 Ok(MentionsAndAddresses {
354 /// Returns the apub ID of the user this comment is responding to. Meaning, in case this is a
355 /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
356 async fn get_comment_parent_creator(pool: &DbPool, comment: &Comment) -> Result<User_, LemmyError> {
357 let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
359 blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
360 parent_comment.creator_id
362 let parent_post_id = comment.post_id;
363 let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??;
364 parent_post.creator_id
366 Ok(blocking(pool, move |conn| User_::read(conn, parent_creator_id)).await??)
369 /// Turns a user id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
371 async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result<Url, LemmyError> {
372 let fetch_url = format!(
373 "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
374 Settings::get().get_protocol_string(),
379 debug!("Fetching webfinger url: {}", &fetch_url);
381 let response = retry(|| client.get(&fetch_url).send()).await?;
383 let res: WebFingerResponse = response
386 .map_err(|e| RecvError(e.to_string()))?;
391 .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
392 .ok_or_else(|| anyhow!("No application/activity+json link found."))?;
396 .map(|u| Url::parse(&u))
398 .ok_or_else(|| anyhow!("No href found.").into())