Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1 from ThePuzzlemaker/master
Browse files Browse the repository at this point in the history
Code cleanup and improve algorithm to both fix bugs and make it work faster (hopefully)
  • Loading branch information
TimonPasslick authored Apr 6, 2021
2 parents 3bac6a2 + 57dc4a5 commit 4ff6166
Showing 1 changed file with 129 additions and 105 deletions.
234 changes: 129 additions & 105 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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::<FixedOffset>::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,
Expand All @@ -75,49 +45,24 @@ struct Handler {
archived: Mutex<Vec<(ChannelId, DateTime<FixedOffset>)>>,
}

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<ChannelId, GuildChannel>) {
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)) {
Expand All @@ -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 = (
Expand All @@ -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)
{
Expand All @@ -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<Utc> = 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)| {
Expand All @@ -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<ChannelId, GuildChannel>) {
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::<Vec<_>>();
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::<HashMap<_, _>>();
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<RwLock<GuildChannel>>) {
self.tick(&ctx);
}

fn channel_delete(&self, ctx: Context, _channel: Arc<RwLock<GuildChannel>>) {
self.tick(&ctx);
}

fn message(&self, ctx: Context, _msg: Message) {
self.tick(&ctx);
}
}

Expand All @@ -221,5 +218,32 @@ fn update_file(archived: &[(ChannelId, DateTime<FixedOffset>)]) {
out.extend_from_slice(&timestamp.timestamp().to_le_bytes());
out.extend_from_slice(&timestamp.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<Vec<(ChannelId, DateTime<FixedOffset>)>> {
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::<FixedOffset>::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()
}

0 comments on commit 4ff6166

Please sign in to comment.