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::source::{
20 community::{Community, CommunityModerator},
24 use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
25 use lemmy_db_views_actor::{
26 community_person_ban_view::CommunityPersonBanView,
27 community_view::CommunityView,
31 settings::structs::Settings,
36 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
37 use serde::Deserialize;
38 use std::process::Command;
49 #[async_trait::async_trait(?Send)]
51 type Response: serde::ser::Serialize + Send;
55 context: &Data<LemmyContext>,
56 websocket_id: Option<ConnectionId>,
57 ) -> Result<Self::Response, LemmyError>;
60 pub(crate) async fn is_mod_or_admin(
64 ) -> Result<(), LemmyError> {
65 let is_mod_or_admin = blocking(pool, move |conn| {
66 CommunityView::is_mod_or_admin(conn, person_id, community_id)
70 return Err(ApiError::err("not_a_mod_or_admin").into());
75 // TODO this probably isn't necessary anymore
76 // pub async fn is_admin(pool: &DbPool, person_id: i32) -> Result<(), LemmyError> {
77 // let user = blocking(pool, move |conn| LocalUser::read(conn, person_id)).await??;
79 // return Err(ApiError::err("not_an_admin").into());
84 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
85 if !local_user_view.local_user.admin {
86 return Err(ApiError::err("not_an_admin").into());
91 pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
92 match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
94 Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
98 pub(crate) async fn get_local_user_view_from_jwt(
101 ) -> Result<LocalUserView, LemmyError> {
102 let claims = match Claims::decode(&jwt) {
103 Ok(claims) => claims.claims,
104 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
106 let local_user_id = claims.id;
107 let local_user_view =
108 blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
109 // Check for a site ban
110 if local_user_view.person.banned {
111 return Err(ApiError::err("site_ban").into());
116 pub(crate) async fn get_local_user_view_from_jwt_opt(
117 jwt: &Option<String>,
119 ) -> Result<Option<LocalUserView>, LemmyError> {
121 Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool).await?)),
126 pub(crate) async fn get_local_user_settings_view_from_jwt(
129 ) -> Result<LocalUserSettingsView, LemmyError> {
130 let claims = match Claims::decode(&jwt) {
131 Ok(claims) => claims.claims,
132 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
134 let local_user_id = claims.id;
135 let local_user_view = blocking(pool, move |conn| {
136 LocalUserSettingsView::read(conn, local_user_id)
139 // Check for a site ban
140 if local_user_view.person.banned {
141 return Err(ApiError::err("site_ban").into());
146 pub(crate) async fn get_local_user_settings_view_from_jwt_opt(
147 jwt: &Option<String>,
149 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
151 Some(jwt) => Ok(Some(
152 get_local_user_settings_view_from_jwt(jwt, pool).await?,
158 pub(crate) async fn check_community_ban(
162 ) -> Result<(), LemmyError> {
164 move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
165 if blocking(pool, is_banned).await? {
166 Err(ApiError::err("community_ban").into())
172 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
174 let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
175 if !site.enable_downvotes {
176 return Err(ApiError::err("downvotes_disabled").into());
182 /// Returns a list of communities that the user moderates
183 /// or if a community_id is supplied validates the user is a moderator
184 /// of that community and returns the community id in a vec
186 /// * `person_id` - the person id of the moderator
187 /// * `community_id` - optional community id to check for moderator privileges
188 /// * `pool` - the diesel db pool
189 pub(crate) async fn collect_moderated_communities(
191 community_id: Option<i32>,
193 ) -> Result<Vec<i32>, LemmyError> {
194 if let Some(community_id) = community_id {
195 // if the user provides a community_id, just check for mod/admin privileges
196 is_mod_or_admin(pool, person_id, community_id).await?;
197 Ok(vec![community_id])
199 let ids = blocking(pool, move |conn: &'_ _| {
200 CommunityModerator::get_person_moderated_communities(conn, person_id)
207 pub(crate) async fn build_federated_instances(
209 ) -> Result<Option<FederatedInstances>, LemmyError> {
210 if Settings::get().federation().enabled {
211 let distinct_communities = blocking(pool, move |conn| {
212 Community::distinct_federated_communities(conn)
216 let allowed = Settings::get().get_allowed_instances();
217 let blocked = Settings::get().get_blocked_instances();
219 let mut linked = distinct_communities
221 .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
222 .collect::<Result<Vec<String>, LemmyError>>()?;
224 if let Some(allowed) = allowed.as_ref() {
225 linked.extend_from_slice(allowed);
228 if let Some(blocked) = blocked.as_ref() {
229 linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname()));
232 // Sort and remove dupes
233 linked.sort_unstable();
236 Ok(Some(FederatedInstances {
246 pub async fn match_websocket_operation(
247 context: LemmyContext,
251 ) -> Result<String, LemmyError> {
254 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
255 UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
256 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
257 UserOperation::GetPersonDetails => {
258 do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
260 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
261 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
262 UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
263 UserOperation::GetPersonMentions => {
264 do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
266 UserOperation::MarkPersonMentionAsRead => {
267 do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
269 UserOperation::MarkAllAsRead => {
270 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
272 UserOperation::DeleteAccount => {
273 do_websocket_operation::<DeleteAccount>(context, id, op, data).await
275 UserOperation::PasswordReset => {
276 do_websocket_operation::<PasswordReset>(context, id, op, data).await
278 UserOperation::PasswordChange => {
279 do_websocket_operation::<PasswordChange>(context, id, op, data).await
281 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
282 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
283 UserOperation::CommunityJoin => {
284 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
286 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
287 UserOperation::SaveUserSettings => {
288 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
290 UserOperation::GetReportCount => {
291 do_websocket_operation::<GetReportCount>(context, id, op, data).await
294 // Private Message ops
295 UserOperation::CreatePrivateMessage => {
296 do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
298 UserOperation::EditPrivateMessage => {
299 do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
301 UserOperation::DeletePrivateMessage => {
302 do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
304 UserOperation::MarkPrivateMessageAsRead => {
305 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
307 UserOperation::GetPrivateMessages => {
308 do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
312 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
313 UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
314 UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
315 UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
316 UserOperation::GetSiteConfig => {
317 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
319 UserOperation::SaveSiteConfig => {
320 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
322 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
323 UserOperation::TransferCommunity => {
324 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
326 UserOperation::TransferSite => {
327 do_websocket_operation::<TransferSite>(context, id, op, data).await
331 UserOperation::GetCommunity => {
332 do_websocket_operation::<GetCommunity>(context, id, op, data).await
334 UserOperation::ListCommunities => {
335 do_websocket_operation::<ListCommunities>(context, id, op, data).await
337 UserOperation::CreateCommunity => {
338 do_websocket_operation::<CreateCommunity>(context, id, op, data).await
340 UserOperation::EditCommunity => {
341 do_websocket_operation::<EditCommunity>(context, id, op, data).await
343 UserOperation::DeleteCommunity => {
344 do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
346 UserOperation::RemoveCommunity => {
347 do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
349 UserOperation::FollowCommunity => {
350 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
352 UserOperation::GetFollowedCommunities => {
353 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
355 UserOperation::BanFromCommunity => {
356 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
358 UserOperation::AddModToCommunity => {
359 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
363 UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
364 UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
365 UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
366 UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
367 UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
368 UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
369 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
370 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
371 UserOperation::CreatePostLike => {
372 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
374 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
375 UserOperation::CreatePostReport => {
376 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
378 UserOperation::ListPostReports => {
379 do_websocket_operation::<ListPostReports>(context, id, op, data).await
381 UserOperation::ResolvePostReport => {
382 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
386 UserOperation::CreateComment => {
387 do_websocket_operation::<CreateComment>(context, id, op, data).await
389 UserOperation::EditComment => {
390 do_websocket_operation::<EditComment>(context, id, op, data).await
392 UserOperation::DeleteComment => {
393 do_websocket_operation::<DeleteComment>(context, id, op, data).await
395 UserOperation::RemoveComment => {
396 do_websocket_operation::<RemoveComment>(context, id, op, data).await
398 UserOperation::MarkCommentAsRead => {
399 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
401 UserOperation::SaveComment => {
402 do_websocket_operation::<SaveComment>(context, id, op, data).await
404 UserOperation::GetComments => {
405 do_websocket_operation::<GetComments>(context, id, op, data).await
407 UserOperation::CreateCommentLike => {
408 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
410 UserOperation::CreateCommentReport => {
411 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
413 UserOperation::ListCommentReports => {
414 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
416 UserOperation::ResolveCommentReport => {
417 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
422 async fn do_websocket_operation<'a, 'b, Data>(
423 context: LemmyContext,
427 ) -> Result<String, LemmyError>
429 for<'de> Data: Deserialize<'de> + 'a,
432 let parsed_data: Data = serde_json::from_str(&data)?;
433 let res = parsed_data
434 .perform(&web::Data::new(context), Some(id))
436 serialize_websocket_message(&op, &res)
439 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
440 let mut built_text = String::new();
442 // Building proper speech text for espeak
443 for mut c in captcha.chars() {
444 let new_str = if c.is_alphabetic() {
445 if c.is_lowercase() {
446 c.make_ascii_uppercase();
447 format!("lower case {} ... ", c)
449 c.make_ascii_uppercase();
450 format!("capital {} ... ", c)
456 built_text.push_str(&new_str);
459 espeak_wav_base64(&built_text)
462 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
463 // Make a temp file path
464 let uuid = uuid::Uuid::new_v4().to_string();
465 let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
467 // Write the wav file
468 Command::new("espeak")
474 // Read the wav file bytes
475 let bytes = std::fs::read(&file_path)?;
478 std::fs::remove_file(file_path)?;
481 let base64 = base64::encode(bytes);
486 /// Checks the password length
487 pub(crate) fn password_length_check(pass: &str) -> Result<(), LemmyError> {
489 Err(ApiError::err("invalid_password").into())
497 use crate::captcha_espeak_wav_base64;
501 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())