diff --git a/src/main.rs b/src/main.rs index f60c5f0..263c40f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,21 @@ -use { - chrono::{ - naive::NaiveDateTime, - offset::{FixedOffset, Utc}, - DateTime, Duration, - }, - serenity::{ - model::{ - channel::Message, - id::{ChannelId, GuildId, RoleId}, - }, - prelude::*, - }, - std::{ - cmp::Ordering::Less, - convert::TryInto, - fs::{read, write}, - iter::once, - mem::drop, +use chrono::{ + naive::NaiveDateTime, + offset::{FixedOffset, Utc}, + DateTime, Duration, +}; +use serenity::{ + model::{ + channel::{GuildChannel, Message}, + id::{ChannelId, GuildId, RoleId}, }, + prelude::*, }; +use std::{collections::HashMap, convert::TryInto, fs, sync::Arc}; //token in gitignore to prevent leak const TOKEN: &str = include_str!("bot-token.txt"); -const ACTIVE_CATEGORIES: [ChannelId; 2] = - [ChannelId(530604963911696404), ChannelId(745791509290418187)]; +const ACTIVE_A_THRU_M: ChannelId = ChannelId(530604963911696404); +const ACTIVE_N_THRU_Z: ChannelId = ChannelId(745791509290418187); const INACTIVE_CATEGORY: ChannelId = ChannelId(541808219593506827); const STICKY_CHANNEL: ChannelId = ChannelId(688618253563592718); const PERMISSION_ROLE: RoleId = RoleId(530618624122028042); @@ -36,29 +28,7 @@ fn main() { match Client::new( TOKEN, Handler { - archived: read(FILE) - .map(|bytes| { - let mut result = Vec::with_capacity(bytes.len() / 20); - let mut slice = &bytes[..]; - while !slice.is_empty() { - result.push(( - ChannelId(u64::from_le_bytes((&slice[..8]).try_into().unwrap())), - DateTime::::from_utc( - NaiveDateTime::from_timestamp( - i64::from_le_bytes((&slice[8..16]).try_into().unwrap()), - 0, //nanoseconds to make Unix timestamp more precise, not needed - ), - FixedOffset::east(i32::from_le_bytes( - (&slice[16..20]).try_into().unwrap(), - )), - ), - )); - slice = &slice[20..]; - } - result - }) - .unwrap_or(vec![]) - .into(), + archived: decode_file(), }, ) { Ok(client) => break client, @@ -75,49 +45,24 @@ struct Handler { archived: Mutex)>>, } -impl EventHandler for Handler { - fn message(&self, ctx: Context, _msg: Message) { - let mut archived_lock = self.archived.lock(); - let mut channels = match GUILD.channels(&ctx) { - Ok(channels) => channels, - Err(why) => { - eprintln!("Couldn't get channels: {:?}", why); - return; - } - }; - let names_and_positions = |filtered_category| { - channels - .iter() - .filter_map(|(_id, guild_channel)| match guild_channel.category_id { - Some(category) - if category == filtered_category && guild_channel.id != STICKY_CHANNEL => - { - Some((guild_channel.name.clone(), guild_channel.position)) - } - _ => None, - }) - .collect() - }; - let (active_n_p, inactive_n_p) = ( - [ - names_and_positions(ACTIVE_CATEGORIES[0]), - names_and_positions(ACTIVE_CATEGORIES[1]), - ], - names_and_positions(INACTIVE_CATEGORY), - ); +impl Handler { + fn archive_channels(&self, ctx: &Context, channels: &mut HashMap) { let relevant_channels = channels.iter_mut().filter_map(|(&id, guild_channel)| { if id == STICKY_CHANNEL { return None; } match guild_channel.category_id { Some(category) - if ACTIVE_CATEGORIES.contains(&category) || category == INACTIVE_CATEGORY => + if [ACTIVE_A_THRU_M, ACTIVE_N_THRU_Z, INACTIVE_CATEGORY] + .contains(&category) => { Some(guild_channel) } _ => None, } }); + + let mut archived_lock = self.archived.lock(); for channel in relevant_channels { //no more than 100 messages allowed let messages = match channel.messages(&ctx, |get_messages| get_messages.limit(100)) { @@ -127,6 +72,7 @@ impl EventHandler for Handler { if messages.is_empty() { continue; } + let last_message = messages.iter().find(|message| message.webhook_id == None); if let Some(message) = last_message { let entry = ( @@ -136,7 +82,7 @@ impl EventHandler for Handler { if message.content.trim() == "!archive" && message .author - .has_role(&ctx, GUILD, PERMISSION_ROLE) + .has_role(ctx, GUILD, PERMISSION_ROLE) .unwrap_or(false) && !archived_lock.contains(&entry) { @@ -148,21 +94,21 @@ impl EventHandler for Handler { } archived_lock.push(entry); update_file(&archived_lock); - let _ = channel.delete_messages(&ctx, once(message)); } } + let new_category = match &last_message { - Some(message) + Some(&ref message) if { let timestamp: DateTime = message.timestamp.into(); Utc::now() - timestamp < Duration::days(30 * 2) } => { let active_category = { - if channel.name.cmp(&String::from("n")) == Less { - ACTIVE_CATEGORIES[0] + if &*channel.name < "n" { + ACTIVE_A_THRU_M } else { - ACTIVE_CATEGORIES[1] + ACTIVE_N_THRU_Z } }; if let Some(index) = archived_lock.iter().position(|(id, timestamp)| { @@ -185,32 +131,83 @@ impl EventHandler for Handler { if new_category == channel.category_id.unwrap() { continue; } - let names_and_positions: &Vec<_> = if new_category == ACTIVE_CATEGORIES[0] { - &active_n_p[0] - } else if new_category == ACTIVE_CATEGORIES[1] { - &active_n_p[1] - } else { - &inactive_n_p - }; - let new_position = names_and_positions + + if let Err(why) = channel.edit(ctx, |edit_channel| edit_channel.category(new_category)) + { + eprintln!("Couldn't edit channel: {:?}", why); + return; + } + } + drop(archived_lock); + } + + fn sort_channels(ctx: &Context, channels: &mut HashMap) { + for category in &[ACTIVE_A_THRU_M, ACTIVE_N_THRU_Z, INACTIVE_CATEGORY] { + let mut channels = channels + .iter_mut() + .filter_map(|(_id, guild_channel)| match guild_channel.category_id { + Some(cat) if &cat == category => Some(guild_channel), + _ => None, + }) + .collect::>(); + channels.sort_by_key(|channel| channel.position); + let old_positions = channels .iter() - .max_by(|(left, _), (right, _)| { - if left >= &channel.name && right >= &channel.name { - right.cmp(left) //smallest - } else { - left.cmp(right) //biggest - } + .enumerate() + .map(|(idx, channel)| (channel.id, idx)) + .collect::>(); + channels.sort_by_key(|channel| channel.name.clone()); + if let Some(pos) = channels + .iter() + .position(|channel| channel.id == STICKY_CHANNEL) + { + channels.swap(pos, 0); + } + if let Err(why) = channels + .into_iter() + .enumerate() + .filter(|(idx, channel)| { + let old_position = *old_positions.get(&channel.id).unwrap(); + let new_position = *idx; + old_position != new_position }) - .map(|(name, pos)| if &channel.name < name { *pos } else { pos + 1 }) - .unwrap_or(1) - .try_into() - .unwrap(); - let _ = channel.edit(&ctx, |edit_channel| { - edit_channel.category(new_category).position(new_position) - }); + .try_for_each(|(new_position, channel)| -> serenity::Result<()> { + channel.edit(ctx, |edit_channel| { + edit_channel.position(new_position as u64) + })?; + Ok(()) + }) + { + eprintln!("Couldn't edit channel: {:?}", why); + return; + } } - //makes sure the message functions always run in sequence - drop(archived_lock); + } + + fn tick(&self, ctx: &Context) { + let mut channels = match GUILD.channels(&ctx) { + Ok(channels) => channels, + Err(why) => { + eprintln!("Couldn't get channels: {:?}", why); + return; + } + }; + self.archive_channels(ctx, &mut channels); + Self::sort_channels(ctx, &mut channels); + } +} + +impl EventHandler for Handler { + fn channel_create(&self, ctx: Context, _channel: Arc>) { + self.tick(&ctx); + } + + fn channel_delete(&self, ctx: Context, _channel: Arc>) { + self.tick(&ctx); + } + + fn message(&self, ctx: Context, _msg: Message) { + self.tick(&ctx); } } @@ -221,5 +218,32 @@ fn update_file(archived: &[(ChannelId, DateTime)]) { out.extend_from_slice(×tamp.timestamp().to_le_bytes()); out.extend_from_slice(×tamp.offset().local_minus_utc().to_le_bytes()); } - let _ = write(FILE, out); + if let Err(why) = fs::write(FILE, out) { + eprintln!("Couldn't update archived channel file: {:?}", why); + return; + }; +} + +fn decode_file() -> Mutex)>> { + fs::read(FILE) + .map(|bytes| { + let mut result = Vec::with_capacity(bytes.len() / 20); + let mut slice = &bytes[..]; + while !slice.is_empty() { + result.push(( + ChannelId(u64::from_le_bytes((&slice[..8]).try_into().unwrap())), + DateTime::::from_utc( + NaiveDateTime::from_timestamp( + i64::from_le_bytes((&slice[8..16]).try_into().unwrap()), + 0, //nanoseconds to make Unix timestamp more precise, not needed + ), + FixedOffset::east(i32::from_le_bytes((&slice[16..20]).try_into().unwrap())), + ), + )); + slice = &slice[20..]; + } + result + }) + .unwrap_or_default() + .into() }