Compare commits
15
Commits
c5e3d701ab
..
troll
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7e6f002f2
|
||
|
|
9b662232ff
|
||
|
|
2dfe04608b
|
||
|
|
8acb879ebc
|
||
|
|
2d9ff53bae
|
||
|
|
b9522c761d
|
||
|
|
57b41d8e63
|
||
|
|
8a0527972f
|
||
|
|
1c0b36ae60
|
||
|
|
194ebcb071
|
||
|
|
f9089bc9b7
|
||
|
|
cda2defde5
|
||
|
|
f8094d2872
|
||
|
|
d3f2aeb220
|
||
|
|
1333b1f02f
|
Generated
+1997
-516
File diff suppressed because it is too large
Load Diff
+7
-6
@@ -7,13 +7,14 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.9"
|
env_logger = "0.11.8"
|
||||||
chrono = "0.4"
|
time = "0.3.41"
|
||||||
itertools = "0.10"
|
itertools = "0.14"
|
||||||
tokio = {version = "1.20", features = ["rt-multi-thread", "signal"]}
|
tokio = {version = "1.47", features = ["rt-multi-thread", "signal"]}
|
||||||
dashmap = "5.3.4"
|
dashmap = "6.1.0"
|
||||||
|
reqwest = { version = "0.13.2", features = ["json"] }
|
||||||
|
|
||||||
[dependencies.serenity]
|
[dependencies.serenity]
|
||||||
version = "0.11"
|
version = "0.12"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"]
|
features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"]
|
||||||
@@ -40,7 +40,7 @@ async fn main() -> ExitCode {
|
|||||||
.expect("error on waiting for shutdown signal");
|
.expect("error on waiting for shutdown signal");
|
||||||
|
|
||||||
debug!("Shutting down...");
|
debug!("Shutting down...");
|
||||||
shard_manager.lock().await.shutdown_all().await;
|
shard_manager.shutdown_all().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
match client.start().await {
|
match client.start().await {
|
||||||
|
|||||||
+84
-24
@@ -1,17 +1,20 @@
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
use serenity::all::{ActivityData, CreateEmbed, CreateMessage, TypingStartEvent};
|
||||||
use serenity::client::{Context, EventHandler};
|
use serenity::client::{Context, EventHandler};
|
||||||
use serenity::model::channel::Message;
|
use serenity::model::channel::Message;
|
||||||
|
use serenity::model::colour::Colour;
|
||||||
use serenity::model::event::ResumedEvent;
|
use serenity::model::event::ResumedEvent;
|
||||||
use serenity::model::gateway::{Activity, Ready};
|
use serenity::model::gateway::Ready;
|
||||||
use serenity::model::id::{ChannelId, UserId};
|
use serenity::model::id::{ChannelId, UserId};
|
||||||
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::time::Duration;
|
use std::sync::Mutex;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use time::OffsetDateTime;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
const DEFAULT_CONTEST_DURATION: Duration = Duration::from_secs(60);
|
const DEFAULT_CONTEST_DURATION: Duration = Duration::from_secs(60);
|
||||||
@@ -26,23 +29,76 @@ type Contests = DashMap<ChannelId, mpsc::Sender<Message>>;
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Handler {
|
pub struct Handler {
|
||||||
contests: Contests,
|
contests: Contests,
|
||||||
|
ntfy_typing_last: Mutex<Option<Instant>>,
|
||||||
|
http_client: reqwest::Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self {
|
||||||
|
contests: Contests::new(),
|
||||||
|
ntfy_typing_last: Mutex::new(None),
|
||||||
|
http_client: reqwest::Client::builder()
|
||||||
|
.tls_danger_accept_invalid_certs(true)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NTFY_USER_ID: u64 = 313015629659635713;
|
||||||
|
const NTFY_URL: &str = "https://ntfy.noisruker.de/up123456789TestTest";
|
||||||
|
const NTFY_TYPING_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
#[serenity::async_trait]
|
#[serenity::async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
|
async fn typing_start(&self, _ctx: Context, event: TypingStartEvent) {
|
||||||
|
if event
|
||||||
|
.member
|
||||||
|
.is_some_and(|m| m.user.id.get() == NTFY_USER_ID)
|
||||||
|
&& { self.ntfy_typing_last.lock() }
|
||||||
|
.unwrap()
|
||||||
|
.is_none_or(|i| i.elapsed() > NTFY_TYPING_TIMEOUT)
|
||||||
|
{
|
||||||
|
let res = self.http_client.post(NTFY_URL)
|
||||||
|
.header("Title", "Ich höre zu ...")
|
||||||
|
.header("Priority", "max")
|
||||||
|
.header("Tags", "ear")
|
||||||
|
.body("Du tippst gerade eine Nachricht auf Discord. Weiter so! Ich (wer auch immer ich sein mag) bin empfangsbereit.").send().await;
|
||||||
|
match res {
|
||||||
|
Ok(_) => info!("NTFY typing message sent"),
|
||||||
|
Err(error) => error!("NTFY error: {error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.ntfy_typing_last.lock().unwrap() = Some(Instant::now());
|
||||||
|
}
|
||||||
|
|
||||||
async fn message(&self, ctx: Context, msg: Message) {
|
async fn message(&self, ctx: Context, msg: Message) {
|
||||||
|
if msg.author.id.get() == NTFY_USER_ID {
|
||||||
|
let res = self
|
||||||
|
.http_client
|
||||||
|
.post(NTFY_URL)
|
||||||
|
.header("Title", "Ich habe deine Nachricht erhalten!")
|
||||||
|
.header("Priority", "max")
|
||||||
|
.header("Tags", "mailbox_with_mail")
|
||||||
|
.body(format!("Du hast geschrieben: {}", msg.content))
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
match res {
|
||||||
|
Ok(_) => info!("NTFY message message sent"),
|
||||||
|
Err(error) => error!("NTFY error: {error}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.ntfy_typing_last.lock().unwrap() = None;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(contest) = self.contests.get(&msg.channel_id) {
|
if let Some(contest) = self.contests.get(&msg.channel_id) {
|
||||||
debug!(
|
debug!(
|
||||||
"Counting message {} (from {} in channel {})",
|
"Counting message {} (from {} in channel {})",
|
||||||
msg.id,
|
msg.id,
|
||||||
msg.author.tag(),
|
msg.author.tag(),
|
||||||
msg.channel_id.0
|
msg.channel_id.get()
|
||||||
);
|
);
|
||||||
contest.value().send(msg).await.unwrap();
|
contest.value().send(msg).await.unwrap();
|
||||||
return;
|
return;
|
||||||
@@ -61,7 +117,7 @@ impl EventHandler for Handler {
|
|||||||
"User {} started a {} second contest in channel {}",
|
"User {} started a {} second contest in channel {}",
|
||||||
msg.author.tag(),
|
msg.author.tag(),
|
||||||
duration.as_secs(),
|
duration.as_secs(),
|
||||||
msg.channel_id.0
|
msg.channel_id.get()
|
||||||
);
|
);
|
||||||
|
|
||||||
match run_contest(
|
match run_contest(
|
||||||
@@ -75,20 +131,21 @@ impl EventHandler for Handler {
|
|||||||
{
|
{
|
||||||
Ok(contest) => debug!(
|
Ok(contest) => debug!(
|
||||||
"Contest in channel {} has ended with {} participant(s)",
|
"Contest in channel {} has ended with {} participant(s)",
|
||||||
msg.channel_id.0,
|
msg.channel_id.get(),
|
||||||
contest.counts.len()
|
contest.counts.len()
|
||||||
),
|
),
|
||||||
Err(err) => error!(
|
Err(err) => error!(
|
||||||
"Error while running contest in channel {}: {}",
|
"Error while running contest in channel {}: {}",
|
||||||
msg.channel_id.0, err
|
msg.channel_id.get(),
|
||||||
|
err
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
info!("Connected as {}", ready.user.tag());
|
info!("Connected as {} (troll)", ready.user.tag());
|
||||||
ctx.set_activity(Activity::listening("Spam")).await;
|
ctx.set_activity(Some(ActivityData::listening("Spam")));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resume(&self, _ctx: Context, _: ResumedEvent) {
|
async fn resume(&self, _ctx: Context, _: ResumedEvent) {
|
||||||
@@ -143,7 +200,7 @@ impl Contest {
|
|||||||
|
|
||||||
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| fk((*elt).1)) {
|
for (_, rank_group) in &ranking.into_iter().chunk_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(
|
||||||
@@ -164,20 +221,21 @@ async fn run_contest(
|
|||||||
contests: &Contests,
|
contests: &Contests,
|
||||||
pin: bool,
|
pin: bool,
|
||||||
) -> serenity::Result<Contest> {
|
) -> serenity::Result<Contest> {
|
||||||
let end_timestamp =
|
let end_timestamp = (OffsetDateTime::now_utc() + duration).unix_timestamp();
|
||||||
(chrono::Utc::now() + chrono::Duration::from_std(duration).unwrap()).timestamp() + 1;
|
|
||||||
|
|
||||||
// send announcement message
|
// send announcement message
|
||||||
let announcement = channel_id
|
let announcement = channel_id
|
||||||
.send_message(&ctx.http, |m| {
|
.send_message(
|
||||||
m.embed(|e| {
|
&ctx.http,
|
||||||
e.title("Es wurde ein Spam-Wettbewerb gestartet!")
|
CreateMessage::new().add_embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title("Es wurde ein Spam-Wettbewerb gestartet!")
|
||||||
.description(format!(
|
.description(format!(
|
||||||
"Wer am meisten spamt, gewinnt.\nEnde <t:{end_timestamp}:R>.",
|
"Wer am meisten spamt, gewinnt.\nEnde <t:{end_timestamp}:R>.",
|
||||||
))
|
))
|
||||||
.colour(Colour::BLUE)
|
.colour(Colour::BLUE),
|
||||||
})
|
),
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if pin {
|
if pin {
|
||||||
@@ -211,9 +269,11 @@ async fn run_contest(
|
|||||||
|
|
||||||
// send ranking message
|
// send ranking message
|
||||||
channel_id
|
channel_id
|
||||||
.send_message(&ctx.http, |m| {
|
.send_message(
|
||||||
m.embed(|e| {
|
&ctx.http,
|
||||||
e.title("Der Wettbewerb ist beendet!")
|
CreateMessage::new().add_embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title("Der Wettbewerb ist beendet!")
|
||||||
.colour(Colour::DARK_GREEN)
|
.colour(Colour::DARK_GREEN)
|
||||||
.field(
|
.field(
|
||||||
"Ergebnisse (nach Nachrichten):",
|
"Ergebnisse (nach Nachrichten):",
|
||||||
@@ -224,9 +284,9 @@ async fn run_contest(
|
|||||||
"Ergebnisse (nach Zeichen):",
|
"Ergebnisse (nach Zeichen):",
|
||||||
counts.ranking_by(|c| Reverse(c.characters), |c| c.characters),
|
counts.ranking_by(|c| Reverse(c.characters), |c| c.characters),
|
||||||
false,
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user