2023-09-18 21:01:22 +00:00
use chrono ::{ DateTime , Duration , NaiveDateTime , Utc } ;
2023-08-31 21:36:37 +00:00
use config ::{ CommunitiesVector , Config , LemmyCommunities , PrevPost , Secrets } ;
2023-06-18 22:26:50 +00:00
use lemmy_api_common ::{
2023-08-31 21:36:37 +00:00
lemmy_db_views ::structs ::PostView ,
2023-06-18 22:26:50 +00:00
person ::{ Login , LoginResponse } ,
2023-08-31 21:36:37 +00:00
post ::{ CreatePost , FeaturePost , GetPosts , GetPostsResponse } ,
sensitive ::Sensitive ,
2023-06-18 22:26:50 +00:00
} ;
2023-09-18 21:01:22 +00:00
use lemmy_db_schema ::newtypes ::PostId ;
2023-08-31 21:36:37 +00:00
use lemmy_db_schema ::{ newtypes ::CommunityId , ListingType , PostFeatureType , SortType } ;
2023-06-18 22:26:50 +00:00
use once_cell ::sync ::Lazy ;
2023-08-31 21:36:37 +00:00
use reqwest ::{ Client , StatusCode } ;
use std ::{
collections ::HashMap ,
error ::Error ,
sync ::{
atomic ::{ AtomicBool , Ordering } ,
Arc ,
} ,
2023-09-18 21:01:22 +00:00
vec ,
2023-08-31 21:36:37 +00:00
} ;
2023-09-18 21:01:22 +00:00
use std ::str ::FromStr ;
use std ::time ::UNIX_EPOCH ;
2023-08-31 21:36:37 +00:00
use tokio ::sync ::Mutex ;
2023-08-31 22:40:59 +00:00
use tokio ::time ::sleep ;
2023-06-18 22:26:50 +00:00
mod config ;
2023-09-18 21:01:22 +00:00
mod feeds ;
2023-06-18 22:26:50 +00:00
pub static CLIENT : Lazy < Client > = Lazy ::new ( | | {
let client = Client ::builder ( )
2023-09-18 21:01:22 +00:00
. timeout ( Duration ::seconds ( 30 ) . to_std ( ) . unwrap ( ) )
. connect_timeout ( Duration ::seconds ( 30 ) . to_std ( ) . unwrap ( ) )
2023-06-18 22:26:50 +00:00
. build ( )
. expect ( " build client " ) ;
client
} ) ;
2023-09-18 21:01:22 +00:00
struct PostQueueMetadata {
id : usize ,
series : String ,
part : Option < String > ,
volume : Option < String > ,
}
2023-08-31 21:36:37 +00:00
#[ derive(Clone) ]
2023-06-18 22:26:50 +00:00
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 > ,
2023-09-18 21:01:22 +00:00
login_error : bool ,
start_time : DateTime < Utc > ,
2023-08-31 22:40:59 +00:00
message_queue : Vec < String > ,
2023-09-18 21:01:22 +00:00
error_queue : Vec < String > ,
2023-06-18 22:26:50 +00:00
}
impl Bot {
pub ( crate ) fn new ( ) -> Bot {
Bot {
2023-06-22 20:08:10 +00:00
secrets : Secrets ::init ( ) ,
config : Config ::init ( ) ,
2023-06-18 22:26:50 +00:00
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 ( ) ) ,
2023-09-18 21:01:22 +00:00
login_error : true ,
start_time : Utc ::now ( ) ,
2023-08-31 22:40:59 +00:00
message_queue : vec ! [ ] ,
2023-09-18 21:01:22 +00:00
error_queue : vec ! [ ] ,
2023-06-18 22:26:50 +00:00
}
}
2023-07-30 18:34:24 +00:00
/// Get JWT Token
2023-08-31 21:36:37 +00:00
///
2023-09-18 21:01:22 +00:00
/// * `return` : Returns true if token was successfully retrieved, false otherwise
2023-07-30 19:10:36 +00:00
#[ warn(unused_results) ]
2023-08-31 21:36:37 +00:00
pub ( crate ) async fn login ( & mut self ) -> Result < Sensitive < String > , Box < dyn Error > > {
2023-06-18 22:26:50 +00:00
let login_params = Login {
username_or_email : self . secrets . lemmy . get_username ( ) ,
password : self . secrets . lemmy . get_password ( ) ,
2023-07-09 08:18:51 +00:00
totp_2fa_token : None ,
2023-06-18 22:26:50 +00:00
} ;
2023-08-03 19:38:22 +00:00
let res = CLIENT
2023-06-19 20:10:28 +00:00
. post ( self . config . instance . clone ( ) + " /api/v3/user/login " )
2023-06-18 22:26:50 +00:00
. json ( & login_params )
2023-09-18 21:01:22 +00:00
. send ( )
. await ? ;
2023-06-18 22:26:50 +00:00
2023-09-18 21:01:22 +00:00
return if res . status ( ) = = StatusCode ::OK {
2023-08-31 21:36:37 +00:00
let data : & LoginResponse = & res . json ( ) . await . unwrap ( ) ;
2023-06-18 22:26:50 +00:00
2023-09-18 21:01:22 +00:00
let jwt = match data . jwt . clone ( ) {
Some ( data ) = > data ,
None = > {
self . error_queue . push ( format! ( " Error: Missing JWT Token " ) ) ;
return Err ( Box ::try_from ( format! ( " Error: Missing JWT Token " ) ) . unwrap ( ) ) ;
}
} ;
2023-08-31 21:36:37 +00:00
self . auth = jwt . clone ( ) ;
2023-09-18 21:01:22 +00:00
self . login_error = false ;
Ok ( jwt )
2023-06-18 22:26:50 +00:00
} else {
2023-09-18 21:01:22 +00:00
self . error_queue
. push ( format! ( " Error Code: {:?} " , res . status ( ) ) ) ;
Err ( Box ::new ( res . error_for_status ( ) . unwrap_err ( ) ) )
} ;
2023-06-18 22:26:50 +00:00
}
2023-07-30 18:34:24 +00:00
/// Make Post to Lemmy Instance
2023-08-31 21:36:37 +00:00
///
2023-07-30 18:34:24 +00:00
/// * `post_data` : Object of type [CreatePost] containing post info
2023-09-18 21:01:22 +00:00
/// * `return` : Returns true if Post was successful, false otherwise
2023-07-30 19:10:36 +00:00
#[ warn(unused_results) ]
2023-09-18 21:01:22 +00:00
pub ( crate ) async fn post ( & mut self , post_data : & CreatePost ) -> Result < PostView , Box < dyn Error > > {
2023-08-03 19:38:22 +00:00
let res = CLIENT
2023-06-19 20:10:28 +00:00
. post ( self . config . instance . clone ( ) + " /api/v3/post " )
2023-09-18 21:01:22 +00:00
. json ( post_data )
. send ( )
. await ? ;
2023-07-30 18:34:24 +00:00
2023-09-18 21:01:22 +00:00
// TODO: process res to get info about if post was successfully (mostly if jwt token was valid)
let ret : PostView = serde_json ::from_str ::< HashMap < & str , PostView > > ( res . text ( ) . await ? . as_str ( ) ) ?
2023-08-31 21:36:37 +00:00
. remove ( " post_view " )
. unwrap ( ) ;
2023-08-03 21:34:23 +00:00
return Ok ( ret ) ;
}
#[ warn(unused_results) ]
2023-09-18 21:01:22 +00:00
pub ( crate ) async fn pin_post ( & mut self , new_post_id : & PostId , new_post_community : & CommunityId ) -> Result < ( bool , bool ) , Box < dyn Error > > {
let mut local_pins = true ;
let mut community_pins = true ;
// Unpin Old Posts
2023-08-03 21:34:23 +00:00
2023-09-18 21:01:22 +00:00
// Get Local Posts & Unpin The Other Post
let mut meta_community : CommunityId = CommunityId ( 15 ) ;
self . community_ids . ids . iter ( ) . for_each ( | ( id , name ) | {
if name = = & LemmyCommunities ::metadiscussions . to_string ( ) {
meta_community = id . clone ( ) ;
2023-08-03 21:34:23 +00:00
}
2023-09-18 21:01:22 +00:00
} ) ;
2023-08-03 21:34:23 +00:00
let get_params = GetPosts {
auth : Some ( self . auth . clone ( ) ) ,
.. Default ::default ( )
} ;
2023-09-18 21:01:22 +00:00
local_pins = self
. unpin_old_posts ( get_params , PostFeatureType ::Local , & meta_community )
. await ? ;
2023-08-03 21:34:23 +00:00
2023-09-18 21:01:22 +00:00
// Get Community Posts & Unpin The Other Post
let get_params = GetPosts {
auth : Some ( self . auth . clone ( ) ) ,
community_id : Some ( new_post_community . clone ( ) ) ,
.. Default ::default ( )
} ;
2023-08-31 21:36:37 +00:00
2023-09-18 21:01:22 +00:00
community_pins = self
. unpin_old_posts ( get_params , PostFeatureType ::Community , & meta_community )
. await ? ;
2023-08-03 21:34:23 +00:00
2023-09-18 21:01:22 +00:00
// Pin New Post
2023-08-03 21:34:23 +00:00
let pin_new_community = FeaturePost {
2023-09-18 21:01:22 +00:00
post_id : new_post_id . clone ( ) ,
2023-08-03 21:34:23 +00:00
featured : true ,
feature_type : PostFeatureType ::Community ,
auth : self . auth . clone ( ) ,
} ;
2023-09-18 21:01:22 +00:00
match self . pin ( pin_new_community ) . await {
Ok ( _ ) = > { }
Err ( e ) = > {
self . message_queue
. push ( format! ( " Error Unpinning Post: {:#?} " , e ) ) ;
community_pins = false ;
}
} ;
2023-08-03 21:34:23 +00:00
let pin_new_local = FeaturePost {
2023-09-18 21:01:22 +00:00
post_id : new_post_id . clone ( ) ,
2023-08-03 21:34:23 +00:00
featured : true ,
feature_type : PostFeatureType ::Local ,
auth : self . auth . clone ( ) ,
} ;
2023-08-31 21:36:37 +00:00
match self . pin ( pin_new_local ) . await {
Ok ( _ ) = > { }
2023-09-18 21:01:22 +00:00
Err ( e ) = > {
self . message_queue
. push ( format! ( " Error Unpinning Post: {:#?} " , e ) ) ;
local_pins = false ;
}
2023-08-31 21:36:37 +00:00
} ;
2023-07-30 18:34:24 +00:00
2023-09-18 21:01:22 +00:00
return Ok ( ( community_pins , local_pins ) ) ;
}
#[ warn(unused_results) ]
pub ( crate ) async fn unpin_old_posts (
& mut self ,
get_params : GetPosts ,
pin_scope : PostFeatureType ,
meta_community : & CommunityId ,
) -> Result < bool , Box < dyn Error > > {
let post_list_json = CLIENT
. get ( self . config . instance . clone ( ) + " /api/v3/post/list " )
. query ( & get_params )
. send ( )
. await ?
. text ( )
. await ? ;
let post_list : GetPostsResponse = serde_json ::from_str ( post_list_json . as_str ( ) ) . unwrap ( ) ;
for post_view in post_list . posts {
2023-09-18 21:11:29 +00:00
if & post_view . community . id ! = meta_community & & ( ( pin_scope = = PostFeatureType ::Local & & post_view . post . featured_local ) | | ( pin_scope = = PostFeatureType ::Community & & post_view . post . featured_community ) ) {
2023-09-18 21:01:22 +00:00
let remove_local_pin = FeaturePost {
post_id : post_view . post . id ,
featured : false ,
feature_type : pin_scope ,
auth : self . auth . clone ( ) ,
} ;
match self . pin ( remove_local_pin ) . await {
Ok ( _ ) = > { }
Err ( e ) = > {
self . message_queue
. push ( format! ( " Error Unpinning Post: {:#?} " , e ) ) ;
return Err ( Box ::from ( format! ( " {} " , e ) ) ) ;
}
} ;
}
}
Ok ( true )
2023-06-18 22:26:50 +00:00
}
2023-06-22 20:08:10 +00:00
2023-08-31 21:36:37 +00:00
pub ( crate ) async fn pin ( & mut self , pin_data : FeaturePost ) -> Result < bool , Box < dyn Error > > {
2023-08-03 21:34:23 +00:00
let res = CLIENT
. post ( self . config . instance . clone ( ) + " /api/v3/post/feature " )
. json ( & pin_data )
2023-09-18 21:01:22 +00:00
. send ( )
. await ? ;
2023-08-03 21:34:23 +00:00
2023-09-18 21:01:22 +00:00
let ret : PostView = serde_json ::from_str ::< HashMap < & str , PostView > > ( res . text ( ) . await ? . as_str ( ) ) ?
2023-08-31 21:36:37 +00:00
. remove ( " post_view " )
. unwrap ( ) ;
2023-08-03 21:34:23 +00:00
return Ok ( ret . post . featured_local ) ;
}
2023-08-31 22:40:59 +00:00
pub ( crate ) async fn idle ( & mut self ) {
2023-09-18 21:01:22 +00:00
let mut sleep_duration = Duration ::seconds ( 30 ) ;
if Utc ::now ( ) - self . start_time > sleep_duration {
sleep_duration = Duration ::seconds ( 60 ) ;
2023-06-22 20:08:10 +00:00
}
2023-08-02 21:39:35 +00:00
2023-09-18 21:01:22 +00:00
match reqwest ::get ( " https://status.neshweb.net/api/push/7s1CjPPzrV?status=up&msg=OK&ping= " ) . await {
2023-08-31 21:36:37 +00:00
Ok ( _ ) = > { }
2023-09-18 21:01:22 +00:00
Err ( err ) = > self . error_queue . push ( format! ( " {} " , err ) ) ,
2023-07-30 18:34:24 +00:00
} ;
2023-09-18 21:01:22 +00:00
while Utc ::now ( ) - self . start_time < sleep_duration {
sleep ( Duration ::milliseconds ( 100 ) . to_std ( ) . unwrap ( ) ) . await ;
}
2023-07-29 00:32:58 +00:00
}
2023-06-18 22:26:50 +00:00
}
2023-08-31 21:36:37 +00:00
async fn list_posts ( auth : & Sensitive < String > , base : String ) -> GetPostsResponse {
2023-06-18 22:26:50 +00:00
let params = GetPosts {
type_ : Some ( ListingType ::Local ) ,
sort : Some ( SortType ::New ) ,
auth : Some ( auth . clone ( ) ) ,
.. Default ::default ( )
} ;
let res = CLIENT
2023-06-19 20:10:28 +00:00
. get ( base + " /api/v3/post/list " )
2023-06-18 22:26:50 +00:00
. query ( & params )
2023-09-18 21:01:22 +00:00
. send ( )
. await
2023-06-18 22:26:50 +00:00
. unwrap ( )
2023-09-18 21:01:22 +00:00
. text ( )
. await
2023-06-18 22:26:50 +00:00
. unwrap ( ) ;
return serde_json ::from_str ( & res ) . unwrap ( ) ;
}
2023-08-31 21:36:37 +00:00
async fn run_bot ( bot : Arc < Mutex < Bot > > ) {
let mut this = bot . lock ( ) . await . clone ( ) ;
2023-09-18 21:01:22 +00:00
let now = Utc ::now ;
let mut config_reload_duration = this . config . config_reload . unwrap_or ( 360 ) ;
let mut config_reload_time = now ( ) - Duration ::minutes ( config_reload_duration as i64 + 1 ) ; // Setting this to be in the future by default prevents unneeded code duplication
2023-08-31 21:36:37 +00:00
loop {
2023-09-18 21:01:22 +00:00
* bot . lock ( ) . await = this . clone ( ) ;
this . start_time = now ( ) ;
2023-08-31 22:18:03 +00:00
this . idle ( ) . await ;
2023-09-18 21:01:22 +00:00
// After configured time passed reload config
if now ( ) - config_reload_time > = Duration ::minutes ( config_reload_duration as i64 ) {
this . config . load ( ) ;
this . secrets . load ( ) ;
config_reload_duration = this . config . config_reload . unwrap_or ( 360 ) ;
let _ = this
. community_ids
. load ( & this . auth , & this . config . instance )
. await ;
this . message_queue
. push ( format! ( " Config Reloaded at {} " , now ( ) . naive_local ( ) ) ) ;
config_reload_time = now ( ) ;
}
// Check if Login token is valid, if it is not get a new one
while this . login_error {
let _ = this . login ( ) . await ;
}
// Perform Run
// Start the polling process
// Get all feed URLs (use cache)
let queue_data = match this
. config
. check_feeds ( & this . post_history , & this . community_ids , & this . auth )
. await
{
Ok ( data ) = > data ,
Err ( e ) = > {
this . error_queue . push ( format! ( " {} " , e ) ) ;
continue ;
}
2023-08-31 21:36:37 +00:00
} ;
2023-09-18 21:01:22 +00:00
this . message_queue
. push ( format! ( " Checked Feeds at {} " , now ( ) . naive_local ( ) ) ) ;
for queued_post in queue_data {
let ( post , post_metadata ) = queued_post ;
this . message_queue . push ( format! ( " Posting: {} " , post . name ) ) ;
// Perform Post and Pins
let post_data = match this . post ( & post ) . await {
Ok ( data ) = > data ,
Err ( e ) = > {
this . error_queue . push ( format! ( " {} " , e ) ) ;
continue ;
}
} ;
let _ = this
. pin_post ( & post_data . post . id , & post_data . community . id )
. await ;
// Update Post History
let mut index_exists = false ;
if this . post_history . len ( ) > post_metadata . id {
index_exists = true ;
}
if let Some ( part_slug ) = post_metadata . part {
match index_exists {
true = > {
this . post_history [ post_metadata . id ] . last_part_slug = Some ( part_slug ) ;
this . post_history [ post_metadata . id ] . last_part_time = Some ( now ( ) . to_string ( ) ) ;
}
false = > {
let new_history = PrevPost {
id : post_metadata . id ,
last_volume_slug : None ,
last_volume_time : None ,
last_part_slug : Some ( part_slug ) ,
last_part_time : Some ( now ( ) . to_string ( ) ) ,
} ;
this . post_history . push ( new_history ) ;
}
}
}
if let Some ( volume_slug ) = post_metadata . volume {
match index_exists {
true = > {
this . post_history [ post_metadata . id ] . last_volume_slug = Some ( volume_slug ) ;
this . post_history [ post_metadata . id ] . last_volume_time = Some ( now ( ) . to_string ( ) ) ;
}
false = > {
let new_history = PrevPost {
id : post_metadata . id ,
last_volume_slug : Some ( volume_slug ) ,
last_volume_time : Some ( now ( ) . to_string ( ) ) ,
last_part_slug : None ,
last_part_time : None ,
} ;
this . post_history . push ( new_history ) ;
}
} ;
}
}
PrevPost ::save ( & this . post_history ) ;
// Fix Queue Lengths and Update Mutex Data
while this . message_queue . len ( ) > 5 {
this . message_queue . remove ( 0 ) ;
}
while this . error_queue . len ( ) > 10 {
this . error_queue . remove ( 0 ) ;
}
2023-08-31 21:36:37 +00:00
2023-08-31 22:18:03 +00:00
this . idle ( ) . await ;
2023-07-30 18:34:24 +00:00
}
2023-06-21 19:29:14 +00:00
}
2023-08-31 21:36:37 +00:00
async fn print_info ( shutdown : Arc < AtomicBool > , bot : Arc < Mutex < Bot > > ) {
while ! shutdown . load ( Ordering ::Relaxed ) {
2023-09-18 21:01:22 +00:00
let snapshot : tokio ::sync ::MutexGuard < '_ , Bot > = bot . lock ( ) . await ;
sleep ( Duration ::milliseconds ( 200 ) . to_std ( ) . unwrap ( ) ) . await ;
2023-08-31 21:36:37 +00:00
2023-08-31 22:11:02 +00:00
print! ( " \x1B [2J \x1B [1;1H " ) ;
2023-08-31 21:36:37 +00:00
println! (
" ##[Ascendance of a Bookworm Bot]## | Time: {} " ,
Utc ::now ( ) . naive_local ( ) . format ( " %H:%M:%S " )
) ;
2023-09-18 21:01:22 +00:00
println! ( " Instance: {} " , snapshot . config . instance ) ;
println! (
" Ran Last: {} " ,
snapshot
. start_time
. naive_local ( )
. format ( " %d/%m/%Y %H:%M:%S " )
) ;
println! ( " {:#<1$} " , " " , 175 ) ;
snapshot . post_history . iter ( ) . for_each ( | post | {
if post . last_part_time . is_some ( ) & & post . last_volume_time . is_some ( ) {
let part_time = post . last_part_time . clone ( ) . unwrap ( ) ;
let volume_time = post . last_volume_time . clone ( ) . unwrap ( ) ;
let parsed_part_time = DateTime ::< Utc > ::from_str ( & part_time ) . unwrap_or ( DateTime ::< Utc > ::from ( UNIX_EPOCH ) ) . naive_local ( ) ;
let parsed_volume_time = DateTime ::< Utc > ::from_str ( & volume_time ) . unwrap_or ( DateTime ::< Utc > ::from ( UNIX_EPOCH ) ) . naive_local ( ) ;
let formatted_time ;
if parsed_part_time > parsed_volume_time {
formatted_time = parsed_part_time ;
}
else {
formatted_time = parsed_volume_time ;
}
print! ( " | {} | " , formatted_time . format ( " %d/%m/%Y %H:%M:%S " ) ) ;
}
else if post . last_part_time . is_some ( ) {
let part_time = post . last_part_time . clone ( ) . unwrap ( ) ;
let formatted_time : NaiveDateTime = DateTime ::< Utc > ::from_str ( & part_time ) . unwrap_or ( DateTime ::< Utc > ::from ( UNIX_EPOCH ) ) . naive_local ( ) ;
print! ( " | {} | " , formatted_time . format ( " %d/%m/%Y %H:%M:%S " ) ) ;
}
else if post . last_volume_time . is_some ( ) {
let volume_time = post . last_volume_time . clone ( ) . unwrap ( ) ;
let formatted_time = DateTime ::< Utc > ::from_str ( & volume_time ) . unwrap_or ( DateTime ::< Utc > ::from ( UNIX_EPOCH ) ) . naive_local ( ) ;
print! ( " | {} | " , formatted_time . format ( " %d/%m/%Y %H:%M:%S " ) ) ;
}
else {
print! ( " | {:<1$} | " , " " , 19 ) ;
}
print! ( " {:<1$} " , " " , 2 - post . id . to_string ( ) . len ( ) ) ;
print! ( " {} | " , post . id ) ;
print! ( " {} " , post . last_part_slug . clone ( ) . unwrap_or ( " N/A " . to_string ( ) ) ) ;
print! ( " {:<1$} | " , " " , 75 - post . last_part_slug . clone ( ) . unwrap_or ( " N/A " . to_string ( ) ) . len ( ) ) ;
print! ( " {} " , post . last_volume_slug . clone ( ) . unwrap_or ( " N/A " . to_string ( ) ) ) ;
println! ( " {:<1$} | " , " " , 70 - post . last_volume_slug . clone ( ) . unwrap_or ( " N/A " . to_string ( ) ) . len ( ) ) ;
2023-08-31 22:40:59 +00:00
} ) ;
2023-09-18 21:01:22 +00:00
println! ( " {:#<1$} " , " " , 175 ) ;
for error in snapshot . error_queue . iter ( ) {
println! ( " {} " , error ) ;
}
println! ( " {:#<1$} " , " " , 175 ) ;
for message in snapshot . message_queue . iter ( ) {
2023-08-31 22:40:59 +00:00
println! ( " {} " , message ) ;
2023-09-18 21:01:22 +00:00
}
2023-08-31 21:36:37 +00:00
}
}
#[ tokio::main ]
async fn main ( ) {
let shutdown = Arc ::new ( AtomicBool ::new ( false ) ) ;
loop {
println! ( " Starting AoB Bot... " ) ;
2023-09-18 21:01:22 +00:00
2023-08-31 21:36:37 +00:00
let shutdown_clone = shutdown . clone ( ) ;
let bot = Arc ::new ( Mutex ::new ( Bot ::new ( ) ) ) ;
let bot_clone = bot . clone ( ) ;
let bot_thread = tokio ::spawn ( async move { run_bot ( bot ) . await } ) ;
let tui_thread = tokio ::spawn ( async move { print_info ( shutdown_clone , bot_clone ) . await } ) ;
let _ = bot_thread . await ;
tui_thread . abort ( ) ;
println! ( " Bot crashed due to unknown Error, restarting thread after wait... " ) ;
2023-09-18 21:01:22 +00:00
sleep ( Duration ::seconds ( 10 ) . to_std ( ) . unwrap ( ) ) . await ;
2023-08-31 21:36:37 +00:00
}
2023-06-18 22:26:50 +00:00
}