several changes and updates
This commit is contained in:
+19
-5
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+69
-35
@@ -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<Duration> =
|
||||
|
||||
const PIN_ANNOUNCEMENT_THRESHOLD: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
type Contests = Mutex<HashMap<ChannelId, Contest>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Handler {
|
||||
contests: Arc<Mutex<HashMap<ChannelId, Contest>>>,
|
||||
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<UserId, SpamCount>,
|
||||
}
|
||||
|
||||
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<F, K, D, S>(&self, kf: F, df: D) -> String
|
||||
fn ranking_by<Fk, K, Fd, D>(&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::<Vec<_>>();
|
||||
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<Mutex<HashMap<ChannelId, Contest>>>,
|
||||
pin_announcement: bool,
|
||||
) -> serenity::Result<()> {
|
||||
contests: &Contests,
|
||||
pin: bool,
|
||||
) -> serenity::Result<Contest> {
|
||||
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 <t:{}:R>.",
|
||||
end_timestamp
|
||||
"Wer am meisten spamt, gewinnt.\nEnde <t:{end_timestamp}:R>.",
|
||||
))
|
||||
.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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user