main.rs
1 use std::env; 2 use std::sync::Arc; 3 use std::time::Duration; 4 use chrono::DateTime; 5 use chrono::Utc; 6 use dotenvy::dotenv; 7 8 use serenity::all::ChannelId; 9 use serenity::async_trait; 10 use serenity::builder::GetMessages; 11 use serenity::http; 12 use serenity::prelude::*; 13 use serenity::model::channel::Message; 14 use serenity::model::gateway::Ready; 15 use serenity::model::timestamp::Timestamp; 16 use serenity::framework::standard::macros::{command, group}; 17 use serenity::framework::standard::{StandardFramework, Configuration, CommandResult}; 18 use tokio::task::JoinHandle; 19 use tokio::time::sleep; 20 21 fn get_link_to_msg(msg: &Message) -> Option<String> { 22 if msg.guild_id.is_some() { 23 Some(format!("https://discord.com/channels/{}/{}/{}", msg.guild_id.unwrap().get(), msg.channel_id.get(), msg.id.get())) 24 } else { 25 None 26 } 27 } 28 29 fn get_fleeting_channel_id() -> ChannelId { 30 use std::str::FromStr; 31 let channel_id = env::var("CHANNEL_ID").expect("Channel ID not specified"); 32 ChannelId::from_str(&channel_id).unwrap() 33 } 34 35 #[group] 36 #[commands(ping)] 37 struct General; 38 39 fn serenity_ts_to_chrono_dt(ts: Timestamp) -> DateTime<Utc> { 40 let ts = ts.to_rfc3339().unwrap(); 41 DateTime::parse_from_rfc3339(&ts).unwrap().with_timezone(&Utc) 42 } 43 44 #[derive(Clone)] 45 struct Handler{ 46 messages: Arc<Mutex<Vec<Message>>>, 47 timer: Arc<Mutex<Option<JoinHandle<()>>>>, 48 } 49 50 impl Handler { 51 fn new(messages: Arc<Mutex<Vec<Message>>>) -> Self { 52 Self { messages, timer: Arc::new(Mutex::new(None)) } 53 } 54 55 /** 56 * Start the timer to process messages 57 * The timer will not stop until there are no more messages to process 58 */ 59 async fn start_timer(&self, ctx: Context) { 60 let messages = self.messages.clone(); 61 let timer = self.timer.clone(); 62 let http = ctx.http.clone(); 63 { 64 let mut timer_shared_state = timer.lock().await; 65 // Early exit if the timer is already running 66 if timer_shared_state.is_some() { return; } 67 68 69 let timer = self.timer.clone(); 70 let task = tokio::spawn(async move { 71 let mut timer_shared_state = timer.lock().await; 72 loop { 73 let message = { 74 let mut messages = messages.lock().await; 75 // Early exit if there are no messages to process 76 if messages.is_empty() { 77 *timer_shared_state = None; 78 break; 79 } 80 messages.remove(0) 81 }; 82 // Compute sleep duration 83 let day = std::time::Duration::from_secs(60); 84 let sleep_duration = { 85 let timestamp = message.timestamp; 86 let target: DateTime<Utc> = serenity_ts_to_chrono_dt(timestamp) + day; 87 let duration = target.signed_duration_since(Utc::now()); 88 duration 89 }; 90 // Sleep until the message is ready to be processed 91 sleep(sleep_duration.to_std().unwrap_or(Duration::ZERO)).await; 92 93 // Process message 94 let threshold = { 95 let threshold = std::time::SystemTime::now() - day; 96 let threshold = threshold.duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_millis(); 97 Timestamp::from_millis(threshold.try_into().unwrap()).unwrap() 98 }; 99 if message.timestamp <= threshold { 100 // Delete message 101 match message.delete(&http).await { 102 Ok(_) => {}, 103 Err(e) => { 104 let link_str = get_link_to_msg(&message).map_or("".to_string(), |link| link); 105 eprintln!("Error deleting message {link_str}: {}", e); 106 // Re-insert message into queue 107 let messages = messages.clone(); 108 { 109 let mut messages = messages.lock().await; 110 messages.insert(0, message); 111 } 112 } 113 } 114 //println!("Del: {}", get_link_to_msg(&message).unwrap_or(message.content.to_string())); 115 } else { 116 panic!("Message was not ready to be processed but should have been ready"); 117 } 118 } 119 }); 120 *timer_shared_state = Some(task); 121 } 122 } 123 } 124 125 #[async_trait] 126 impl EventHandler for Handler { 127 // Add new messages to the all-messages vector 128 async fn message(&self, ctx: Context, msg: Message) { 129 if msg.channel_id != get_fleeting_channel_id() { return; } 130 { 131 let mut messages = self.messages.lock().await; 132 messages.push(msg); 133 } 134 // Start the timer to process them 135 let this = Arc::new(Mutex::new(self.clone())); 136 tokio::spawn(async move { 137 let this = this.clone(); 138 let handler = this.lock().await; 139 handler.start_timer(ctx).await; 140 }); 141 } 142 143 // Populate the messages vector with all messages in the channel 144 async fn ready(&self, ctx: Context, _ready: Ready) { 145 // Get channelid obj 146 let fleeting_channelid = get_fleeting_channel_id(); 147 // Populate all messages 148 let mut last_id = None; 149 loop { 150 let messages_slice = fleeting_channelid.messages(&ctx.http, { 151 let retriever = GetMessages::new() 152 .limit(100); 153 let retriever = if let Some(id) = last_id { 154 // Continue where we left off from the last slice 155 retriever.before(id) 156 } else { retriever }; 157 retriever 158 }).await.expect("Error fetching messages"); 159 160 if messages_slice.is_empty() { 161 // No more messages 162 break; 163 } 164 last_id = messages_slice.last().map(|m| m.id); 165 166 // Continue populating all messages with more messages 167 { 168 let mut messages = self.messages.lock().await; 169 for message in messages_slice { 170 messages.push(message); 171 } 172 } 173 } 174 // Flip order because newest was at the beginning 175 { 176 let mut messages = self.messages.lock().await; 177 messages.reverse(); 178 } 179 180 // Start the timer to process them 181 let this = Arc::new(Mutex::new(self.clone())); 182 tokio::spawn(async move { 183 let this = this.clone(); 184 let handler = this.lock().await; 185 handler.start_timer(ctx).await; 186 }); 187 } 188 } 189 190 #[tokio::main] 191 async fn main() { 192 dotenv().expect(".env file not found"); 193 194 let framework = StandardFramework::new().group(&GENERAL_GROUP); 195 framework.configure(Configuration::new().prefix("。")); // set the bot's prefix to "。" 196 197 // Queue of messages 198 let messages = Arc::new(Mutex::new(Vec::new())); 199 200 // Login with a bot token from the environment 201 let token = env::var("DISCORD_TOKEN").expect("token"); 202 let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MESSAGES; 203 let mut client = Client::builder(token, intents) 204 .event_handler(Handler::new(messages.clone())) 205 .framework(framework) 206 .await 207 .expect("Error creating client"); 208 209 // start listening for events by starting a single shard 210 if let Err(why) = client.start().await { 211 println!("An error occurred while running the client: {:?}", why); 212 } 213 } 214 215 #[command] 216 async fn ping(ctx: &Context, msg: &Message) -> CommandResult { 217 msg.reply(ctx, "Pong!").await?; 218 219 println!("Msg: {}", get_link_to_msg(msg).unwrap_or("No link".to_string())); 220 221 Ok(()) 222 }