2024-05-07 22:10:21 +02:00
use std ::cmp ::Ordering ;
2024-07-21 02:27:16 +02:00
use crate ::config ::{ PostConfig , SeriesConfig } ;
2024-05-06 20:51:31 +02:00
use crate ::{ HTTP_CLIENT } ;
2023-12-19 22:08:23 +01:00
use lemmy_api_common ::community ::{ ListCommunities , ListCommunitiesResponse } ;
use lemmy_api_common ::lemmy_db_views ::structs ::PostView ;
2023-12-17 18:03:11 +01:00
use lemmy_api_common ::person ::{ Login , LoginResponse } ;
2023-12-19 22:08:23 +01:00
use lemmy_api_common ::post ::{ CreatePost , FeaturePost , GetPosts , GetPostsResponse } ;
2024-05-07 22:10:21 +02:00
use lemmy_db_schema ::newtypes ::{ CommunityId , LanguageId , PostId } ;
2023-12-19 22:08:23 +01:00
use lemmy_db_schema ::{ ListingType , PostFeatureType } ;
2023-12-17 18:03:11 +01:00
use reqwest ::StatusCode ;
2023-12-30 01:27:11 +01:00
use std ::collections ::HashMap ;
2024-07-15 22:15:19 +02:00
use lemmy_db_schema ::sensitive ::SensitiveString ;
2024-05-06 20:53:50 +02:00
use serde ::{ Deserialize , Serialize } ;
2024-07-21 02:27:16 +02:00
use crate ::logging ::Logging ;
use crate ::settings ::{ BotSettings , PostBody } ;
2023-12-17 18:03:11 +01:00
pub ( crate ) struct Lemmy {
2024-07-15 22:15:19 +02:00
jwt_token : SensitiveString ,
2023-12-17 18:03:11 +01:00
instance : String ,
2024-05-07 22:10:21 +02:00
communities : HashMap < String , CommunityId > ,
2023-12-17 18:03:11 +01:00
}
2024-01-08 21:06:52 +01:00
#[ derive(Debug, Clone) ]
pub ( crate ) struct PostInfoInner {
pub ( crate ) title : String ,
2024-07-15 22:15:19 +02:00
pub ( crate ) url : String ,
pub ( crate ) thumbnail : Option < String >
2024-01-08 21:06:52 +01:00
}
2024-05-07 22:10:21 +02:00
#[ derive(Debug, Copy, Clone) ]
pub ( crate ) enum PartInfo {
NoParts ,
Part ( u8 ) ,
}
2024-01-08 21:06:52 +01:00
2024-05-07 22:10:21 +02: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 21:06:52 +01:00
}
2024-05-07 22:10:21 +02: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 18:03:11 +01:00
2024-05-07 22:10:21 +02: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-30 00:23:01 +01:00
}
2024-05-07 22:10:21 +02: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 )
}
}
2024-07-21 02:27:16 +02:00
#[ derive(Debug, Clone, Copy, PartialEq, Eq, Hash) ]
2024-05-07 22:10:21 +02:00
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 18:03:11 +01:00
2024-05-07 22:10:21 +02: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
}
2024-05-07 23:49:55 +02:00
2024-05-07 22:10:21 +02:00
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-30 00:23:01 +01:00
}
2023-12-17 18:03:11 +01:00
}
2024-05-07 22:10:21 +02:00
None = > series . prepub_community . clone ( ) ,
2023-12-30 01:27:11 +01:00
}
2024-05-07 22:10:21 +02:00
}
2024-05-07 23:49:55 +02:00
2024-05-07 22:10:21 +02:00
pub ( crate ) fn get_post_data ( & self , series : & SeriesConfig , lemmy : & Lemmy ) -> CreatePost {
let post_config = self . get_post_config ( series ) ;
2024-05-07 23:49:55 +02:00
2024-05-07 22:10:21 +02:00
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 ) ,
2024-07-15 22:15:19 +02:00
custom_thumbnail : self . get_info ( ) . thumbnail ,
2024-05-07 22:10:21 +02:00
body : post_body ,
2024-07-15 22:15:19 +02:00
alt_text : None ,
2024-05-07 22:10:21 +02:00
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
2024-05-07 23:49:55 +02:00
}
2024-05-07 22:10:21 +02:00
}
}
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-30 00:23:01 +01:00
}
2023-12-17 18:03:11 +01:00
}
2024-05-07 22:10:21 +02: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 18:03:11 +01:00
}
impl Lemmy {
2024-05-07 22:10:21 +02:00
pub ( crate ) fn get_community_id ( & self , name : & str ) -> CommunityId {
2024-07-21 02:27:16 +02:00
* self . communities . get ( name ) . expect ( format! ( " Community ' {name} ' is invalid " ) . as_str ( ) )
2024-05-07 22:10:21 +02:00
}
2024-07-21 02:27:16 +02:00
pub ( crate ) fn new ( config : & BotSettings ) -> Result < Self , ( ) > {
2024-05-07 22:10:21 +02:00
let login_params = Login {
2024-07-21 02:27:16 +02:00
username_or_email : config . username ( ) ,
password : config . password ( ) ,
2024-05-07 22:10:21 +02:00
totp_2fa_token : None ,
} ;
let response = match HTTP_CLIENT
2024-07-21 02:27:16 +02:00
. post ( config . instance ( ) . to_owned ( ) + " /api/v3/user/login " )
2024-05-07 22:10:21 +02:00
. json ( & login_params )
. send ( )
{
Ok ( data ) = > data ,
Err ( e ) = > {
let err_msg = format! ( " {e} " ) ;
2024-07-21 02:27:16 +02:00
Logging ::error ( err_msg . as_str ( ) ) ;
2024-05-07 22:10:21 +02:00
return Err ( ( ) ) ;
}
} ;
match response . status ( ) {
StatusCode ::OK = > {
let data : LoginResponse = response
. json ( )
. expect ( " Successful Login Request should return JSON " ) ;
match data . jwt {
Some ( token ) = > Ok ( Lemmy {
jwt_token : token . clone ( ) ,
2024-07-21 02:27:16 +02:00
instance : config . instance ( ) . to_owned ( ) ,
2024-05-07 22:10:21 +02:00
communities : HashMap ::new ( ) ,
} ) ,
None = > {
2024-07-21 02:27:16 +02:00
Logging ::error ( " Login did not return JWT token. Are the credentials valid? " ) ;
2024-05-07 22:10:21 +02:00
Err ( ( ) )
}
}
}
status = > {
let err_msg = format! ( " Unexpected HTTP Status ' {} ' during Login " , status ) ;
2024-07-21 02:27:16 +02:00
Logging ::error ( err_msg . as_str ( ) ) ;
2024-05-07 22:10:21 +02:00
Err ( ( ) )
}
}
}
2024-07-21 02:27:16 +02:00
pub fn logout ( & self ) {
let _ = self . post_data_json ( " /api/v3/user/logout " , & " " ) ;
2024-05-07 22:10:21 +02:00
}
2024-07-21 02:27:16 +02:00
pub fn login ( & self ) {
}
2024-05-07 22:10:21 +02:00
2024-07-21 02:27:16 +02:00
pub fn post ( & self , post : CreatePost ) -> Option < PostId > {
let response : String = match self . post_data_json ( " /api/v3/post " , & post ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2024-07-21 02:27:16 +02:00
let json_data : PostView = match self . parse_json_map ( & response ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2023-12-17 18:03:11 +01:00
2024-05-07 23:49:55 +02:00
Some ( json_data . post . id )
2023-12-17 18:03:11 +01:00
}
2024-07-21 02:27:16 +02:00
fn feature ( & self , params : FeaturePost ) -> Option < PostView > {
let response : String = match self . post_data_json ( " /api/v3/post/feature " , & params ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2024-07-21 02:27:16 +02:00
let json_data : PostView = match self . parse_json_map ( & response ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2023-12-17 18:03:11 +01:00
2024-05-07 23:49:55 +02:00
Some ( json_data )
2023-12-17 18:03:11 +01:00
}
2024-07-21 02:27:16 +02:00
pub ( crate ) fn unpin ( & self , post_id : PostId , location : PostFeatureType ) -> Option < PostView > {
2023-12-19 22:08:23 +01:00
let pin_params = FeaturePost {
2023-12-17 18:03:11 +01:00
post_id ,
featured : false ,
feature_type : location ,
} ;
2024-07-21 02:27:16 +02:00
self . feature ( pin_params )
2023-12-17 18:03:11 +01:00
}
2024-07-21 02:27:16 +02:00
pub ( crate ) fn pin ( & self , post_id : PostId , location : PostFeatureType ) -> Option < PostView > {
2023-12-19 22:08:23 +01:00
let pin_params = FeaturePost {
2023-12-17 18:03:11 +01:00
post_id ,
featured : true ,
feature_type : location ,
} ;
2024-07-21 02:27:16 +02:00
self . feature ( pin_params )
2023-12-17 18:03:11 +01:00
}
2024-07-21 02:27:16 +02:00
pub ( crate ) fn get_community_pinned ( & self , community : CommunityId ) -> Option < Vec < PostView > > {
2023-12-17 18:03:11 +01:00
let list_params = GetPosts {
community_id : Some ( community ) ,
type_ : Some ( ListingType ::Local ) ,
.. Default ::default ( )
} ;
2024-07-21 02:27:16 +02:00
let response : String = match self . get_data_query ( " /api/v3/post/list " , & list_params ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2024-07-21 02:27:16 +02:00
let json_data : GetPostsResponse = match self . parse_json ( & response ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2023-12-17 18:03:11 +01:00
2024-05-07 23:49:55 +02:00
Some ( json_data
2023-12-30 01:27:11 +01:00
. posts
. iter ( )
. filter ( | post | post . post . featured_community )
2023-12-17 18:03:11 +01:00
. cloned ( )
2023-12-30 01:27:11 +01:00
. collect ( ) )
2023-12-17 18:03:11 +01:00
}
2024-07-21 02:27:16 +02:00
pub ( crate ) fn get_local_pinned ( & self ) -> Option < Vec < PostView > > {
2023-12-17 18:03:11 +01:00
let list_params = GetPosts {
type_ : Some ( ListingType ::Local ) ,
.. Default ::default ( )
} ;
2024-07-21 02:27:16 +02:00
let response : String = match self . get_data_query ( " /api/v3/post/list " , & list_params ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2024-07-21 02:27:16 +02:00
let json_data : GetPostsResponse = match self . parse_json ( & response ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return None ,
} ;
2023-12-17 18:03:11 +01:00
2024-05-07 23:49:55 +02:00
Some ( json_data
2023-12-30 01:27:11 +01:00
. posts
. iter ( )
. filter ( | post | post . post . featured_local )
2023-12-17 18:03:11 +01:00
. cloned ( )
2023-12-30 01:27:11 +01:00
. collect ( ) )
2023-12-17 18:03:11 +01:00
}
2024-07-21 02:27:16 +02:00
pub ( crate ) fn get_communities ( & mut self ) {
2023-12-17 18:03:11 +01:00
let list_params = ListCommunities {
type_ : Some ( ListingType ::Local ) ,
.. Default ::default ( )
} ;
2024-07-21 02:27:16 +02:00
let response : String = match self . get_data_query ( " /api/v3/community/list " , & list_params ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return ,
2024-05-07 22:10:21 +02:00
} ;
2024-07-21 02:27:16 +02:00
let json_data : ListCommunitiesResponse = match self . parse_json ::< ListCommunitiesResponse > ( & response ) {
2024-05-07 23:49:55 +02:00
Some ( data ) = > data ,
None = > return ,
2024-05-07 22:10:21 +02:00
} ;
2024-05-06 20:53:50 +02: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 22:10:21 +02:00
self . communities = communities ;
2024-05-06 20:53:50 +02:00
}
2024-07-21 02:27:16 +02:00
fn post_data_json < T : Serialize > ( & self , route : & str , json : & T ) -> Option < String > {
2024-05-06 20:53:50 +02:00
let res = HTTP_CLIENT
2024-05-06 21:16:53 +02:00
. post ( format! ( " {} {route} " , & self . instance ) )
2023-12-17 18:03:11 +01:00
. bearer_auth ( & self . jwt_token . to_string ( ) )
2024-05-06 20:53:50 +02:00
. json ( & json )
2024-07-21 02:27:16 +02:00
. send ( ) ;
self . extract_data ( res )
2024-05-06 20:53:50 +02:00
}
2024-07-21 02:27:16 +02:00
fn get_data_query < T : Serialize > ( & self , route : & str , param : & T ) -> Option < String > {
2024-05-06 20:53:50 +02:00
let res = HTTP_CLIENT
2024-05-06 21:16:53 +02:00
. get ( format! ( " {} {route} " , & self . instance ) )
2024-05-06 20:53:50 +02:00
. bearer_auth ( & self . jwt_token . to_string ( ) )
2024-05-06 21:16:53 +02:00
. query ( & param )
2024-07-21 02:27:16 +02:00
. send ( ) ;
self . extract_data ( res )
2024-05-06 20:53:50 +02:00
}
2024-07-21 02:27:16 +02:00
fn extract_data ( & self , response : Result < reqwest ::blocking ::Response , reqwest ::Error > ) -> Option < String > {
2024-05-06 20:53:50 +02:00
match response {
2024-05-07 23:49:55 +02:00
Ok ( data ) = > {
2024-07-21 02:27:16 +02:00
let msg = format! ( " Status Code: ' {} ' " , data . status ( ) . clone ( ) ) ;
Logging ::debug ( msg . as_str ( ) ) ;
2024-05-07 23:49:55 +02:00
if data . status ( ) . is_success ( ) {
2024-07-21 02:27:16 +02:00
match data . text ( ) {
2024-05-07 23:49:55 +02:00
Ok ( data ) = > Some ( data ) ,
Err ( e ) = > {
let err_msg = format! ( " {e} " ) ;
2024-07-21 02:27:16 +02:00
Logging ::error ( err_msg . as_str ( ) ) ;
2024-05-07 23:49:55 +02:00
None
}
}
}
else {
2024-07-21 02:27:16 +02:00
let err_msg = format! ( " HTTP Request failed: {} " , data . text ( ) . unwrap ( ) ) ;
Logging ::error ( err_msg . as_str ( ) ) ;
2024-05-07 23:49:55 +02:00
None
2023-12-17 22:29:18 +01:00
}
} ,
2023-12-30 00:23:01 +01:00
Err ( e ) = > {
let err_msg = format! ( " {e} " ) ;
2024-07-21 02:27:16 +02:00
Logging ::error ( err_msg . as_str ( ) ) ;
2024-05-07 23:49:55 +02:00
None
2023-12-30 00:23:01 +01:00
}
2024-05-06 20:53:50 +02:00
}
}
2024-07-21 02:27:16 +02:00
fn parse_json < ' a , T : Deserialize < ' a > > ( & self , response : & ' a str ) -> Option < T > {
2024-05-06 21:27:47 +02:00
match serde_json ::from_str ::< T > ( response ) {
2024-05-07 23:49:55 +02:00
Ok ( data ) = > Some ( data ) ,
2024-05-06 21:27:47 +02:00
Err ( e ) = > {
2024-05-07 23:49:55 +02:00
let err_msg = format! ( " while parsing JSON: {e} " ) ;
2024-07-21 02:27:16 +02:00
Logging ::error ( err_msg . as_str ( ) ) ;
2024-05-07 23:49:55 +02:00
None
2024-05-06 21:27:47 +02:00
}
}
}
2024-07-21 02:27:16 +02:00
fn parse_json_map < ' a , T : Deserialize < ' a > > ( & self , response : & ' a str ) -> Option < T > {
Logging ::debug ( response ) ;
2024-05-06 20:53:50 +02:00
match serde_json ::from_str ::< HashMap < & str , T > > ( response ) {
2024-05-07 23:49:55 +02:00
Ok ( mut data ) = > Some ( data . remove ( " post_view " ) . expect ( " Element should be present " ) ) ,
2023-12-30 00:23:01 +01:00
Err ( e ) = > {
2024-05-07 23:49:55 +02:00
let err_msg = format! ( " while parsing JSON HashMap: {e} " ) ;
2024-07-21 02:27:16 +02:00
Logging ::error ( err_msg . as_str ( ) ) ;
2024-05-07 23:49:55 +02:00
None
2023-12-30 00:23:01 +01:00
}
2023-12-17 18:03:11 +01:00
}
}
}