use std::collections::HashMap; use std::env; use std::env::VarError; use lemmy_api_common::community::{ListCommunities}; use lemmy_api_common::person::{Login, LoginResponse}; use lemmy_api_common::post::{GetPosts}; use lemmy_api_common::sensitive::Sensitive; use lemmy_db_schema::newtypes::{CommunityId, InstanceId, LanguageId, PersonId, PostId}; use lemmy_db_schema::{ListingType, PostFeatureType, SubscribedType}; use reqwest::StatusCode; use serde_derive::{Deserialize, Serialize}; use url::Url; use crate::HTTP_CLIENT; pub(crate) struct Credentials { username: String, password: String } impl Credentials { pub(crate) fn get_username(&self) -> Sensitive { Sensitive::new(self.username.clone()) } pub(crate) fn get_password(&self) -> Sensitive { Sensitive::new(self.password.clone()) } pub(crate) fn set_credentials() -> Result { let username = env::var("LEMMY_USERNAME")?; let password = env::var("LEMMY_PASSWORD")?; Ok(Credentials { username, password, }) } } pub(crate) struct Lemmy { jwt_token: Sensitive, instance: String, } pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result { let login_params = Login { username_or_email: credentials.get_username(), password: credentials.get_password(), totp_2fa_token: None, }; let response = match HTTP_CLIENT .post(instance.to_string() + "/api/v3/user/login") .json(&login_params) .send() .await { Ok(data) => data, Err(e) => return Err(format!("{}", e)) }; match response.status() { StatusCode::OK => { let data: LoginResponse = response.json().await.expect("Successful Login Request should return JSON"); match data.jwt { Some(token) => Ok(Lemmy { jwt_token: token.clone(), instance: instance.to_string(), }), None => panic!("Login did not return JWT token. Are the credentials valid?") } }, status => panic!("Unexpected HTTP Status '{}' during Login", status) } } impl Lemmy { pub(crate) async fn post(&self, post: CustomCreatePost) -> Result { let response = match HTTP_CLIENT .post(format!("{}/api/v3/post", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .json(&post) .send() .await { Ok(data) => { match data.text().await { Ok(data) => data, Err(e) => return Err(format!("{}", e)) } }, Err(e) => return Err(format!("{}", e)) }; let json_data = match serde_json::from_str::>(&response) { Ok(mut data) => data.remove("post_view").expect("Element should be present"), Err(e) => return Err(format!("{}", e)) }; Ok(json_data.post.id) } async fn feature(&self, params: CustomFeaturePost) -> Result { let response = match HTTP_CLIENT .post(format!("{}/api/v3/post/feature", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .json(¶ms) .send() .await { Ok(data) => { match data.text().await { Ok(data) => data, Err(e) => return Err(format!("{}", e)) } }, Err(e) => return Err(format!("{}", e)) }; let json_data = match serde_json::from_str::>(&response) { Ok(mut data) => data.remove("post_view").expect("Element should be present"), Err(e) => return Err(format!("{}", e)) }; Ok(json_data) } pub(crate) async fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Result { let pin_params = CustomFeaturePost { post_id, featured: false, feature_type: location, }; self.feature(pin_params).await } pub(crate) async fn pin(&self, post_id: PostId, location: PostFeatureType) -> Result { let pin_params = CustomFeaturePost { post_id, featured: true, feature_type: location, }; self.feature(pin_params).await } pub(crate) async fn get_community_pinned(&self, community: CommunityId) -> Result, String> { let list_params = GetPosts { community_id: Some(community), type_: Some(ListingType::Local), ..Default::default() }; let response = match HTTP_CLIENT .get(format!("{}/api/v3/post/list", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .query(&list_params) .send() .await { Ok(data) => { match data.text().await { Ok(data) => data, Err(e) => return Err(format!("{}", e)) } }, Err(e) => return Err(format!("{}", e)) }; let json_data: CustomGetPostsResponse = match serde_json::from_str(&response) { Ok(data) => data, Err(e) => return Err(format!("{}", e)) }; Ok(json_data.posts.iter().filter(|post| { post.post.featured_community }) .cloned() .collect() ) } pub(crate) async fn get_local_pinned(&self) -> Result, String> { let list_params = GetPosts { type_: Some(ListingType::Local), ..Default::default() }; let response = match HTTP_CLIENT .get(format!("{}/api/v3/post/list", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .query(&list_params) .send() .await { Ok(data) => { match data.text().await { Ok(data) => data, Err(e) => return Err(format!("{}", e)) } }, Err(e) => return Err(format!("{}", e)) }; let json_data: CustomGetPostsResponse = match serde_json::from_str(&response) { Ok(data) => data, Err(e) => return Err(format!("{}", e)) }; Ok(json_data.posts.iter().filter(|post| { post.post.featured_local }) .cloned() .collect() ) } pub(crate) async fn get_communities(&self) -> Result, String> { let list_params = ListCommunities { type_: Some(ListingType::Local), ..Default::default() }; let response = match HTTP_CLIENT .get(format!("{}/api/v3/community/list", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .query(&list_params) .send() .await { Ok(data) => { match data.text().await { Ok(data) => data, Err(e) => return Err(format!("{}", e)) } }, Err(e) => return Err(format!("{}", e)) }; let json_data: CustomListCommunitiesResponse = match serde_json::from_str(&response) { Ok(data) => data, Err(e) => return Err(format!("{}", e)) }; let mut communities: HashMap = HashMap::new(); for community_view in json_data.communities { let community = community_view.community; communities.insert(community.name, community.id); } Ok(communities) } } #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub(crate) struct CustomCreatePost { pub(crate) name: String, pub(crate) community_id: CommunityId, pub(crate) url: Option, pub(crate) body: Option, pub(crate) honeypot: Option, pub(crate) nsfw: Option, pub(crate) language_id: Option, } #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub(crate) struct CustomFeaturePost { pub(crate) post_id: PostId, pub(crate) featured: bool, pub(crate) feature_type: PostFeatureType, } #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub(crate) struct CustomListCommunitiesResponse { pub(crate) communities: Vec } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomCommunityView { pub(crate) community: CustomCommunity, pub(crate) subscribed: SubscribedType, pub(crate) blocked: bool, pub(crate) counts: CustomCommunityAggregates } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomCommunity { pub(crate) actor_id: String, pub(crate) banner: Option, pub(crate) deleted: bool, pub(crate) description: Option, pub(crate) hidden: bool, pub(crate) icon: Option, pub(crate) id: CommunityId, pub(crate) instance_id: i32, pub(crate) local: bool, pub(crate) name: String, pub(crate) nsfw: bool, pub(crate) posting_restricted_to_mods: bool, pub(crate) published: String, pub(crate) removed: bool, pub(crate) title: String, pub(crate) updated: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomCommunityAggregates { pub(crate) comments: i64, pub(crate) community_id: i32, pub(crate) posts: i64, pub(crate) published: String, pub(crate) subscribers: i64, pub(crate) users_active_day: i64, pub(crate) users_active_half_year: i64, pub(crate) users_active_month: i64, pub(crate) users_active_week: i64, } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomPostView { pub(crate) community: CustomCommunity, pub(crate) counts: CustomPostAggregates, pub(crate) creator: CustomPerson, pub(crate) creator_banned_from_community: bool, pub(crate) creator_blocked: bool, pub(crate) creator_is_admin: bool, pub(crate) creator_is_moderator: bool, pub(crate) my_vote: Option, pub(crate) post: CustomPost, pub(crate) read: bool, pub(crate) saved: bool, pub(crate) subscribed: SubscribedType, pub(crate) unread_comments: i64, } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomPostAggregates { pub(crate) comments: i64, pub(crate) downvotes: i64, pub(crate) post_id: PostId, pub(crate) published: String, pub(crate) score: i64, pub(crate) upvotes: i64, } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomPerson { pub(crate) actor_id: String, pub(crate) avatar: Option, pub(crate) ban_expires: Option, pub(crate) banned: bool, pub(crate) banner: Option, pub(crate) bio: Option, pub(crate) bot_account: bool, pub(crate) deleted: bool, pub(crate) display_name: Option, pub(crate) id: PersonId, pub(crate) instance_id : InstanceId, pub(crate) local: bool, pub(crate) matrix_user_id: Option, pub(crate) name: String, pub(crate) published: String, pub(crate) updated: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomPost { pub(crate) id: PostId, pub(crate) name: String, pub(crate) url: Option, pub(crate) body: Option, pub(crate) creator_id: PersonId, pub(crate) community_id: CommunityId, pub(crate) removed: bool, pub(crate) locked: bool, pub(crate) published: String, pub(crate) updated: Option, pub(crate) deleted: bool, pub(crate) nsfw: bool, pub(crate) embed_title: Option, pub(crate) embed_description: Option, pub(crate) thumbnail_url: Option, pub(crate) ap_id: String, pub(crate) local: bool, pub(crate) embed_video_url: Option, pub(crate) language_id: LanguageId, pub(crate) featured_community: bool, pub(crate) featured_local: bool, } #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct CustomGetPostsResponse { pub(crate) posts: Vec, }