1 use actix_web::{web, web::Data};
2 use lemmy_api_structs::{
11 use lemmy_db_queries::{
13 community::{CommunityModerator_, Community_},
15 user::UserSafeSettings_,
20 use lemmy_db_schema::source::{
21 community::{Community, CommunityModerator},
24 user::{UserSafeSettings, User_},
26 use lemmy_db_views_actor::{
27 community_user_ban_view::CommunityUserBanView,
28 community_view::CommunityView,
30 use lemmy_utils::{claims::Claims, settings::Settings, ApiError, ConnectionId, LemmyError};
31 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
32 use serde::Deserialize;
33 use std::process::Command;
44 #[async_trait::async_trait(?Send)]
46 type Response: serde::ser::Serialize + Send;
50 context: &Data<LemmyContext>,
51 websocket_id: Option<ConnectionId>,
52 ) -> Result<Self::Response, LemmyError>;
55 pub(crate) async fn is_mod_or_admin(
59 ) -> Result<(), LemmyError> {
60 let is_mod_or_admin = blocking(pool, move |conn| {
61 CommunityView::is_mod_or_admin(conn, user_id, community_id)
65 return Err(ApiError::err("not_a_mod_or_admin").into());
69 pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
70 let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
72 return Err(ApiError::err("not_an_admin").into());
77 pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
78 match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
80 Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
84 pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
85 let claims = match Claims::decode(&jwt) {
86 Ok(claims) => claims.claims,
87 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
89 let user_id = claims.id;
90 let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
91 // Check for a site ban
93 return Err(ApiError::err("site_ban").into());
98 pub(crate) async fn get_user_from_jwt_opt(
101 ) -> Result<Option<User_>, LemmyError> {
103 Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
108 pub(crate) async fn get_user_safe_settings_from_jwt(
111 ) -> Result<UserSafeSettings, LemmyError> {
112 let claims = match Claims::decode(&jwt) {
113 Ok(claims) => claims.claims,
114 Err(_e) => return Err(ApiError::err("not_logged_in").into()),
116 let user_id = claims.id;
117 let user = blocking(pool, move |conn| UserSafeSettings::read(conn, user_id)).await??;
118 // Check for a site ban
120 return Err(ApiError::err("site_ban").into());
125 pub(crate) async fn get_user_safe_settings_from_jwt_opt(
126 jwt: &Option<String>,
128 ) -> Result<Option<UserSafeSettings>, LemmyError> {
130 Some(jwt) => Ok(Some(get_user_safe_settings_from_jwt(jwt, pool).await?)),
135 pub(crate) async fn check_community_ban(
139 ) -> Result<(), LemmyError> {
140 let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
141 if blocking(pool, is_banned).await? {
142 Err(ApiError::err("community_ban").into())
148 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
150 let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
151 if !site.enable_downvotes {
152 return Err(ApiError::err("downvotes_disabled").into());
158 /// Returns a list of communities that the user moderates
159 /// or if a community_id is supplied validates the user is a moderator
160 /// of that community and returns the community id in a vec
162 /// * `user_id` - the user id of the moderator
163 /// * `community_id` - optional community id to check for moderator privileges
164 /// * `pool` - the diesel db pool
165 pub(crate) async fn collect_moderated_communities(
167 community_id: Option<i32>,
169 ) -> Result<Vec<i32>, LemmyError> {
170 if let Some(community_id) = community_id {
171 // if the user provides a community_id, just check for mod/admin privileges
172 is_mod_or_admin(pool, user_id, community_id).await?;
173 Ok(vec![community_id])
175 let ids = blocking(pool, move |conn: &'_ _| {
176 CommunityModerator::get_user_moderated_communities(conn, user_id)
183 pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
184 if let Some(Some(item)) = &item {
185 if Url::parse(item).is_err() {
186 return Err(ApiError::err("invalid_url").into());
192 pub(crate) async fn build_federated_instances(
194 ) -> Result<Option<FederatedInstances>, LemmyError> {
195 if Settings::get().federation.enabled {
196 let distinct_communities = blocking(pool, move |conn| {
197 Community::distinct_federated_communities(conn)
201 let allowed = Settings::get().get_allowed_instances();
202 let blocked = Settings::get().get_blocked_instances();
204 let mut linked = distinct_communities
206 .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
207 .collect::<Result<Vec<String>, LemmyError>>()?;
209 linked.extend_from_slice(&allowed);
210 linked.retain(|a| !blocked.contains(a) && !a.eq("") && !a.eq(&Settings::get().hostname));
212 // Sort and remove dupes
213 linked.sort_unstable();
216 Ok(Some(FederatedInstances {
226 pub async fn match_websocket_operation(
227 context: LemmyContext,
231 ) -> Result<String, LemmyError> {
234 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
235 UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
236 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
237 UserOperation::GetUserDetails => {
238 do_websocket_operation::<GetUserDetails>(context, id, op, data).await
240 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
241 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
242 UserOperation::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
243 UserOperation::GetUserMentions => {
244 do_websocket_operation::<GetUserMentions>(context, id, op, data).await
246 UserOperation::MarkUserMentionAsRead => {
247 do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
249 UserOperation::MarkAllAsRead => {
250 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
252 UserOperation::DeleteAccount => {
253 do_websocket_operation::<DeleteAccount>(context, id, op, data).await
255 UserOperation::PasswordReset => {
256 do_websocket_operation::<PasswordReset>(context, id, op, data).await
258 UserOperation::PasswordChange => {
259 do_websocket_operation::<PasswordChange>(context, id, op, data).await
261 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
262 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
263 UserOperation::CommunityJoin => {
264 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
266 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
267 UserOperation::SaveUserSettings => {
268 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
270 UserOperation::GetReportCount => {
271 do_websocket_operation::<GetReportCount>(context, id, op, data).await
274 // Private Message ops
275 UserOperation::CreatePrivateMessage => {
276 do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
278 UserOperation::EditPrivateMessage => {
279 do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
281 UserOperation::DeletePrivateMessage => {
282 do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
284 UserOperation::MarkPrivateMessageAsRead => {
285 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
287 UserOperation::GetPrivateMessages => {
288 do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
292 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
293 UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
294 UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
295 UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
296 UserOperation::GetSiteConfig => {
297 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
299 UserOperation::SaveSiteConfig => {
300 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
302 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
303 UserOperation::TransferCommunity => {
304 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
306 UserOperation::TransferSite => {
307 do_websocket_operation::<TransferSite>(context, id, op, data).await
311 UserOperation::GetCommunity => {
312 do_websocket_operation::<GetCommunity>(context, id, op, data).await
314 UserOperation::ListCommunities => {
315 do_websocket_operation::<ListCommunities>(context, id, op, data).await
317 UserOperation::CreateCommunity => {
318 do_websocket_operation::<CreateCommunity>(context, id, op, data).await
320 UserOperation::EditCommunity => {
321 do_websocket_operation::<EditCommunity>(context, id, op, data).await
323 UserOperation::DeleteCommunity => {
324 do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
326 UserOperation::RemoveCommunity => {
327 do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
329 UserOperation::FollowCommunity => {
330 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
332 UserOperation::GetFollowedCommunities => {
333 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
335 UserOperation::BanFromCommunity => {
336 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
338 UserOperation::AddModToCommunity => {
339 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
343 UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
344 UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
345 UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
346 UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
347 UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
348 UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
349 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
350 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
351 UserOperation::CreatePostLike => {
352 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
354 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
355 UserOperation::CreatePostReport => {
356 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
358 UserOperation::ListPostReports => {
359 do_websocket_operation::<ListPostReports>(context, id, op, data).await
361 UserOperation::ResolvePostReport => {
362 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
366 UserOperation::CreateComment => {
367 do_websocket_operation::<CreateComment>(context, id, op, data).await
369 UserOperation::EditComment => {
370 do_websocket_operation::<EditComment>(context, id, op, data).await
372 UserOperation::DeleteComment => {
373 do_websocket_operation::<DeleteComment>(context, id, op, data).await
375 UserOperation::RemoveComment => {
376 do_websocket_operation::<RemoveComment>(context, id, op, data).await
378 UserOperation::MarkCommentAsRead => {
379 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
381 UserOperation::SaveComment => {
382 do_websocket_operation::<SaveComment>(context, id, op, data).await
384 UserOperation::GetComments => {
385 do_websocket_operation::<GetComments>(context, id, op, data).await
387 UserOperation::CreateCommentLike => {
388 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
390 UserOperation::CreateCommentReport => {
391 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
393 UserOperation::ListCommentReports => {
394 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
396 UserOperation::ResolveCommentReport => {
397 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
402 async fn do_websocket_operation<'a, 'b, Data>(
403 context: LemmyContext,
407 ) -> Result<String, LemmyError>
409 for<'de> Data: Deserialize<'de> + 'a,
412 let parsed_data: Data = serde_json::from_str(&data)?;
413 let res = parsed_data
414 .perform(&web::Data::new(context), Some(id))
416 serialize_websocket_message(&op, &res)
419 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
420 let mut built_text = String::new();
422 // Building proper speech text for espeak
423 for mut c in captcha.chars() {
424 let new_str = if c.is_alphabetic() {
425 if c.is_lowercase() {
426 c.make_ascii_uppercase();
427 format!("lower case {} ... ", c)
429 c.make_ascii_uppercase();
430 format!("capital {} ... ", c)
436 built_text.push_str(&new_str);
439 espeak_wav_base64(&built_text)
442 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
443 // Make a temp file path
444 let uuid = uuid::Uuid::new_v4().to_string();
445 let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
447 // Write the wav file
448 Command::new("espeak")
454 // Read the wav file bytes
455 let bytes = std::fs::read(&file_path)?;
458 std::fs::remove_file(file_path)?;
461 let base64 = base64::encode(bytes);
468 use crate::captcha_espeak_wav_base64;
472 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())