several changes and updates

This commit is contained in:
2022-05-26 22:59:42 +02:00
parent 578f3dcc2f
commit ed8be45829
4 changed files with 179 additions and 43 deletions
Generated
+88 -2
View File
@@ -8,6 +8,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.53" version = "0.1.53"
@@ -35,6 +44,17 @@ dependencies = [
"webpki-roots", "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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@@ -170,6 +190,19 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.23" version = "1.0.23"
@@ -358,6 +391,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.18" version = "0.14.18"
@@ -566,9 +605,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
@@ -683,6 +722,23 @@ dependencies = [
"bitflags", "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]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.10" version = "0.11.10"
@@ -875,6 +931,15 @@ dependencies = [
"digest", "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]] [[package]]
name = "slab" name = "slab"
version = "0.4.6" version = "0.4.6"
@@ -902,7 +967,9 @@ name = "spamcontest"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"env_logger",
"itertools", "itertools",
"log",
"serenity", "serenity",
"tokio", "tokio",
] ]
@@ -924,6 +991,15 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.31"
@@ -994,6 +1070,7 @@ dependencies = [
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"winapi", "winapi",
@@ -1321,6 +1398,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 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]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
+3 -1
View File
@@ -6,9 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
log = "0.4"
env_logger = "0.9"
chrono = "0.4" chrono = "0.4"
itertools = "0.10" itertools = "0.10"
tokio = {version = "1.18", features = ["rt-multi-thread"]} tokio = {version = "1.18", features = ["rt-multi-thread", "signal"]}
[dependencies.serenity] [dependencies.serenity]
version = "0.11" version = "0.11"
+19 -5
View File
@@ -1,3 +1,4 @@
use log::{debug, error, LevelFilter};
use serenity::prelude::*; use serenity::prelude::*;
use spamcontest::Handler; use spamcontest::Handler;
use std::{env, process}; use std::{env, process};
@@ -6,11 +7,16 @@ const TOKEN_VAR_KEY: &str = "DISCORD_TOKEN";
#[tokio::main] #[tokio::main]
async fn 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) { let token = match env::var(TOKEN_VAR_KEY) {
Ok(token) => token, Ok(token) => token,
Err(err) => { Err(err) => {
eprintln!("Unable to get {}: {}", TOKEN_VAR_KEY, err); error!("Unable to get {}: {}", TOKEN_VAR_KEY, err);
process::exit(1) process::exit(1);
} }
}; };
@@ -21,12 +27,20 @@ async fn main() {
{ {
Ok(client) => client, Ok(client) => client,
Err(err) => { Err(err) => {
eprintln!("Unable to start client: {}", err); error!("Unable to start client: {:?}", err);
process::exit(2) 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 { 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);
} }
} }
+69 -35
View File
@@ -1,13 +1,16 @@
use itertools::Itertools; use itertools::Itertools;
use log::{debug, error, info};
use serenity::client::{Context, EventHandler};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use serenity::model::event::ResumedEvent;
use serenity::model::gateway::{Activity, Ready};
use serenity::model::id::{ChannelId, UserId}; use serenity::model::id::{ChannelId, UserId};
use serenity::prelude::*;
use serenity::utils::Colour; use serenity::utils::Colour;
use std::cmp::Reverse; use std::cmp::Reverse;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use std::sync::Arc; use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
const DEFAULT_CONTEST_DURATION: Duration = Duration::from_secs(60); const DEFAULT_CONTEST_DURATION: Duration = Duration::from_secs(60);
@@ -17,9 +20,11 @@ const ALLOWED_DURATION_RANGE: RangeInclusive<Duration> =
const PIN_ANNOUNCEMENT_THRESHOLD: Duration = Duration::from_secs(5 * 60); const PIN_ANNOUNCEMENT_THRESHOLD: Duration = Duration::from_secs(5 * 60);
type Contests = Mutex<HashMap<ChannelId, Contest>>;
#[derive(Default)] #[derive(Default)]
pub struct Handler { pub struct Handler {
contests: Arc<Mutex<HashMap<ChannelId, Contest>>>, contests: Contests,
} }
impl Handler { impl Handler {
@@ -31,7 +36,13 @@ impl Handler {
#[serenity::async_trait] #[serenity::async_trait]
impl EventHandler for Handler { impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) { 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); contest.count(&msg);
return; return;
} }
@@ -42,11 +53,17 @@ impl EventHandler for Handler {
.split_ascii_whitespace() .split_ascii_whitespace()
.filter_map(|w| w.parse().ok()) .filter_map(|w| w.parse().ok())
.map(Duration::from_secs) .map(Duration::from_secs)
.filter(|d| ALLOWED_DURATION_RANGE.contains(d)) .find(|d| ALLOWED_DURATION_RANGE.contains(d))
.next()
.unwrap_or(DEFAULT_CONTEST_DURATION); .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, ctx,
msg.channel_id, msg.channel_id,
duration, duration,
@@ -55,63 +72,81 @@ impl EventHandler for Handler {
) )
.await .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)] #[derive(Default, PartialEq, Eq)]
pub struct SpamCount { struct SpamCount {
messages: usize, messages: usize,
characters: usize, characters: usize,
} }
#[derive(Default)] #[derive(Default)]
pub struct Contest { struct Contest {
counts: HashMap<UserId, SpamCount>, counts: HashMap<UserId, SpamCount>,
} }
impl Contest { impl Contest {
pub fn new() -> Self { fn new() -> Self {
Self::default() 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) { match self.counts.get_mut(&message.author.id) {
None => { None => {
self.counts.insert( self.counts.insert(
message.author.id, message.author.id,
SpamCount { SpamCount {
messages: 1, messages: 1,
characters: message.content.len(), characters: char_count,
}, },
); );
} }
Some(count) => { Some(count) => {
count.messages += 1; count.messages += 1;
count.characters += message.content.len() count.characters += char_count;
} }
}; };
} }
pub fn ranking_by<F, K, D, S>(&self, kf: F, df: D) -> String fn ranking_by<Fk, K, Fd, D>(&self, fk: Fk, fd: Fd) -> String
where where
F: Fn(&SpamCount) -> K, Fk: Fn(&SpamCount) -> K,
K: Ord, K: Ord,
D: Fn(&SpamCount) -> S, Fd: Fn(&SpamCount) -> D,
S: Display, D: Display,
{ {
let mut ranking = self.counts.iter().collect::<Vec<_>>(); let mut ranking = self.counts.iter().collect::<Vec<_>>();
ranking.sort_unstable_by_key(|(_, c)| kf(c)); ranking.sort_unstable_by_key(|(_, c)| fk(c));
let mut result = String::new(); let mut result = String::new();
let mut cur_rank_num = 1; 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; let mut group_size = 0;
for (userid, count) in rank_group { for (userid, count) in rank_group {
result.push_str( 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; group_size += 1;
} }
@@ -121,13 +156,13 @@ impl Contest {
} }
} }
pub async fn run_contest( async fn run_contest(
ctx: Context, ctx: Context,
channel_id: ChannelId, channel_id: ChannelId,
duration: Duration, duration: Duration,
contests: &Arc<Mutex<HashMap<ChannelId, Contest>>>, contests: &Contests,
pin_announcement: bool, pin: bool,
) -> serenity::Result<()> { ) -> serenity::Result<Contest> {
let end_timestamp = let end_timestamp =
(chrono::Utc::now() + chrono::Duration::from_std(duration).unwrap()).timestamp() + 1; (chrono::Utc::now() + chrono::Duration::from_std(duration).unwrap()).timestamp() + 1;
@@ -137,27 +172,26 @@ pub async fn run_contest(
m.embed(|e| { m.embed(|e| {
e.title("Es wurde ein Spam-Wettbewerb gestartet!") e.title("Es wurde ein Spam-Wettbewerb gestartet!")
.description(format!( .description(format!(
"Wer am meisten spamt, gewinnt.\nEnde <t:{}:R>.", "Wer am meisten spamt, gewinnt.\nEnde <t:{end_timestamp}:R>.",
end_timestamp
)) ))
.colour(Colour::BLUE) .colour(Colour::BLUE)
}) })
}) })
.await?; .await?;
if pin_announcement { if pin {
announcement.pin(&ctx.http).await?; 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; 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() { if contest.counts.is_empty() {
announcement.delete(&ctx.http).await?; announcement.delete(&ctx.http).await?;
} else { } else {
if pin_announcement { if pin {
announcement.unpin(&ctx.http).await?; announcement.unpin(&ctx.http).await.ok();
} }
// send ranking message // send ranking message
@@ -181,5 +215,5 @@ pub async fn run_contest(
.await?; .await?;
} }
Ok(()) Ok(contest)
} }