1 use actix_web::{web, web::Data};
2 use lemmy_api_structs::{
11 use lemmy_db_queries::{
13 community::{CommunityModerator_, Community_},
19 use lemmy_db_schema::{
21 community::{Community, CommunityModerator},
30 use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
31 use lemmy_db_views_actor::{
32 community_person_ban_view::CommunityPersonBanView,
33 community_view::CommunityView,
37 settings::structs::Settings,
42 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
43 use serde::Deserialize;
44 use std::process::Command;
55 #[async_trait::async_trait(?Send)]
57 type Response: serde::ser::Serialize + Send;
61 context: &Data<LemmyContext>,
62 websocket_id: Option<ConnectionId>,
63 ) -> Result<Self::Response, LemmyError>;
66 pub(crate) async fn is_mod_or_admin(
69 community_id: CommunityId,
70 ) -> Result<(), LemmyError> {
71 let is_mod_or_admin = blocking(pool, move |conn| {
72 CommunityView::is_mod_or_admin(conn, person_id, community_id)
76 return Err(ApiError::err("not_a_mod_or_admin").into());
81 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
82 if !local_user_view.local_user.admin {
83 return Err(ApiError::err("not_an_admin").into());
88 pub(crate) async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
89 match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
91 Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
95 pub(crate) async fn get_local_user_view_from_jwt(
98 ) -> Result<LocalUserView, LemmyError> {
99 let claims = match Claims::decode(&jwt) {
100 Ok(claims) => claims.claims,
101 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
103 let local_user_id = LocalUserId(claims.local_user_id);
104 let local_user_view =
105 blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
106 // Check for a site ban
107 if local_user_view.person.banned {
108 return Err(ApiError::err("site_ban").into());
113 pub(crate) async fn get_local_user_view_from_jwt_opt(
114 jwt: &Option<String>,
116 ) -> Result<Option<LocalUserView>, LemmyError> {
118 Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)),
123 pub(crate) async fn get_local_user_settings_view_from_jwt(
126 ) -> Result<LocalUserSettingsView, LemmyError> {
127 let claims = match Claims::decode(&jwt) {
128 Ok(claims) => claims.claims,
129 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
131 let local_user_id = LocalUserId(claims.local_user_id);
132 let local_user_view = blocking(pool, move |conn| {
133 LocalUserSettingsView::read(conn, local_user_id)
136 // Check for a site ban
137 if local_user_view.person.banned {
138 return Err(ApiError::err("site_ban").into());
143 pub(crate) async fn get_local_user_settings_view_from_jwt_opt(
144 jwt: &Option<String>,
146 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
148 Some(jwt) => Ok(Some(
149 get_local_user_settings_view_from_jwt(jwt, pool).await?,
155 pub(crate) async fn check_community_ban(
157 community_id: CommunityId,
159 ) -> Result<(), LemmyError> {
161 move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
162 if blocking(pool, is_banned).await? {
163 Err(ApiError::err("community_ban").into())
169 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
171 let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
172 if !site.enable_downvotes {
173 return Err(ApiError::err("downvotes_disabled").into());
179 /// Returns a list of communities that the user moderates
180 /// or if a community_id is supplied validates the user is a moderator
181 /// of that community and returns the community id in a vec
183 /// * `person_id` - the person id of the moderator
184 /// * `community_id` - optional community id to check for moderator privileges
185 /// * `pool` - the diesel db pool
186 pub(crate) async fn collect_moderated_communities(
188 community_id: Option<CommunityId>,
190 ) -> Result<Vec<CommunityId>, LemmyError> {
191 if let Some(community_id) = community_id {
192 // if the user provides a community_id, just check for mod/admin privileges
193 is_mod_or_admin(pool, person_id, community_id).await?;
194 Ok(vec![community_id])
196 let ids = blocking(pool, move |conn: &'_ _| {
197 CommunityModerator::get_person_moderated_communities(conn, person_id)
204 pub(crate) async fn build_federated_instances(
206 ) -> Result<Option<FederatedInstances>, LemmyError> {
207 if Settings::get().federation().enabled {
208 let distinct_communities = blocking(pool, move |conn| {
209 Community::distinct_federated_communities(conn)
213 let allowed = Settings::get().get_allowed_instances();
214 let blocked = Settings::get().get_blocked_instances();
216 let mut linked = distinct_communities
218 .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
219 .collect::<Result<Vec<String>, LemmyError>>()?;
221 if let Some(allowed) = allowed.as_ref() {
222 linked.extend_from_slice(allowed);
225 if let Some(blocked) = blocked.as_ref() {
226 linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
229 // Sort and remove dupes
230 linked.sort_unstable();
233 Ok(Some(FederatedInstances {
243 pub async fn match_websocket_operation(
244 context: LemmyContext,
248 ) -> Result<String, LemmyError> {
251 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
252 UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
253 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
254 UserOperation::GetPersonDetails => {
255 do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
257 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
258 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
259 UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
260 UserOperation::GetPersonMentions => {
261 do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
263 UserOperation::MarkPersonMentionAsRead => {
264 do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
266 UserOperation::MarkAllAsRead => {
267 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
269 UserOperation::DeleteAccount => {
270 do_websocket_operation::<DeleteAccount>(context, id, op, data).await
272 UserOperation::PasswordReset => {
273 do_websocket_operation::<PasswordReset>(context, id, op, data).await
275 UserOperation::PasswordChange => {
276 do_websocket_operation::<PasswordChange>(context, id, op, data).await
278 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
279 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
280 UserOperation::CommunityJoin => {
281 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
283 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
284 UserOperation::SaveUserSettings => {
285 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
287 UserOperation::GetReportCount => {
288 do_websocket_operation::<GetReportCount>(context, id, op, data).await
291 // Private Message ops
292 UserOperation::CreatePrivateMessage => {
293 do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
295 UserOperation::EditPrivateMessage => {
296 do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
298 UserOperation::DeletePrivateMessage => {
299 do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
301 UserOperation::MarkPrivateMessageAsRead => {
302 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
304 UserOperation::GetPrivateMessages => {
305 do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
309 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
310 UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
311 UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
312 UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
313 UserOperation::GetSiteConfig => {
314 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
316 UserOperation::SaveSiteConfig => {
317 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
319 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
320 UserOperation::TransferCommunity => {
321 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
323 UserOperation::TransferSite => {
324 do_websocket_operation::<TransferSite>(context, id, op, data).await
328 UserOperation::GetCommunity => {
329 do_websocket_operation::<GetCommunity>(context, id, op, data).await
331 UserOperation::ListCommunities => {
332 do_websocket_operation::<ListCommunities>(context, id, op, data).await
334 UserOperation::CreateCommunity => {
335 do_websocket_operation::<CreateCommunity>(context, id, op, data).await
337 UserOperation::EditCommunity => {
338 do_websocket_operation::<EditCommunity>(context, id, op, data).await
340 UserOperation::DeleteCommunity => {
341 do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
343 UserOperation::RemoveCommunity => {
344 do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
346 UserOperation::FollowCommunity => {
347 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
349 UserOperation::GetFollowedCommunities => {
350 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
352 UserOperation::BanFromCommunity => {
353 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
355 UserOperation::AddModToCommunity => {
356 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
360 UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
361 UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
362 UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
363 UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
364 UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
365 UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
366 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
367 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
368 UserOperation::CreatePostLike => {
369 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
371 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
372 UserOperation::CreatePostReport => {
373 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
375 UserOperation::ListPostReports => {
376 do_websocket_operation::<ListPostReports>(context, id, op, data).await
378 UserOperation::ResolvePostReport => {
379 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
383 UserOperation::CreateComment => {
384 do_websocket_operation::<CreateComment>(context, id, op, data).await
386 UserOperation::EditComment => {
387 do_websocket_operation::<EditComment>(context, id, op, data).await
389 UserOperation::DeleteComment => {
390 do_websocket_operation::<DeleteComment>(context, id, op, data).await
392 UserOperation::RemoveComment => {
393 do_websocket_operation::<RemoveComment>(context, id, op, data).await
395 UserOperation::MarkCommentAsRead => {
396 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
398 UserOperation::SaveComment => {
399 do_websocket_operation::<SaveComment>(context, id, op, data).await
401 UserOperation::GetComments => {
402 do_websocket_operation::<GetComments>(context, id, op, data).await
404 UserOperation::CreateCommentLike => {
405 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
407 UserOperation::CreateCommentReport => {
408 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
410 UserOperation::ListCommentReports => {
411 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
413 UserOperation::ResolveCommentReport => {
414 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
419 async fn do_websocket_operation<'a, 'b, Data>(
420 context: LemmyContext,
424 ) -> Result<String, LemmyError>
426 for<'de> Data: Deserialize<'de> + 'a,
429 let parsed_data: Data = serde_json::from_str(&data)?;
430 let res = parsed_data
431 .perform(&web::Data::new(context), Some(id))
433 serialize_websocket_message(&op, &res)
436 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
437 let mut built_text = String::new();
439 // Building proper speech text for espeak
440 for mut c in captcha.chars() {
441 let new_str = if c.is_alphabetic() {
442 if c.is_lowercase() {
443 c.make_ascii_uppercase();
444 format!("lower case {} ... ", c)
446 c.make_ascii_uppercase();
447 format!("capital {} ... ", c)
453 built_text.push_str(&new_str);
456 espeak_wav_base64(&built_text)
459 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
460 // Make a temp file path
461 let uuid = uuid::Uuid::new_v4().to_string();
462 let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
464 // Write the wav file
465 Command::new("espeak")
471 // Read the wav file bytes
472 let bytes = std::fs::read(&file_path)?;
475 std::fs::remove_file(file_path)?;
478 let base64 = base64::encode(bytes);
483 /// Checks the password length
484 pub(crate) fn password_length_check(pass: &str) -> Result<(), LemmyError> {
486 Err(ApiError::err("invalid_password").into())
494 use crate::captcha_espeak_wav_base64;
498 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())