2024-05-07 20:10:21 +00:00
use std ::cmp ::Ordering ;
use crate ::config ::{ Config , PostBody , PostConfig , SeriesConfig } ;
2024-05-06 18:51:31 +00:00
use crate ::{ HTTP_CLIENT } ;
2023-12-19 21:08:23 +00:00
use lemmy_api_common ::community ::{ ListCommunities , ListCommunitiesResponse } ;
use lemmy_api_common ::lemmy_db_views ::structs ::PostView ;
2023-12-17 17:03:11 +00:00
use lemmy_api_common ::person ::{ Login , LoginResponse } ;
2023-12-19 21:08:23 +00:00
use lemmy_api_common ::post ::{ CreatePost , FeaturePost , GetPosts , GetPostsResponse } ;
2023-12-17 17:03:11 +00:00
use lemmy_api_common ::sensitive ::Sensitive ;
2024-05-07 20:10:21 +00:00
use lemmy_db_schema ::newtypes ::{ CommunityId , LanguageId , PostId } ;
2023-12-19 21:08:23 +00:00
use lemmy_db_schema ::{ ListingType , PostFeatureType } ;
2023-12-17 17:03:11 +00:00
use reqwest ::StatusCode ;
2023-12-30 00:27:11 +00:00
use std ::collections ::HashMap ;
2024-05-07 20:10:21 +00:00
use std ::sync ::{ RwLock } ;
2024-05-06 18:53:50 +00:00
use serde ::{ Deserialize , Serialize } ;
2024-01-08 20:06:52 +00:00
use url ::Url ;
2024-05-06 18:51:31 +00:00
use systemd_journal_logger ::connected_to_journal ;
macro_rules ! error {
( $msg :tt ) = > {
match connected_to_journal ( ) {
true = > log ::error! ( " [ERROR] {} " , $msg ) ,
false = > eprintln! ( " [ERROR] {} " , $msg ) ,
}
} ;
}
2023-12-17 17:03:11 +00:00
pub ( crate ) struct Lemmy {
jwt_token : Sensitive < String > ,
instance : String ,
2024-05-07 20:10:21 +00:00
communities : HashMap < String , CommunityId > ,
2023-12-17 17:03:11 +00:00
}
2024-01-08 20:06:52 +00:00
#[ derive(Debug, Clone) ]
pub ( crate ) struct PostInfoInner {
pub ( crate ) title : String ,
pub ( crate ) url : Url ,
}
2024-05-07 20:10:21 +00:00
#[ derive(Debug, Copy, Clone) ]
pub ( crate ) enum PartInfo {
NoParts ,
Part ( u8 ) ,
}
2024-01-08 20:06:52 +00:00
2024-05-07 20:10:21 +00:00
impl PartInfo {
pub ( crate ) fn as_u8 ( & self ) -> u8 {
match self {
PartInfo ::Part ( number ) = > * number ,
PartInfo ::NoParts = > 0 ,
}
}
pub ( crate ) fn as_string ( & self ) -> String {
self . as_u8 ( ) . to_string ( )
}
2024-01-08 20:06:52 +00:00
}
2024-05-07 20:10:21 +00:00
impl PartialEq for PartInfo {
fn eq ( & self , other : & Self ) -> bool {
let self_numeric = self . as_u8 ( ) ;
let other_numeric = other . as_u8 ( ) ;
self_numeric = = other_numeric
}
}
2023-12-17 17:03:11 +00:00
2024-05-07 20:10:21 +00:00
impl PartialOrd for PartInfo {
fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
if self . gt ( other ) {
Some ( Ordering ::Greater )
} else if self . eq ( other ) {
Some ( Ordering ::Equal )
} else {
Some ( Ordering ::Less )
2023-12-29 23:23:01 +00:00
}
2024-05-07 20:10:21 +00:00
}
fn lt ( & self , other : & Self ) -> bool {
let self_numeric = self . as_u8 ( ) ;
let other_numeric = other . as_u8 ( ) ;
self_numeric < other_numeric
}
fn le ( & self , other : & Self ) -> bool {
! self . gt ( other )
}
fn gt ( & self , other : & Self ) -> bool {
let self_numeric = self . as_u8 ( ) ;
let other_numeric = other . as_u8 ( ) ;
self_numeric > other_numeric
}
fn ge ( & self , other : & Self ) -> bool {
! self . lt ( other )
}
}
#[ derive(Debug, Clone, Copy) ]
pub ( crate ) enum PostType {
Chapter ,
Volume
}
#[ derive(Debug, Clone) ]
pub ( crate ) struct PostInfo {
pub ( crate ) part : Option < PartInfo > ,
pub ( crate ) lemmy_info : PostInfoInner ,
pub ( crate ) description : Option < String > ,
pub ( crate ) post_type : Option < PostType >
}
2023-12-17 17:03:11 +00:00
2024-05-07 20:10:21 +00:00
impl PostInfo {
pub ( crate ) fn get_info ( & self ) -> PostInfoInner {
self . lemmy_info . clone ( )
}
pub ( crate ) fn get_description ( & self ) -> Option < String > {
self . description . clone ( )
}
pub ( crate ) fn get_part_info ( & self ) -> Option < PartInfo > {
self . part
}
pub ( crate ) fn get_post_config ( & self , series : & SeriesConfig ) -> PostConfig {
match self . post_type {
Some ( post_type ) = > {
match post_type {
PostType ::Chapter = > series . prepub_community . clone ( ) ,
PostType ::Volume = > series . volume_community . clone ( ) ,
2023-12-29 23:23:01 +00:00
}
2023-12-17 17:03:11 +00:00
}
2024-05-07 20:10:21 +00:00
None = > series . prepub_community . clone ( ) ,
2023-12-30 00:27:11 +00:00
}
2024-05-07 20:10:21 +00:00
}
pub ( crate ) fn get_post_data ( & self , series : & SeriesConfig , lemmy : & Lemmy ) -> CreatePost {
let post_config = self . get_post_config ( series ) ;
let post_body = match & post_config . post_body {
PostBody ::None = > None ,
PostBody ::Description = > self . get_description ( ) ,
PostBody ::Custom ( text ) = > Some ( text . clone ( ) ) ,
} ;
let community_id : CommunityId = lemmy . get_community_id ( & post_config . name ) ;
CreatePost {
name : self . get_info ( ) . title . clone ( ) ,
community_id ,
url : Some ( self . get_info ( ) . url ) ,
body : post_body ,
honeypot : None ,
nsfw : None ,
language_id : Some ( LanguageId ( 37 ) ) , // TODO get this id once every few hours per API request, the ordering of IDs suggests that the EN Id might change in the future
}
}
}
impl PartialEq for PostInfo {
fn eq ( & self , other : & Self ) -> bool {
self . part . eq ( & other . part )
}
}
impl PartialOrd for PostInfo {
fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
if self . gt ( other ) {
Some ( Ordering ::Greater )
} else if self . eq ( other ) {
Some ( Ordering ::Equal )
} else {
Some ( Ordering ::Less )
2023-12-29 23:23:01 +00:00
}
2023-12-17 17:03:11 +00:00
}
2024-05-07 20:10:21 +00:00
fn lt ( & self , other : & Self ) -> bool {
self . part < other . part
}
fn le ( & self , other : & Self ) -> bool {
! self . gt ( other )
}
fn gt ( & self , other : & Self ) -> bool {
self . part > other . part
}
fn ge ( & self , other : & Self ) -> bool {
! self . lt ( other )
}
2023-12-17 17:03:11 +00:00
}
impl Lemmy {
2024-05-07 20:10:21 +00:00
pub ( crate ) fn get_community_id ( & self , name : & str ) -> CommunityId {
* self . communities . get ( name ) . expect ( " Given community is invalid " )
}
pub ( crate ) async fn new ( config : & RwLock < Config > ) -> Result < Self , ( ) > {
let read_config = config . read ( ) . expect ( " Read Lock Failed " ) . clone ( ) ;
let login_params = Login {
username_or_email : read_config . get_username ( ) ,
password : read_config . get_password ( ) ,
totp_2fa_token : None ,
} ;
let response = match HTTP_CLIENT
. post ( read_config . instance . to_owned ( ) + " /api/v3/user/login " )
. json ( & login_params )
. send ( )
. await
{
Ok ( data ) = > data ,
Err ( e ) = > {
let err_msg = format! ( " {e} " ) ;
error! ( err_msg ) ;
return Err ( ( ) ) ;
}
} ;
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 : read_config . instance . to_owned ( ) ,
communities : HashMap ::new ( ) ,
} ) ,
None = > {
let err_msg = " Login did not return JWT token. Are the credentials valid? " . to_owned ( ) ;
error! ( err_msg ) ;
Err ( ( ) )
}
}
}
status = > {
let err_msg = format! ( " Unexpected HTTP Status ' {} ' during Login " , status ) ;
error! ( err_msg ) ;
Err ( ( ) )
}
}
}
pub ( crate ) async fn logout ( & self ) {
let _ = self . post_data_json ( " /api/v3/user/logout " , & " " ) . await ;
}
2023-12-29 23:23:01 +00:00
pub ( crate ) async fn post ( & self , post : CreatePost ) -> Result < PostId , ( ) > {
2024-05-06 19:16:53 +00:00
let response : String = self . post_data_json ( " /api/v3/post " , & post ) . await ? ;
2024-05-06 19:27:47 +00:00
let json_data : PostView = self . parse_json_map ( & response ) . await ? ;
2023-12-17 17:03:11 +00:00
Ok ( json_data . post . id )
}
2023-12-29 23:23:01 +00:00
async fn feature ( & self , params : FeaturePost ) -> Result < PostView , ( ) > {
2024-05-06 19:16:53 +00:00
let response : String = self . post_data_json ( " /api/v3/post/feature " , & params ) . await ? ;
2024-05-06 19:27:47 +00:00
let json_data : PostView = self . parse_json_map ( & response ) . await ? ;
2023-12-17 17:03:11 +00:00
Ok ( json_data )
}
2023-12-29 23:23:01 +00:00
pub ( crate ) async fn unpin ( & self , post_id : PostId , location : PostFeatureType ) -> Result < PostView , ( ) > {
2023-12-19 21:08:23 +00:00
let pin_params = FeaturePost {
2023-12-17 17:03:11 +00:00
post_id ,
featured : false ,
feature_type : location ,
} ;
self . feature ( pin_params ) . await
}
2023-12-29 23:23:01 +00:00
pub ( crate ) async fn pin ( & self , post_id : PostId , location : PostFeatureType ) -> Result < PostView , ( ) > {
2023-12-19 21:08:23 +00:00
let pin_params = FeaturePost {
2023-12-17 17:03:11 +00:00
post_id ,
featured : true ,
feature_type : location ,
} ;
self . feature ( pin_params ) . await
}
2023-12-29 23:23:01 +00:00
pub ( crate ) async fn get_community_pinned ( & self , community : CommunityId ) -> Result < Vec < PostView > , ( ) > {
2023-12-17 17:03:11 +00:00
let list_params = GetPosts {
community_id : Some ( community ) ,
type_ : Some ( ListingType ::Local ) ,
.. Default ::default ( )
} ;
2024-05-06 19:16:53 +00:00
let response : String = self . get_data_query ( " /api/v3/post/list " , & list_params ) . await ? ;
2024-05-06 18:53:50 +00:00
let json_data : GetPostsResponse = self . parse_json ( & response ) . await ? ;
2023-12-17 17:03:11 +00:00
2023-12-30 00:27:11 +00:00
Ok ( json_data
. posts
. iter ( )
. filter ( | post | post . post . featured_community )
2023-12-17 17:03:11 +00:00
. cloned ( )
2023-12-30 00:27:11 +00:00
. collect ( ) )
2023-12-17 17:03:11 +00:00
}
2023-12-29 23:23:01 +00:00
pub ( crate ) async fn get_local_pinned ( & self ) -> Result < Vec < PostView > , ( ) > {
2023-12-17 17:03:11 +00:00
let list_params = GetPosts {
type_ : Some ( ListingType ::Local ) ,
.. Default ::default ( )
} ;
2024-05-06 19:16:53 +00:00
let response : String = self . get_data_query ( " /api/v3/post/list " , & list_params ) . await ? ;
2024-05-06 18:53:50 +00:00
let json_data : GetPostsResponse = self . parse_json ( & response ) . await ? ;
2023-12-17 17:03:11 +00:00
2023-12-30 00:27:11 +00:00
Ok ( json_data
. posts
. iter ( )
. filter ( | post | post . post . featured_local )
2023-12-17 17:03:11 +00:00
. cloned ( )
2023-12-30 00:27:11 +00:00
. collect ( ) )
2023-12-17 17:03:11 +00:00
}
2024-05-07 20:10:21 +00:00
pub ( crate ) async fn get_communities ( & mut self ) {
2023-12-17 17:03:11 +00:00
let list_params = ListCommunities {
type_ : Some ( ListingType ::Local ) ,
.. Default ::default ( )
} ;
2024-05-07 20:10:21 +00:00
let response : String = match self . get_data_query ( " /api/v3/community/list " , & list_params ) . await {
Ok ( data ) = > data ,
Err ( _ ) = > {
error! ( " Unable to extract data from request " ) ;
return ;
}
} ;
let json_data : ListCommunitiesResponse = match self . parse_json ::< ListCommunitiesResponse > ( & response ) . await {
Ok ( data ) = > data ,
Err ( _ ) = > {
error! ( " Unable to parse data from json " ) ;
return ;
} ,
} ;
2024-05-06 18:53:50 +00:00
let mut communities : HashMap < String , CommunityId > = HashMap ::new ( ) ;
for community_view in json_data . communities {
let community = community_view . community ;
communities . insert ( community . name , community . id ) ;
}
2024-05-07 20:10:21 +00:00
self . communities = communities ;
2024-05-06 18:53:50 +00:00
}
2024-05-06 19:16:53 +00:00
async fn post_data_json < T : Serialize > ( & self , route : & str , json : & T ) -> Result < String , ( ) > {
2024-05-06 18:53:50 +00:00
let res = HTTP_CLIENT
2024-05-06 19:16:53 +00:00
. post ( format! ( " {} {route} " , & self . instance ) )
2023-12-17 17:03:11 +00:00
. bearer_auth ( & self . jwt_token . to_string ( ) )
2024-05-06 18:53:50 +00:00
. json ( & json )
2023-12-17 17:03:11 +00:00
. send ( )
2024-05-06 18:53:50 +00:00
. await ;
self . extract_data ( res ) . await
}
2024-05-06 19:16:53 +00:00
async fn get_data_query < T : Serialize > ( & self , route : & str , param : & T ) -> Result < String , ( ) > {
2024-05-06 18:53:50 +00:00
let res = HTTP_CLIENT
2024-05-06 19:16:53 +00:00
. get ( format! ( " {} {route} " , & self . instance ) )
2024-05-06 18:53:50 +00:00
. bearer_auth ( & self . jwt_token . to_string ( ) )
2024-05-06 19:16:53 +00:00
. query ( & param )
2024-05-06 18:53:50 +00:00
. send ( )
. await ;
self . extract_data ( res ) . await
}
async fn extract_data ( & self , response : Result < reqwest ::Response , reqwest ::Error > ) -> Result < String , ( ) > {
match response {
2023-12-30 00:27:11 +00:00
Ok ( data ) = > match data . text ( ) . await {
2024-05-06 18:53:50 +00:00
Ok ( data ) = > Ok ( data ) ,
2023-12-30 00:27:11 +00:00
Err ( e ) = > {
let err_msg = format! ( " {e} " ) ;
2024-05-06 18:51:31 +00:00
error! ( err_msg ) ;
Err ( ( ) )
2023-12-17 21:29:18 +00:00
}
} ,
2023-12-29 23:23:01 +00:00
Err ( e ) = > {
let err_msg = format! ( " {e} " ) ;
2024-05-06 18:51:31 +00:00
error! ( err_msg ) ;
Err ( ( ) )
2023-12-29 23:23:01 +00:00
}
2024-05-06 18:53:50 +00:00
}
}
async fn parse_json < ' a , T : Deserialize < ' a > > ( & self , response : & ' a str ) -> Result < T , ( ) > {
2024-05-06 19:27:47 +00:00
match serde_json ::from_str ::< T > ( response ) {
Ok ( data ) = > Ok ( data ) ,
Err ( e ) = > {
2024-05-06 20:21:04 +00:00
let err_msg = format! ( " {e} while parsing JSON " ) ;
2024-05-06 19:27:47 +00:00
error! ( err_msg ) ;
Err ( ( ) )
}
}
}
async fn parse_json_map < ' a , T : Deserialize < ' a > > ( & self , response : & ' a str ) -> Result < T , ( ) > {
2024-05-06 18:53:50 +00:00
match serde_json ::from_str ::< HashMap < & str , T > > ( response ) {
Ok ( mut data ) = > Ok ( data . remove ( " post_view " ) . expect ( " Element should be present " ) ) ,
2023-12-29 23:23:01 +00:00
Err ( e ) = > {
2024-05-06 20:21:04 +00:00
let err_msg = format! ( " {e} while parsing JSON HashMap " ) ;
2024-05-06 18:51:31 +00:00
error! ( err_msg ) ;
Err ( ( ) )
2023-12-29 23:23:01 +00:00
}
2023-12-17 17:03:11 +00:00
}
}
}