]> Untitled Git - lemmy.git/blob - crates/api_common/src/websocket/send.rs
Merge pull request #2593 from LemmyNet/refactor-notifications
[lemmy.git] / crates / api_common / src / websocket / send.rs
1 use crate::{
2   comment::CommentResponse,
3   community::CommunityResponse,
4   context::LemmyContext,
5   post::PostResponse,
6   private_message::PrivateMessageResponse,
7   utils::{check_person_block, get_interface_language, send_email_to_user},
8   websocket::{
9     messages::{SendComment, SendCommunityRoomMessage, SendPost, SendUserRoomMessage},
10     OperationType,
11   },
12 };
13 use lemmy_db_schema::{
14   newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
15   source::{
16     comment::Comment,
17     comment_reply::{CommentReply, CommentReplyInsertForm},
18     person::Person,
19     person_mention::{PersonMention, PersonMentionInsertForm},
20     post::Post,
21   },
22   traits::{Crud, DeleteableOrRemoveable},
23   SubscribedType,
24 };
25 use lemmy_db_views::structs::{CommentView, LocalUserView, PostView, PrivateMessageView};
26 use lemmy_db_views_actor::structs::CommunityView;
27 use lemmy_utils::{error::LemmyError, utils::MentionData, ConnectionId};
28
29 #[tracing::instrument(skip_all)]
30 pub async fn send_post_ws_message<OP: ToString + Send + OperationType + 'static>(
31   post_id: PostId,
32   op: OP,
33   websocket_id: Option<ConnectionId>,
34   person_id: Option<PersonId>,
35   context: &LemmyContext,
36 ) -> Result<PostResponse, LemmyError> {
37   let post_view = PostView::read(context.pool(), post_id, person_id).await?;
38
39   let res = PostResponse { post_view };
40
41   context.chat_server().do_send(SendPost {
42     op,
43     post: res.clone(),
44     websocket_id,
45   });
46
47   Ok(res)
48 }
49
50 // TODO: in many call sites in apub crate, we are setting an empty vec for recipient_ids,
51 //       we should get the actual recipient actors from somewhere
52 #[tracing::instrument(skip_all)]
53 pub async fn send_comment_ws_message_simple<OP: ToString + Send + OperationType + 'static>(
54   comment_id: CommentId,
55   op: OP,
56   context: &LemmyContext,
57 ) -> Result<CommentResponse, LemmyError> {
58   send_comment_ws_message(comment_id, op, None, None, None, vec![], context).await
59 }
60
61 #[tracing::instrument(skip_all)]
62 pub async fn send_comment_ws_message<OP: ToString + Send + OperationType + 'static>(
63   comment_id: CommentId,
64   op: OP,
65   websocket_id: Option<ConnectionId>,
66   form_id: Option<String>,
67   person_id: Option<PersonId>,
68   recipient_ids: Vec<LocalUserId>,
69   context: &LemmyContext,
70 ) -> Result<CommentResponse, LemmyError> {
71   let mut view = CommentView::read(context.pool(), comment_id, person_id).await?;
72
73   if view.comment.deleted || view.comment.removed {
74     view.comment = view.comment.blank_out_deleted_or_removed_info();
75   }
76
77   let mut res = CommentResponse {
78     comment_view: view,
79     recipient_ids,
80     // The sent out form id should be null
81     form_id: None,
82   };
83
84   context.chat_server().do_send(SendComment {
85     op,
86     comment: res.clone(),
87     websocket_id,
88   });
89
90   // The recipient_ids should be empty for returns
91   res.recipient_ids = Vec::new();
92   res.form_id = form_id;
93
94   Ok(res)
95 }
96
97 #[tracing::instrument(skip_all)]
98 pub async fn send_community_ws_message<OP: ToString + Send + OperationType + 'static>(
99   community_id: CommunityId,
100   op: OP,
101   websocket_id: Option<ConnectionId>,
102   person_id: Option<PersonId>,
103   context: &LemmyContext,
104 ) -> Result<CommunityResponse, LemmyError> {
105   let community_view = CommunityView::read(context.pool(), community_id, person_id).await?;
106
107   let res = CommunityResponse { community_view };
108
109   // Strip out the person id and subscribed when sending to others
110   let mut res_mut = res.clone();
111   res_mut.community_view.subscribed = SubscribedType::NotSubscribed;
112
113   context.chat_server().do_send(SendCommunityRoomMessage {
114     op,
115     response: res_mut,
116     community_id: res.community_view.community.id,
117     websocket_id,
118   });
119
120   Ok(res)
121 }
122
123 #[tracing::instrument(skip_all)]
124 pub async fn send_pm_ws_message<OP: ToString + Send + OperationType + 'static>(
125   private_message_id: PrivateMessageId,
126   op: OP,
127   websocket_id: Option<ConnectionId>,
128   context: &LemmyContext,
129 ) -> Result<PrivateMessageResponse, LemmyError> {
130   let mut view = PrivateMessageView::read(context.pool(), private_message_id).await?;
131
132   // Blank out deleted or removed info
133   if view.private_message.deleted {
134     view.private_message = view.private_message.blank_out_deleted_or_removed_info();
135   }
136
137   let res = PrivateMessageResponse {
138     private_message_view: view,
139   };
140
141   // Send notifications to the local recipient, if one exists
142   if res.private_message_view.recipient.local {
143     let recipient_id = res.private_message_view.recipient.id;
144     let local_recipient = LocalUserView::read_person(context.pool(), recipient_id).await?;
145     context.chat_server().do_send(SendUserRoomMessage {
146       op,
147       response: res.clone(),
148       local_recipient_id: local_recipient.local_user.id,
149       websocket_id,
150     });
151   }
152
153   Ok(res)
154 }
155
156 #[tracing::instrument(skip_all)]
157 pub async fn send_local_notifs(
158   mentions: Vec<MentionData>,
159   comment: &Comment,
160   person: &Person,
161   post: &Post,
162   do_send_email: bool,
163   context: &LemmyContext,
164 ) -> Result<Vec<LocalUserId>, LemmyError> {
165   let mut recipient_ids = Vec::new();
166   let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
167
168   // Send the local mentions
169   for mention in mentions
170     .iter()
171     .filter(|m| m.is_local(&context.settings().hostname) && m.name.ne(&person.name))
172     .collect::<Vec<&MentionData>>()
173   {
174     let mention_name = mention.name.clone();
175     let user_view = LocalUserView::read_from_name(context.pool(), &mention_name).await;
176     if let Ok(mention_user_view) = user_view {
177       // TODO
178       // At some point, make it so you can't tag the parent creator either
179       // This can cause two notifications, one for reply and the other for mention
180       recipient_ids.push(mention_user_view.local_user.id);
181
182       let user_mention_form = PersonMentionInsertForm {
183         recipient_id: mention_user_view.person.id,
184         comment_id: comment.id,
185         read: None,
186       };
187
188       // Allow this to fail softly, since comment edits might re-update or replace it
189       // Let the uniqueness handle this fail
190       PersonMention::create(context.pool(), &user_mention_form)
191         .await
192         .ok();
193
194       // Send an email to those local users that have notifications on
195       if do_send_email {
196         let lang = get_interface_language(&mention_user_view);
197         send_email_to_user(
198           &mention_user_view,
199           &lang.notification_mentioned_by_subject(&person.name),
200           &lang.notification_mentioned_by_body(&comment.content, &inbox_link, &person.name),
201           context.settings(),
202         )
203       }
204     }
205   }
206
207   // Send comment_reply to the parent commenter / poster
208   if let Some(parent_comment_id) = comment.parent_comment_id() {
209     let parent_comment = Comment::read(context.pool(), parent_comment_id).await?;
210
211     // Get the parent commenter local_user
212     let parent_creator_id = parent_comment.creator_id;
213
214     // Only add to recipients if that person isn't blocked
215     let creator_blocked = check_person_block(person.id, parent_creator_id, context.pool())
216       .await
217       .is_err();
218
219     // Don't send a notif to yourself
220     if parent_comment.creator_id != person.id && !creator_blocked {
221       let user_view = LocalUserView::read_person(context.pool(), parent_creator_id).await;
222       if let Ok(parent_user_view) = user_view {
223         recipient_ids.push(parent_user_view.local_user.id);
224
225         let comment_reply_form = CommentReplyInsertForm {
226           recipient_id: parent_user_view.person.id,
227           comment_id: comment.id,
228           read: None,
229         };
230
231         // Allow this to fail softly, since comment edits might re-update or replace it
232         // Let the uniqueness handle this fail
233         CommentReply::create(context.pool(), &comment_reply_form)
234           .await
235           .ok();
236
237         if do_send_email {
238           let lang = get_interface_language(&parent_user_view);
239           send_email_to_user(
240             &parent_user_view,
241             &lang.notification_comment_reply_subject(&person.name),
242             &lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name),
243             context.settings(),
244           )
245         }
246       }
247     }
248   } else {
249     // If there's no parent, its the post creator
250     // Only add to recipients if that person isn't blocked
251     let creator_blocked = check_person_block(person.id, post.creator_id, context.pool())
252       .await
253       .is_err();
254
255     if post.creator_id != person.id && !creator_blocked {
256       let creator_id = post.creator_id;
257       let parent_user = LocalUserView::read_person(context.pool(), creator_id).await;
258       if let Ok(parent_user_view) = parent_user {
259         recipient_ids.push(parent_user_view.local_user.id);
260
261         let comment_reply_form = CommentReplyInsertForm {
262           recipient_id: parent_user_view.person.id,
263           comment_id: comment.id,
264           read: None,
265         };
266
267         // Allow this to fail softly, since comment edits might re-update or replace it
268         // Let the uniqueness handle this fail
269         CommentReply::create(context.pool(), &comment_reply_form)
270           .await
271           .ok();
272
273         if do_send_email {
274           let lang = get_interface_language(&parent_user_view);
275           send_email_to_user(
276             &parent_user_view,
277             &lang.notification_post_reply_subject(&person.name),
278             &lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name),
279             context.settings(),
280           )
281         }
282       }
283     }
284   }
285
286   Ok(recipient_ids)
287 }