2023-06-18 22:26:50 +00:00
use chrono ::Utc ;
2023-06-19 17:21:28 +00:00
use config ::{ Config , PrevPost , Secrets , LemmyCommunities } ;
2023-06-18 22:26:50 +00:00
use lemmy_api_common ::{
person ::{ Login , LoginResponse } ,
post ::{ CreatePost , GetPosts , GetPostsResponse } ,
2023-06-19 17:21:28 +00:00
sensitive ::Sensitive , community ::{ ListCommunities , ListCommunitiesResponse } ,
2023-06-18 22:26:50 +00:00
} ;
use lemmy_db_schema ::{
newtypes ::{ CommunityId , LanguageId } ,
ListingType , SortType ,
} ;
use once_cell ::sync ::Lazy ;
use reqwest ::{ blocking ::Client , StatusCode } ;
use std ::{ thread ::sleep , time } ;
use url ::Url ;
use crate ::config ::FeedData ;
mod config ;
pub static CLIENT : Lazy < Client > = Lazy ::new ( | | {
let client = Client ::builder ( )
. timeout ( time ::Duration ::from_secs ( 30 ) )
. connect_timeout ( time ::Duration ::from_secs ( 30 ) )
. build ( )
. expect ( " build client " ) ;
client
} ) ;
struct Bot {
secrets : Secrets ,
config : Config ,
post_history : Vec < PrevPost > ,
2023-06-19 17:21:28 +00:00
community_ids : CommunitiesVector ,
2023-06-18 22:26:50 +00:00
auth : Sensitive < String > ,
}
impl Bot {
pub ( crate ) fn new ( ) -> Bot {
Bot {
secrets : Secrets ::load ( ) ,
config : Config ::load ( ) ,
post_history : PrevPost ::load ( ) ,
2023-06-19 17:21:28 +00:00
community_ids : CommunitiesVector ::new ( ) ,
2023-06-18 22:26:50 +00:00
auth : Sensitive ::new ( " " . to_string ( ) ) ,
}
}
pub ( crate ) fn login ( & mut self ) {
let login_params = Login {
username_or_email : self . secrets . lemmy . get_username ( ) ,
password : self . secrets . lemmy . get_password ( ) ,
} ;
let res = CLIENT
. post ( " https://lemmy.neshweb.net/api/v3/user/login " )
. json ( & login_params )
. send ( )
. unwrap ( ) ;
if res . status ( ) = = StatusCode ::OK {
let data : & LoginResponse = & res . json ( ) . unwrap ( ) ;
let jwt = data . jwt . clone ( ) . expect ( " JWT Token could not be acquired " ) ;
self . auth = jwt ;
} else {
println! ( " Error Code: {:?} " , res . status ( ) ) ;
panic! ( " JWT Token could not be acquired " ) ;
}
}
pub ( crate ) fn post ( & mut self , post_data : CreatePost ) {
let res = CLIENT
. post ( " https://lemmy.neshweb.net/api/v3/post " )
. json ( & post_data )
. send ( )
. unwrap ( ) ;
}
}
fn list_posts ( auth : & Sensitive < String > ) -> GetPostsResponse {
let params = GetPosts {
type_ : Some ( ListingType ::Local ) ,
sort : Some ( SortType ::New ) ,
auth : Some ( auth . clone ( ) ) ,
.. Default ::default ( )
} ;
let res = CLIENT
. get ( " https://lemmy.neshweb.net/api/v3/post/list " )
. query ( & params )
. send ( )
. unwrap ( )
. text ( )
. unwrap ( ) ;
return serde_json ::from_str ( & res ) . unwrap ( ) ;
}
2023-06-19 17:21:28 +00:00
struct CommunitiesVector {
ids : Vec < ( CommunityId , String ) > ,
}
impl CommunitiesVector {
fn new ( ) -> CommunitiesVector {
CommunitiesVector { ids : vec ! [ ] }
}
fn load ( & mut self , auth : & Sensitive < String > ) {
let params = ListCommunities {
auth : Some ( auth . clone ( ) ) ,
.. Default ::default ( )
} ;
let res = CLIENT
. get ( " https://lemmy.neshweb.net/api/v3/community/list " )
. query ( & params )
. send ( )
. unwrap ( )
. text ( )
. unwrap ( ) ;
let site_data : ListCommunitiesResponse = serde_json ::from_str ( & res ) . unwrap ( ) ;
let mut ids = [ ] . to_vec ( ) ;
site_data . communities . iter ( ) . for_each ( | entry | {
let new_id = ( entry . community . id , entry . community . name . clone ( ) ) ;
ids . push ( new_id ) ;
} ) ;
self . ids = ids ;
}
fn find ( & self , name : & LemmyCommunities ) -> CommunityId {
let mut ret_id = CommunityId ( 0 ) ;
self . ids . iter ( ) . for_each ( | id | {
let id_name = & id . 1 ;
if & name . to_string ( ) = = id_name {
ret_id = id . 0 ;
}
} ) ;
return ret_id ;
}
}
2023-06-18 22:26:50 +00:00
fn main ( ) {
// Get all needed auth tokens at the start
let mut old = Utc ::now ( ) . time ( ) ;
let mut this = Bot ::new ( ) ;
println! ( " {} " , this . secrets . lemmy . username ) ;
this . login ( ) ;
2023-06-19 17:21:28 +00:00
this . community_ids . load ( & this . auth ) ;
2023-06-18 22:26:50 +00:00
// Create empty eTag list
println! ( " TODO: Etag list " ) ;
// Enter a loop (not for debugging)
loop {
let start = Utc ::now ( ) ;
print! ( " \x1B [2J \x1B [1;1H " ) ;
2023-06-19 17:21:28 +00:00
println! (
" Started loop at {} {} " ,
start . format ( " %H:%M:%S " ) ,
start . timezone ( )
) ;
2023-06-18 22:26:50 +00:00
if start . time ( ) - old > chrono ::Duration ::seconds ( 6 ) {
old = start . time ( ) ;
this . config = Config ::load ( ) ;
2023-06-19 17:21:28 +00:00
this . community_ids . load ( & this . auth ) ;
2023-06-18 22:26:50 +00:00
}
// Start the polling process
// Get all feed URLs (use cache)
let mut post_queue : Vec < CreatePost > = vec! [ ] ;
this . config . feeds . iter ( ) . for_each ( | feed | {
let res = CLIENT
. get ( feed . feed_url . clone ( ) )
. send ( )
. unwrap ( )
. text ( )
. unwrap ( ) ;
let data : FeedData = serde_json ::from_str ( & res ) . unwrap ( ) ;
let mut prev_post_idx : Option < usize > = None ;
let mut do_post = true ;
this . post_history
. iter ( )
. enumerate ( )
. for_each ( | ( idx , post ) | {
if & post . last_post_url = = & data . items [ 0 ] . url {
do_post = false ;
} else if & post . title = = & data . title {
prev_post_idx = Some ( idx ) ;
}
} ) ;
if do_post {
let item = & data . items [ 0 ] ;
let new_post = CreatePost {
name : item . title . clone ( ) ,
2023-06-19 17:21:28 +00:00
community_id : this . community_ids . find ( & feed . communities . chapter ) ,
2023-06-18 22:26:50 +00:00
url : Some ( Url ::parse ( & item . url ) . unwrap ( ) ) ,
body : Some (
" [Reddit](https://reddit.com) \n \n [Discord](https://discord.com) " . into ( ) ,
) ,
honeypot : None ,
nsfw : Some ( false ) ,
2023-06-19 17:21:28 +00:00
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
2023-06-18 22:26:50 +00:00
auth : this . auth . clone ( ) ,
} ;
post_queue . push ( new_post ) ;
match prev_post_idx {
Some ( idx ) = > {
this . post_history [ idx ] . title = data . title ;
this . post_history [ idx ] . last_post_url = item . url . clone ( ) ;
}
None = > this . post_history . push ( PrevPost {
title : data . title ,
last_post_url : item . url . clone ( ) ,
} ) ,
}
}
sleep ( time ::Duration ::from_millis ( 100 ) ) ; // Should prevent dos-ing J-Novel servers
} ) ;
PrevPost ::save ( & this . post_history ) ;
post_queue . iter ( ) . for_each ( | post | {
2023-06-19 17:21:28 +00:00
println! ( " Posting: {} " , post . name ) ;
2023-06-18 22:26:50 +00:00
this . post ( post . clone ( ) ) ;
} ) ;
while Utc ::now ( ) . time ( ) - start . time ( ) < chrono ::Duration ::seconds ( 60 ) {
sleep ( time ::Duration ::from_secs ( 10 ) ) ;
}
}
}