1 use crate::claims::Claims;
2 use actix_web::{web, web::Data};
4 source::community::{CommunityModerator_, Community_},
5 views::community::community_user_ban_view::CommunityUserBanView,
9 use lemmy_db_schema::source::{
10 community::{Community, CommunityModerator},
14 use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*};
15 use lemmy_utils::{settings::Settings, APIError, ConnectionId, LemmyError};
16 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
17 use serde::Deserialize;
18 use std::process::Command;
29 #[async_trait::async_trait(?Send)]
31 type Response: serde::ser::Serialize + Send;
35 context: &Data<LemmyContext>,
36 websocket_id: Option<ConnectionId>,
37 ) -> Result<Self::Response, LemmyError>;
40 pub(crate) async fn is_mod_or_admin(
44 ) -> Result<(), LemmyError> {
45 let is_mod_or_admin = blocking(pool, move |conn| {
46 Community::is_mod_or_admin(conn, user_id, community_id)
50 return Err(APIError::err("not_a_mod_or_admin").into());
54 pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
55 let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
57 return Err(APIError::err("not_an_admin").into());
62 pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
63 match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
65 Err(_e) => Err(APIError::err("couldnt_find_post").into()),
69 pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
70 let claims = match Claims::decode(&jwt) {
71 Ok(claims) => claims.claims,
72 Err(_e) => return Err(APIError::err("not_logged_in").into()),
74 let user_id = claims.id;
75 let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
76 // Check for a site ban
78 return Err(APIError::err("site_ban").into());
83 pub(crate) async fn get_user_from_jwt_opt(
86 ) -> Result<Option<User_>, LemmyError> {
88 Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
93 pub(crate) async fn check_community_ban(
97 ) -> Result<(), LemmyError> {
98 let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
99 if blocking(pool, is_banned).await? {
100 Err(APIError::err("community_ban").into())
106 /// Returns a list of communities that the user moderates
107 /// or if a community_id is supplied validates the user is a moderator
108 /// of that community and returns the community id in a vec
110 /// * `user_id` - the user id of the moderator
111 /// * `community_id` - optional community id to check for moderator privileges
112 /// * `pool` - the diesel db pool
113 pub(crate) async fn collect_moderated_communities(
115 community_id: Option<i32>,
117 ) -> Result<Vec<i32>, LemmyError> {
118 if let Some(community_id) = community_id {
119 // if the user provides a community_id, just check for mod/admin privileges
120 is_mod_or_admin(pool, user_id, community_id).await?;
121 Ok(vec![community_id])
123 let ids = blocking(pool, move |conn: &'_ _| {
124 CommunityModerator::get_user_moderated_communities(conn, user_id)
131 pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
132 if let Some(Some(item)) = &item {
133 if Url::parse(item).is_err() {
134 return Err(APIError::err("invalid_url").into());
140 pub(crate) async fn linked_instances(pool: &DbPool) -> Result<Vec<String>, LemmyError> {
141 let mut instances: Vec<String> = Vec::new();
143 if Settings::get().federation.enabled {
144 let distinct_communities = blocking(pool, move |conn| {
145 Community::distinct_federated_communities(conn)
149 instances = distinct_communities
151 .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
152 .collect::<Result<Vec<String>, LemmyError>>()?;
154 instances.append(&mut Settings::get().get_allowed_instances());
155 instances.retain(|a| {
156 !Settings::get().get_blocked_instances().contains(a)
158 && !a.eq(&Settings::get().hostname)
161 // Sort and remove dupes
162 instances.sort_unstable();
169 pub async fn match_websocket_operation(
170 context: LemmyContext,
174 ) -> Result<String, LemmyError> {
177 UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
178 UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
179 UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
180 UserOperation::GetUserDetails => {
181 do_websocket_operation::<GetUserDetails>(context, id, op, data).await
183 UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
184 UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
185 UserOperation::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
186 UserOperation::GetUserMentions => {
187 do_websocket_operation::<GetUserMentions>(context, id, op, data).await
189 UserOperation::MarkUserMentionAsRead => {
190 do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
192 UserOperation::MarkAllAsRead => {
193 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
195 UserOperation::DeleteAccount => {
196 do_websocket_operation::<DeleteAccount>(context, id, op, data).await
198 UserOperation::PasswordReset => {
199 do_websocket_operation::<PasswordReset>(context, id, op, data).await
201 UserOperation::PasswordChange => {
202 do_websocket_operation::<PasswordChange>(context, id, op, data).await
204 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
205 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
206 UserOperation::CommunityJoin => {
207 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
209 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
210 UserOperation::SaveUserSettings => {
211 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
213 UserOperation::GetReportCount => {
214 do_websocket_operation::<GetReportCount>(context, id, op, data).await
217 // Private Message ops
218 UserOperation::CreatePrivateMessage => {
219 do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
221 UserOperation::EditPrivateMessage => {
222 do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
224 UserOperation::DeletePrivateMessage => {
225 do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
227 UserOperation::MarkPrivateMessageAsRead => {
228 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
230 UserOperation::GetPrivateMessages => {
231 do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
235 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
236 UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
237 UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
238 UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
239 UserOperation::GetSiteConfig => {
240 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
242 UserOperation::SaveSiteConfig => {
243 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
245 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
246 UserOperation::TransferCommunity => {
247 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
249 UserOperation::TransferSite => {
250 do_websocket_operation::<TransferSite>(context, id, op, data).await
252 UserOperation::ListCategories => {
253 do_websocket_operation::<ListCategories>(context, id, op, data).await
257 UserOperation::GetCommunity => {
258 do_websocket_operation::<GetCommunity>(context, id, op, data).await
260 UserOperation::ListCommunities => {
261 do_websocket_operation::<ListCommunities>(context, id, op, data).await
263 UserOperation::CreateCommunity => {
264 do_websocket_operation::<CreateCommunity>(context, id, op, data).await
266 UserOperation::EditCommunity => {
267 do_websocket_operation::<EditCommunity>(context, id, op, data).await
269 UserOperation::DeleteCommunity => {
270 do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
272 UserOperation::RemoveCommunity => {
273 do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
275 UserOperation::FollowCommunity => {
276 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
278 UserOperation::GetFollowedCommunities => {
279 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
281 UserOperation::BanFromCommunity => {
282 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
284 UserOperation::AddModToCommunity => {
285 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
289 UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
290 UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
291 UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
292 UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
293 UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
294 UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
295 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
296 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
297 UserOperation::CreatePostLike => {
298 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
300 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
301 UserOperation::CreatePostReport => {
302 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
304 UserOperation::ListPostReports => {
305 do_websocket_operation::<ListPostReports>(context, id, op, data).await
307 UserOperation::ResolvePostReport => {
308 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
312 UserOperation::CreateComment => {
313 do_websocket_operation::<CreateComment>(context, id, op, data).await
315 UserOperation::EditComment => {
316 do_websocket_operation::<EditComment>(context, id, op, data).await
318 UserOperation::DeleteComment => {
319 do_websocket_operation::<DeleteComment>(context, id, op, data).await
321 UserOperation::RemoveComment => {
322 do_websocket_operation::<RemoveComment>(context, id, op, data).await
324 UserOperation::MarkCommentAsRead => {
325 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
327 UserOperation::SaveComment => {
328 do_websocket_operation::<SaveComment>(context, id, op, data).await
330 UserOperation::GetComments => {
331 do_websocket_operation::<GetComments>(context, id, op, data).await
333 UserOperation::CreateCommentLike => {
334 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
336 UserOperation::CreateCommentReport => {
337 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
339 UserOperation::ListCommentReports => {
340 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
342 UserOperation::ResolveCommentReport => {
343 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
348 async fn do_websocket_operation<'a, 'b, Data>(
349 context: LemmyContext,
353 ) -> Result<String, LemmyError>
355 for<'de> Data: Deserialize<'de> + 'a,
358 let parsed_data: Data = serde_json::from_str(&data)?;
359 let res = parsed_data
360 .perform(&web::Data::new(context), Some(id))
362 serialize_websocket_message(&op, &res)
365 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
366 let mut built_text = String::new();
368 // Building proper speech text for espeak
369 for mut c in captcha.chars() {
370 let new_str = if c.is_alphabetic() {
371 if c.is_lowercase() {
372 c.make_ascii_uppercase();
373 format!("lower case {} ... ", c)
375 c.make_ascii_uppercase();
376 format!("capital {} ... ", c)
382 built_text.push_str(&new_str);
385 espeak_wav_base64(&built_text)
388 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
389 // Make a temp file path
390 let uuid = uuid::Uuid::new_v4().to_string();
391 let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
393 // Write the wav file
394 Command::new("espeak")
400 // Read the wav file bytes
401 let bytes = std::fs::read(&file_path)?;
404 std::fs::remove_file(file_path)?;
407 let base64 = base64::encode(bytes);
414 use crate::captcha_espeak_wav_base64;
418 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())