guzzles data
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs186
1 files changed, 131 insertions, 55 deletions
diff --git a/src/main.rs b/src/main.rs
index e89fb21..0567273 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,6 +6,8 @@ use serenity::futures::StreamExt;
use std::fs::read_to_string;
use std::io::Write;
use std::ops::{Deref, Sub};
+use std::sync::Arc;
+use tokio::sync::Mutex;
type Context<'a> = poise::Context<'a, (), anyhow::Error>;
#[derive(poise::ChoiceParameter, Default, Copy, Clone, Debug)]
@@ -21,6 +23,7 @@ enum Period {
LastWeek,
}
+#[derive(Copy, Clone, Debug)]
struct Days(usize);
impl Deref for Days {
@@ -66,17 +69,121 @@ impl Period {
Self::LastWeek => Days::now() - 7,
}
}
-
- fn tic(self, t: Days) -> bool {
+ fn tic(self, t: Days) -> String {
match self {
- Self::LastYear | Self::AllTime => t.t().day() == 1,
- Self::LastMonth => t.t().day() % 7 == 0,
- Self::LastWeek => true,
+ Period::AllTime if t.t().day() == 1 => t.t().format("%m/%y/%d").to_string(),
+ Period::LastYear if t.t().day() == 1 => t.t().format("%B %d").to_string(),
+ Period::LastMonth if t.t().day() % 7 == 0 => t.t().format("%B %d").to_string(),
+ Period::LastWeek => t.t().format("%A").to_string(),
+ _ => "".to_string(),
}
}
}
#[poise::command(slash_command)]
+/// graph of messages over the last 4 days
+pub async fn msgs(c: Context<'_>) -> Result<()> {
+ c.defer().await?;
+ let (min, cs) = {
+ let Some(g) = c.guild() else {
+ _ = c.reply(format!("{CANCEL} need guild"));
+ return Ok(());
+ };
+ (
+ Days::from(g.id.created_at().timestamp() as usize),
+ g.channels.values().map(|x| x.id).collect::<Vec<_>>(),
+ )
+ };
+
+ let data: Arc<Mutex<Vec<u16>>> = Arc::new(Mutex::new(vec![0; *Days::now() + 1]));
+ let cutoff = Days::now() - 3;
+ futures::stream::iter(cs)
+ .map(|x| {
+ let data = data.clone();
+ async move {
+ let mut c = std::pin::pin!(x.messages_iter(c));
+
+ while let Some(Ok(x)) = c.next().await {
+ if x.author.bot {
+ continue;
+ }
+ let x = x.timestamp;
+ let d = *Days::from(x.timestamp() as usize);
+ if d < *cutoff {
+ break;
+ }
+ data.lock().await[d] += 1;
+ }
+ }
+ })
+ .buffer_unordered(32)
+ .for_each(|_| async {})
+ .await;
+ let min = min.max(*cutoff);
+ let i = c.id();
+ let data = data.lock().await;
+ let mut f = std::fs::File::create(format!("{i}.dat")).unwrap();
+ for (i, &d) in data[min..].iter().enumerate() {
+ writeln!(&mut f, r"{},{d}", Period::LastWeek.tic(Days(i + min))).unwrap();
+ }
+ let plot = if cfg!(debug_assertions) {
+ std::fs::read_to_string("y.plot").unwrap()
+ } else {
+ include_str!("../y.plot").to_string()
+ };
+ let cleanup = tokio::task::spawn_blocking(move || run(plot, &[], i))
+ .await
+ .unwrap();
+
+ poise::send_reply(
+ c,
+ poise::CreateReply::default()
+ .attachment(CreateAttachment::path(format!("{i}.png")).await.unwrap()),
+ )
+ .await?;
+ tokio::task::spawn_blocking(cleanup).await.unwrap();
+ Ok(())
+}
+
+fn run(
+ mut plot: String,
+ templates: &[(&'static str, &dyn std::fmt::Display)],
+ i: u64,
+) -> impl FnOnce() -> () {
+ for (key, value) in templates {
+ plot = plot.replace(&format!("{{{key}}}"), &value.to_string())
+ }
+ let plot = plot.replace("{id}", &i.to_string());
+ let path = format!("{i}.plot");
+ std::fs::File::create_new(&path)
+ .unwrap()
+ .write_all(plot.as_bytes())
+ .unwrap();
+
+ assert!(std::process::Command::new("gnuplot")
+ .arg(&path)
+ .spawn()
+ .unwrap()
+ .wait()
+ .unwrap()
+ .success());
+ assert!(std::process::Command::new("inkscape")
+ .arg(format!("--export-filename={i}.png"))
+ .arg(format!("{i}.svg"))
+ .spawn()
+ .unwrap()
+ .wait()
+ .unwrap()
+ .success());
+ move || {
+ std::fs::remove_file(format!("{i}.dat")).unwrap();
+ std::fs::remove_file(path).unwrap();
+ std::fs::remove_file(format!("{i}.svg")).unwrap();
+ std::fs::remove_file(format!("{i}.png")).unwrap();
+ }
+}
+
+#[poise::command(slash_command)]
/// graph of users over time
pub async fn users(
c: Context<'_>,
@@ -92,13 +199,12 @@ pub async fn users(
g.id
};
let mut s = std::pin::pin!(g.members_iter(c));
- let mut data: Vec<u16> = vec![0; 365 * 12];
- let mut min = 365 * 12;
+ let mut data: Vec<u16> = vec![0; *Days::now() + 1];
+ let min = Days::from(g.created_at().timestamp() as usize);
let mut max = 0;
while let Some(x) = s.next().await {
let x = x?.joined_at.unwrap();
let d = *Days::from(x.timestamp() as usize);
- min = min.min(d);
max = max.max(d);
data[d] += 1;
}
@@ -110,16 +216,7 @@ pub async fn users(
let floor = sum;
for (i, &d) in data[min..max].iter().enumerate() {
sum += d as u64;
- writeln!(
- &mut f,
- r"{},{sum}",
- if p.tic(Days(i + min)) {
- Days(i + min).t().format("%m/%d/%y").to_string()
- } else {
- "".to_string()
- }
- )
- .unwrap();
+ writeln!(&mut f, r"{},{sum}", p.tic(Days(i + min))).unwrap();
}
let plot = if cfg!(debug_assertions) {
std::fs::read_to_string("x.plot").unwrap()
@@ -127,50 +224,29 @@ pub async fn users(
include_str!("../x.plot").to_string()
};
- let plot = plot
- .replace(
- "{floor}",
- &match p {
- Period::AllTime => "".to_string(),
- _ => floor.to_string(),
- },
+ let cleanup = tokio::task::spawn_blocking(move || {
+ run(
+ plot,
+ &[(
+ "floor",
+ &match p {
+ Period::AllTime => "".to_string(),
+ _ => floor.to_string(),
+ },
+ )],
+ i,
)
- .replace("{id}", &i.to_string());
- let path = format!("{i}.plot");
- std::fs::File::create_new(&path)
- .unwrap()
- .write_all(plot.as_bytes())
- .unwrap();
+ })
+ .await
+ .unwrap();
- assert!(std::process::Command::new("gnuplot")
- .arg(&path)
- .spawn()
- .unwrap()
- .wait()
- .unwrap()
- .success());
- assert!(std::process::Command::new("inkscape")
- .arg(format!("--export-filename={i}.png"))
- .arg(format!("{i}.svg"))
- .spawn()
- .unwrap()
- .wait()
- .unwrap()
- .success());
poise::send_reply(
c,
poise::CreateReply::default()
.attachment(CreateAttachment::path(format!("{i}.png")).await.unwrap()),
)
.await?;
- tokio::task::spawn_blocking(move || {
- std::fs::remove_file(format!("{i}.dat")).unwrap();
- std::fs::remove_file(path).unwrap();
- std::fs::remove_file(format!("{i}.svg")).unwrap();
- std::fs::remove_file(format!("{i}.png")).unwrap();
- })
- .await
- .unwrap();
+ tokio::task::spawn_blocking(cleanup).await.unwrap();
Ok(())
}
@@ -180,7 +256,7 @@ async fn main() {
std::env::var("TOKEN").unwrap_or_else(|_| read_to_string("token").expect("wher token"));
let f = poise::Framework::builder()
.options(poise::FrameworkOptions {
- commands: vec![users()],
+ commands: vec![users(), msgs()],
..Default::default()
})
.setup(|ctx, _ready, f| {