From ed8be4582968d06fa8a9bfaf3bccc534dbd83683 Mon Sep 17 00:00:00 2001 From: Niklas Elsbrock Date: Thu, 26 May 2022 22:59:42 +0200 Subject: [PATCH] several changes and updates --- Cargo.lock | 90 ++++++++++++++++++++++++++++++++++- Cargo.toml | 4 +- src/bin/spamcontest.rs | 24 ++++++++-- src/lib.rs | 104 +++++++++++++++++++++++++++-------------- 4 files changed, 179 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 602e851..68c3726 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "async-trait" version = "0.1.53" @@ -35,6 +44,17 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -170,6 +190,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "flate2" version = "1.0.23" @@ -358,6 +391,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.18" @@ -566,9 +605,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "ordered-float" @@ -683,6 +722,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + [[package]] name = "reqwest" version = "0.11.10" @@ -875,6 +931,15 @@ dependencies = [ "digest", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.6" @@ -902,7 +967,9 @@ name = "spamcontest" version = "0.1.0" dependencies = [ "chrono", + "env_logger", "itertools", + "log", "serenity", "tokio", ] @@ -924,6 +991,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.31" @@ -994,6 +1070,7 @@ dependencies = [ "num_cpus", "once_cell", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "winapi", @@ -1321,6 +1398,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 4cc667c..940532d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +log = "0.4" +env_logger = "0.9" chrono = "0.4" itertools = "0.10" -tokio = {version = "1.18", features = ["rt-multi-thread"]} +tokio = {version = "1.18", features = ["rt-multi-thread", "signal"]} [dependencies.serenity] version = "0.11" diff --git a/src/bin/spamcontest.rs b/src/bin/spamcontest.rs index 8deb6a4..b7026f4 100644 --- a/src/bin/spamcontest.rs +++ b/src/bin/spamcontest.rs @@ -1,3 +1,4 @@ +use log::{debug, error, LevelFilter}; use serenity::prelude::*; use spamcontest::Handler; use std::{env, process}; @@ -6,11 +7,16 @@ const TOKEN_VAR_KEY: &str = "DISCORD_TOKEN"; #[tokio::main] async fn main() { + env_logger::builder() + .filter_module(module_path!(), LevelFilter::Info) + .parse_default_env() + .init(); + let token = match env::var(TOKEN_VAR_KEY) { Ok(token) => token, Err(err) => { - eprintln!("Unable to get {}: {}", TOKEN_VAR_KEY, err); - process::exit(1) + error!("Unable to get {}: {}", TOKEN_VAR_KEY, err); + process::exit(1); } }; @@ -21,12 +27,20 @@ async fn main() { { Ok(client) => client, Err(err) => { - eprintln!("Unable to start client: {}", err); - process::exit(2) + error!("Unable to start client: {:?}", err); + process::exit(2); } }; + let shard_manager = client.shard_manager.clone(); + tokio::spawn(async move { + tokio::signal::ctrl_c().await.expect("ctrc+c handler error"); + debug!("Shutting down..."); + shard_manager.lock().await.shutdown_all().await; + }); + if let Err(err) = client.start().await { - eprintln!("An Error occurred while running the client: {:?}", err) + error!("An Error occurred while running the client: {:?}", err); + process::exit(2); } } diff --git a/src/lib.rs b/src/lib.rs index 3993099..24faae0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,16 @@ use itertools::Itertools; +use log::{debug, error, info}; +use serenity::client::{Context, EventHandler}; use serenity::model::channel::Message; +use serenity::model::event::ResumedEvent; +use serenity::model::gateway::{Activity, Ready}; use serenity::model::id::{ChannelId, UserId}; -use serenity::prelude::*; use serenity::utils::Colour; use std::cmp::Reverse; use std::collections::HashMap; use std::fmt::Display; use std::ops::RangeInclusive; -use std::sync::Arc; +use std::sync::Mutex; use std::time::Duration; const DEFAULT_CONTEST_DURATION: Duration = Duration::from_secs(60); @@ -17,9 +20,11 @@ const ALLOWED_DURATION_RANGE: RangeInclusive = const PIN_ANNOUNCEMENT_THRESHOLD: Duration = Duration::from_secs(5 * 60); +type Contests = Mutex>; + #[derive(Default)] pub struct Handler { - contests: Arc>>, + contests: Contests, } impl Handler { @@ -31,7 +36,13 @@ impl Handler { #[serenity::async_trait] impl EventHandler for Handler { async fn message(&self, ctx: Context, msg: Message) { - if let Some(contest) = self.contests.lock().await.get_mut(&msg.channel_id) { + if let Some(contest) = self.contests.lock().unwrap().get_mut(&msg.channel_id) { + debug!( + "Counting message {} (from {} in channel {})", + msg.id, + msg.author.tag(), + msg.channel_id.0 + ); contest.count(&msg); return; } @@ -42,11 +53,17 @@ impl EventHandler for Handler { .split_ascii_whitespace() .filter_map(|w| w.parse().ok()) .map(Duration::from_secs) - .filter(|d| ALLOWED_DURATION_RANGE.contains(d)) - .next() + .find(|d| ALLOWED_DURATION_RANGE.contains(d)) .unwrap_or(DEFAULT_CONTEST_DURATION); - if let Err(err) = run_contest( + info!( + "User {} started a {} second contest in channel {}", + msg.author.tag(), + duration.as_secs(), + msg.channel_id.0 + ); + + match run_contest( ctx, msg.channel_id, duration, @@ -55,63 +72,81 @@ impl EventHandler for Handler { ) .await { - eprintln!("Error: {:?}", err) - } + Ok(contest) => debug!( + "Contest in channel {} has ended with {} participant(s)", + msg.channel_id.0, + contest.counts.len() + ), + Err(err) => error!( + "Error while running contest in channel {}: {:?}", + msg.channel_id.0, err + ), + }; } } + + async fn ready(&self, ctx: Context, ready: Ready) { + info!("Connected as {}", ready.user.tag()); + ctx.set_activity(Activity::listening("Spam")).await; + } + + async fn resume(&self, _ctx: Context, _: ResumedEvent) { + info!("Resumed"); + } } #[derive(Default, PartialEq, Eq)] -pub struct SpamCount { +struct SpamCount { messages: usize, characters: usize, } #[derive(Default)] -pub struct Contest { +struct Contest { counts: HashMap, } impl Contest { - pub fn new() -> Self { + fn new() -> Self { Self::default() } - pub fn count(&mut self, message: &Message) { + fn count(&mut self, message: &Message) { + let char_count = message.content.chars().count(); match self.counts.get_mut(&message.author.id) { None => { self.counts.insert( message.author.id, SpamCount { messages: 1, - characters: message.content.len(), + characters: char_count, }, ); } Some(count) => { count.messages += 1; - count.characters += message.content.len() + count.characters += char_count; } }; } - pub fn ranking_by(&self, kf: F, df: D) -> String + fn ranking_by(&self, fk: Fk, fd: Fd) -> String where - F: Fn(&SpamCount) -> K, + Fk: Fn(&SpamCount) -> K, K: Ord, - D: Fn(&SpamCount) -> S, - S: Display, + Fd: Fn(&SpamCount) -> D, + D: Display, { let mut ranking = self.counts.iter().collect::>(); - ranking.sort_unstable_by_key(|(_, c)| kf(c)); + ranking.sort_unstable_by_key(|(_, c)| fk(c)); let mut result = String::new(); let mut cur_rank_num = 1; - for (_, rank_group) in &ranking.into_iter().group_by(|elt| kf((*elt).1)) { + for (_, rank_group) in &ranking.into_iter().group_by(|elt| fk((*elt).1)) { let mut group_size = 0; for (userid, count) in rank_group { result.push_str( - format!("**{}.:** <@{}> ({})\n", cur_rank_num, userid, df(count)).as_str(), + format!("**{cur_rank_num}.:** <@{userid}> ({})\n", fd(count)).as_str(), ); group_size += 1; } @@ -121,13 +156,13 @@ impl Contest { } } -pub async fn run_contest( +async fn run_contest( ctx: Context, channel_id: ChannelId, duration: Duration, - contests: &Arc>>, - pin_announcement: bool, -) -> serenity::Result<()> { + contests: &Contests, + pin: bool, +) -> serenity::Result { let end_timestamp = (chrono::Utc::now() + chrono::Duration::from_std(duration).unwrap()).timestamp() + 1; @@ -137,27 +172,26 @@ pub async fn run_contest( m.embed(|e| { e.title("Es wurde ein Spam-Wettbewerb gestartet!") .description(format!( - "Wer am meisten spamt, gewinnt.\nEnde .", - end_timestamp + "Wer am meisten spamt, gewinnt.\nEnde .", )) .colour(Colour::BLUE) }) }) .await?; - if pin_announcement { - announcement.pin(&ctx.http).await?; + if pin { + announcement.pin(&ctx.http).await.ok(); } - contests.lock().await.insert(channel_id, Contest::new()); + contests.lock().unwrap().insert(channel_id, Contest::new()); tokio::time::sleep(duration).await; - let contest = contests.lock().await.remove(&channel_id).unwrap(); + let contest = contests.lock().unwrap().remove(&channel_id).unwrap(); if contest.counts.is_empty() { announcement.delete(&ctx.http).await?; } else { - if pin_announcement { - announcement.unpin(&ctx.http).await?; + if pin { + announcement.unpin(&ctx.http).await.ok(); } // send ranking message @@ -181,5 +215,5 @@ pub async fn run_contest( .await?; } - Ok(()) + Ok(contest) }