/ src / main.rs
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  }