4 receive_create_comment,
5 receive_delete_comment,
6 receive_dislike_comment,
8 receive_remove_comment,
9 receive_update_comment,
12 receive_undo_delete_comment,
13 receive_undo_dislike_comment,
14 receive_undo_like_comment,
15 receive_undo_remove_comment,
26 receive_undo_delete_post,
27 receive_undo_dislike_post,
28 receive_undo_like_post,
29 receive_undo_remove_post,
31 receive_unhandled_activity,
32 verify_activity_domains_valid,
34 fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
35 inbox::is_addressed_to_public,
37 use activitystreams::{
38 activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
43 use diesel::result::Error::NotFound;
44 use lemmy_db::{ApubObject, Crud};
45 use lemmy_db_schema::source::{comment::Comment, post::Post, site::Site};
46 use lemmy_structs::blocking;
47 use lemmy_utils::{location_info, LemmyError};
48 use lemmy_websocket::LemmyContext;
51 /// This file is for post/comment activities received by the community, and for post/comment
52 /// activities announced by the community and received by the user.
54 /// A post or comment being created
55 pub(in crate::inbox) async fn receive_create_for_community(
56 context: &LemmyContext,
58 expected_domain: &Url,
59 request_counter: &mut i32,
60 ) -> Result<(), LemmyError> {
61 let create = Create::from_any_base(activity)?.context(location_info!())?;
62 verify_activity_domains_valid(&create, &expected_domain, true)?;
63 is_addressed_to_public(&create)?;
65 match create.object().as_single_kind_str() {
66 Some("Page") => receive_create_post(create, context, request_counter).await,
67 Some("Note") => receive_create_comment(create, context, request_counter).await,
68 _ => receive_unhandled_activity(create),
72 /// A post or comment being edited
73 pub(in crate::inbox) async fn receive_update_for_community(
74 context: &LemmyContext,
76 expected_domain: &Url,
77 request_counter: &mut i32,
78 ) -> Result<(), LemmyError> {
79 let update = Update::from_any_base(activity)?.context(location_info!())?;
80 verify_activity_domains_valid(&update, &expected_domain, true)?;
81 is_addressed_to_public(&update)?;
83 match update.object().as_single_kind_str() {
84 Some("Page") => receive_update_post(update, context, request_counter).await,
85 Some("Note") => receive_update_comment(update, context, request_counter).await,
86 _ => receive_unhandled_activity(update),
90 /// A post or comment being upvoted
91 pub(in crate::inbox) async fn receive_like_for_community(
92 context: &LemmyContext,
94 expected_domain: &Url,
95 request_counter: &mut i32,
96 ) -> Result<(), LemmyError> {
97 let like = Like::from_any_base(activity)?.context(location_info!())?;
98 verify_activity_domains_valid(&like, &expected_domain, false)?;
99 is_addressed_to_public(&like)?;
101 let object_id = get_like_object_id(&like)?;
102 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
103 PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await,
104 PostOrComment::Comment(comment) => {
105 receive_like_comment(like, comment, context, request_counter).await
110 /// A post or comment being downvoted
111 pub(in crate::inbox) async fn receive_dislike_for_community(
112 context: &LemmyContext,
114 expected_domain: &Url,
115 request_counter: &mut i32,
116 ) -> Result<(), LemmyError> {
117 let enable_downvotes = blocking(context.pool(), move |conn| {
118 Site::read(conn, 1).map(|s| s.enable_downvotes)
121 if !enable_downvotes {
125 let dislike = Dislike::from_any_base(activity)?.context(location_info!())?;
126 verify_activity_domains_valid(&dislike, &expected_domain, false)?;
127 is_addressed_to_public(&dislike)?;
129 let object_id = get_like_object_id(&dislike)?;
130 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
131 PostOrComment::Post(post) => {
132 receive_dislike_post(dislike, post, context, request_counter).await
134 PostOrComment::Comment(comment) => {
135 receive_dislike_comment(dislike, comment, context, request_counter).await
140 /// A post or comment being deleted by its creator
141 pub(in crate::inbox) async fn receive_delete_for_community(
142 context: &LemmyContext,
144 expected_domain: &Url,
145 ) -> Result<(), LemmyError> {
146 let delete = Delete::from_any_base(activity)?.context(location_info!())?;
147 verify_activity_domains_valid(&delete, &expected_domain, true)?;
148 is_addressed_to_public(&delete)?;
153 .single_xsd_any_uri()
154 .context(location_info!())?;
156 match find_post_or_comment_by_id(context, object).await {
157 Ok(PostOrComment::Post(p)) => receive_delete_post(context, p).await,
158 Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, c).await,
159 // if we dont have the object, no need to do anything
164 /// A post or comment being removed by a mod/admin
165 pub(in crate::inbox) async fn receive_remove_for_community(
166 context: &LemmyContext,
168 expected_domain: &Url,
169 ) -> Result<(), LemmyError> {
170 let remove = Remove::from_any_base(activity)?.context(location_info!())?;
171 verify_activity_domains_valid(&remove, &expected_domain, false)?;
172 is_addressed_to_public(&remove)?;
176 .map(|c| c.as_many())
178 .context(location_info!())?;
179 let community_id = cc
181 .map(|c| c.as_xsd_any_uri())
183 .context(location_info!())?;
188 .single_xsd_any_uri()
189 .context(location_info!())?;
191 // Ensure that remove activity comes from the same domain as the community
192 remove.id(community_id.domain().context(location_info!())?)?;
194 match find_post_or_comment_by_id(context, object).await {
195 Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, p).await,
196 Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, c).await,
197 // if we dont have the object, no need to do anything
202 /// A post/comment action being reverted (either a delete, remove, upvote or downvote)
203 pub(in crate::inbox) async fn receive_undo_for_community(
204 context: &LemmyContext,
206 expected_domain: &Url,
207 request_counter: &mut i32,
208 ) -> Result<(), LemmyError> {
209 let undo = Undo::from_any_base(activity)?.context(location_info!())?;
210 verify_activity_domains_valid(&undo, &expected_domain.to_owned(), true)?;
211 is_addressed_to_public(&undo)?;
213 match undo.object().as_single_kind_str() {
214 Some("Delete") => receive_undo_delete_for_community(context, undo, expected_domain).await,
215 Some("Remove") => receive_undo_remove_for_community(context, undo, expected_domain).await,
217 receive_undo_like_for_community(context, undo, expected_domain, request_counter).await
220 receive_undo_dislike_for_community(context, undo, expected_domain, request_counter).await
222 _ => receive_unhandled_activity(undo),
226 /// A post or comment deletion being reverted
227 pub(in crate::inbox) async fn receive_undo_delete_for_community(
228 context: &LemmyContext,
230 expected_domain: &Url,
231 ) -> Result<(), LemmyError> {
232 let delete = Delete::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
233 .context(location_info!())?;
234 verify_activity_domains_valid(&delete, &expected_domain, true)?;
235 is_addressed_to_public(&delete)?;
240 .single_xsd_any_uri()
241 .context(location_info!())?;
242 match find_post_or_comment_by_id(context, object).await {
243 Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, p).await,
244 Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, c).await,
245 // if we dont have the object, no need to do anything
250 /// A post or comment removal being reverted
251 pub(in crate::inbox) async fn receive_undo_remove_for_community(
252 context: &LemmyContext,
254 expected_domain: &Url,
255 ) -> Result<(), LemmyError> {
256 let remove = Remove::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
257 .context(location_info!())?;
258 verify_activity_domains_valid(&remove, &expected_domain, false)?;
259 is_addressed_to_public(&remove)?;
264 .single_xsd_any_uri()
265 .context(location_info!())?;
266 match find_post_or_comment_by_id(context, object).await {
267 Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, p).await,
268 Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, c).await,
269 // if we dont have the object, no need to do anything
274 /// A post or comment upvote being reverted
275 pub(in crate::inbox) async fn receive_undo_like_for_community(
276 context: &LemmyContext,
278 expected_domain: &Url,
279 request_counter: &mut i32,
280 ) -> Result<(), LemmyError> {
281 let like = Like::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
282 .context(location_info!())?;
283 verify_activity_domains_valid(&like, &expected_domain, false)?;
284 is_addressed_to_public(&like)?;
286 let object_id = get_like_object_id(&like)?;
287 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
288 PostOrComment::Post(post) => {
289 receive_undo_like_post(&like, post, context, request_counter).await
291 PostOrComment::Comment(comment) => {
292 receive_undo_like_comment(&like, comment, context, request_counter).await
297 /// A post or comment downvote being reverted
298 pub(in crate::inbox) async fn receive_undo_dislike_for_community(
299 context: &LemmyContext,
301 expected_domain: &Url,
302 request_counter: &mut i32,
303 ) -> Result<(), LemmyError> {
304 let dislike = Dislike::from_any_base(undo.object().to_owned().one().context(location_info!())?)?
305 .context(location_info!())?;
306 verify_activity_domains_valid(&dislike, &expected_domain, false)?;
307 is_addressed_to_public(&dislike)?;
309 let object_id = get_like_object_id(&dislike)?;
310 match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
311 PostOrComment::Post(post) => {
312 receive_undo_dislike_post(&dislike, post, context, request_counter).await
314 PostOrComment::Comment(comment) => {
315 receive_undo_dislike_comment(&dislike, comment, context, request_counter).await
325 /// Tries to find a post or comment in the local database, without any network requests.
326 /// This is used to handle deletions and removals, because in case we dont have the object, we can
327 /// simply ignore the activity.
328 async fn find_post_or_comment_by_id(
329 context: &LemmyContext,
331 ) -> Result<PostOrComment, LemmyError> {
332 let ap_id = apub_id.to_string();
333 let post = blocking(context.pool(), move |conn| {
334 Post::read_from_apub_id(conn, &ap_id)
337 if let Ok(p) = post {
338 return Ok(PostOrComment::Post(p));
341 let ap_id = apub_id.to_string();
342 let comment = blocking(context.pool(), move |conn| {
343 Comment::read_from_apub_id(conn, &ap_id)
346 if let Ok(c) = comment {
347 return Ok(PostOrComment::Comment(c));
353 async fn fetch_post_or_comment_by_id(
355 context: &LemmyContext,
356 request_counter: &mut i32,
357 ) -> Result<PostOrComment, LemmyError> {
358 if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
359 return Ok(PostOrComment::Post(post));
362 if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
363 return Ok(PostOrComment::Comment(comment));
369 fn get_like_object_id<Activity>(like_or_dislike: &Activity) -> Result<Url, LemmyError>
371 Activity: ActorAndObjectRefExt,
373 // TODO: For backwards compatibility with older Lemmy versions where like.object contains a full
374 // post/comment. This can be removed after some time, using
375 // `activity.oject().as_single_xsd_any_uri()` instead.
376 let object = like_or_dislike.object();
377 if let Some(xsd_uri) = object.as_single_xsd_any_uri() {
378 Ok(xsd_uri.to_owned())
384 .context(location_info!())?
386 .context(location_info!())?