smol bot
some changes
bendn 8 months ago
parent 3c863cc · commit 3789dd3
-rw-r--r--Cargo.toml9
-rw-r--r--build.rs6
-rw-r--r--html-src/index.html15
-rw-r--r--src/bot/map.rs136
-rw-r--r--src/bot/mod.rs34
-rw-r--r--src/bot/sorter.rs13
-rw-r--r--src/bot/usage.md1
-rw-r--r--src/main.rs1
8 files changed, 151 insertions, 64 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 2722fe8..4192b6e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "plent"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
[dependencies]
paste = "1.0.12"
@@ -19,7 +19,7 @@ serenity = { version = "0.12", features = [
"gateway",
"model",
], default-features = false }
-poise = { git = "https://github.com/fgardt/poise", branch = "feat/user_apps" }
+poise = { git = "https://github.com/serenity-rs/poise", branch = "current" }
anyhow = "1.0.75"
regex = { version = "1.8.4", features = ["std"], default-features = false }
mindus = { version = "5.0.7", features = [], default-features = false }
@@ -62,6 +62,7 @@ kv = "0.24.0"
sled = { version = "0.34.7", features = ["compression"] }
remapper = { version = "0.1.0", path = "../remapper" }
implicit-fn = "0.1.0"
+sql = "0.4.3"
[features]
server = ["axum"]
@@ -72,9 +73,9 @@ emojib = { git = "https://github.com/Apricot-Conservation-Project/emoji", featur
], package = "emoji" }
[profile.release]
-# strip = true
+strip = true
lto = "thin"
-debug = 2
+# debug = 2
[profile.dev.package.mindus]
opt-level = 3
diff --git a/build.rs b/build.rs
index 63b9d11..3411d91 100644
--- a/build.rs
+++ b/build.rs
@@ -35,9 +35,9 @@ fn main() -> std::io::Result<()> {
emojib::load();
- for path in fs::read_dir("html-src")? {
- process(path.unwrap().path().file_name().unwrap())?;
- }
+ // for path in fs::read_dir("html-src")? {
+ // process(path.unwrap().path().file_name().unwrap())?;
+ // }
println!("cargo:rerun-if-changed=html-src/");
println!("cargo:rerun-if-changed=build.rs");
Ok(())
diff --git a/html-src/index.html b/html-src/index.html
index 4befc53..653b49a 100644
--- a/html-src/index.html
+++ b/html-src/index.html
@@ -2,6 +2,16 @@
<html lang="en">
<head>
+ <!-- Google tag (gtag.js) -->
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-M97NB6WZZF"></script>
+ <script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag() { dataLayer.push(arguments); }
+ gtag('js', new Date());
+
+ gtag('config', 'G-M97NB6WZZF');
+ </script>
+
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="https://apricotalliance.org/schems/favicon.ico" />
@@ -203,6 +213,11 @@
</div>
</div>
+ <a target="_blank" href="https://discord.gg/SsDYC4kKbb" class="link"><img class="link"
+ src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MCIgaGVpZ2h0PSI3MCIgdmlld0JveD0iMCAwIDE3NSAxNzUiPjxjaXJjbGUgY3g9Ijg3LjUiIGN5PSI4Ny41IiByPSI4Ny41IiBzdHlsZT0iZmlsbDojM2UzYjNiO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDoyOC4zNDY1O3N0cm9rZS1saW5lam9pbjpiZXZlbDtmaWxsLW9wYWNpdHk6MSIvPjxwYXRoIGQ9Ik0xMzEuNjMgNDcuMzlhMTA1LjE1IDEwNS4xNSAwIDAgMC0yNi4yMy04LjA3IDcyLjA2IDcyLjA2IDAgMCAwLTMuMzYgNi44MyA5Ny42OCA5Ny42OCAwIDAgMC0yOS4xMSAwIDcyLjM3IDcyLjM3IDAgMCAwLTMuMzYtNi44MyAxMDUuODkgMTA1Ljg5IDAgMCAwLTI2LjI1IDguMDljLTE2LjYgMjQuNTYtMjEuMSA0OC41MS0xOC44NSA3Mi4xMmExMDUuNzMgMTA1LjczIDAgMCAwIDMyLjE3IDE2LjE1IDc3LjcgNzcuNyAwIDAgMCA2Ljg5LTExLjExIDY4LjQyIDY4LjQyIDAgMCAxLTEwLjg1LTUuMThjLjkxLS42NiAxLjgtMS4zNCAyLjY2LTJhNzUuNTcgNzUuNTcgMCAwIDAgNjQuMzIgMGMuODcuNzEgMS43NiAxLjM5IDIuNjYgMmE2OC42OCA2OC42OCAwIDAgMS0xMC44NyA1LjE5IDc3IDc3IDAgMCAwIDYuODkgMTEuMSAxMDUuMjUgMTA1LjI1IDAgMCAwIDMyLjE5LTE2LjE0YzIuNjQtMjcuMzgtNC41MS01MS4xMS0xOC45LTcyLjE1em0tNjUuMjUgNTcuNjJjLTYuMjcgMC0xMS40NS01LjY5LTExLjQ1LTEyLjY5IDAtNyA1LTEyLjc0IDExLjQzLTEyLjc0czExLjU3IDUuNzQgMTEuNDYgMTIuNzRjLS4xMSA3LTUuMDUgMTIuNjktMTEuNDQgMTIuNjl6bTQyLjI0IDBjLTYuMjggMC0xMS40NC01LjY5LTExLjQ0LTEyLjY5IDAtNyA1LTEyLjc0IDExLjQ0LTEyLjc0IDYuNDQgMCAxMS41NCA1Ljc0IDExLjQzIDEyLjc0LS4xMSA3LTUuMDQgMTIuNjktMTEuNDMgMTIuNjl6IiBzdHlsZT0iZmlsbDojNTg2NWYyIi8+PC9zdmc+"
+ alt="discord">
+ </a>
+
<div id="grid">
<script type="module" src="/schems/index.js"></script>
<script>
diff --git a/src/bot/map.rs b/src/bot/map.rs
index 76023e0..c790eae 100644
--- a/src/bot/map.rs
+++ b/src/bot/map.rs
@@ -1,12 +1,13 @@
use anyhow::Result;
use mindus::{data::map::ReadError, *};
-use poise::{serenity_prelude::*, CreateReply};
+use poise::{CreateReply, serenity_prelude::*};
use std::{
ops::ControlFlow,
time::{Duration, Instant},
};
-
-use super::{strip_colors, SUCCESS};
+use tokio::task::JoinError;
+const BENDN: ChannelId = ChannelId::new(1149866218057117747);
+use super::{SUCCESS, strip_colors};
fn string((x, f): (ReadError, &str)) -> String {
match x {
@@ -30,18 +31,24 @@ fn string((x, f): (ReadError, &str)) -> String {
}
}
-pub async fn download(a: &Attachment) -> Result<(Result<Map, (ReadError, &str)>, Duration)> {
- let s = a.download().await?;
+pub async fn download(
+ a: &Attachment,
+) -> Result<(Result<(Map, Box<[u8]>), (ReadError, &str)>, Duration)> {
+ let s = a.download().await?.into_boxed_slice();
let then = Instant::now();
// could ignore, but i think if you have a msav, you dont want to ignore failures.
Ok((
- Map::deserialize(&mut mindus::data::DataRead::new(&s)).map_err(|x| (x, &*a.filename)),
+ Map::deserialize(&mut mindus::data::DataRead::new(&s))
+ .map_err(|x| (x, &*a.filename))
+ .map(|x| (x, s)),
then.elapsed(),
))
}
-pub async fn scour(m: &Message) -> Result<Option<(Result<Map, (ReadError, &str)>, Duration)>> {
+pub async fn scour(
+ m: &Message,
+) -> Result<Option<(Result<(Map, Box<[u8]>), (ReadError, &str)>, Duration)>> {
for a in &m.attachments {
if a.filename.ends_with("msav") {
return Ok(Some(download(a).await?));
@@ -50,12 +57,27 @@ pub async fn scour(m: &Message) -> Result<Option<(Result<Map, (ReadError, &str)>
Ok(None)
}
-pub async fn reply(a: &Attachment) -> Result<ControlFlow<CreateReply, String>> {
- let (m, deser_took) = match download(a).await? {
+pub async fn reply(
+ c: super::Context<'_>,
+ a: &Attachment,
+) -> Result<ControlFlow<CreateReply, String>> {
+ let ((m, b), deser_took) = match download(a).await? {
(Err(e), _) => return Ok(ControlFlow::Continue(string(e))),
(Ok(m), deser_took) => (m, deser_took),
};
- let (a, e) = embed(m, deser_took).await;
+ let (a, e) = match embed(m, deser_took).await {
+ Ok(x) => x,
+ Err(e) => {
+ BENDN.send_files(
+ &c,
+ [CreateAttachment::bytes(b, "map.msav")],
+ CreateMessage::new().content(format!("<@696196765564534825> failure: {e}")),
+ );
+ return Ok(ControlFlow::Break(CreateReply::default().content(
+ "there was a problem. i have notified bendn about this issue.",
+ )));
+ }
+ };
Ok(ControlFlow::Break(
CreateReply::default().attachment(a).embed(e),
))
@@ -67,33 +89,29 @@ struct Timings {
compression_took: Duration,
total: Duration,
}
-async fn render(m: Map, deser_took: Duration) -> (Timings, Vec<u8>) {
- tokio::task::spawn_blocking(move || {
- let render_took = Instant::now();
- let i = m.render();
- let render_took = render_took.elapsed();
- let compression_took = Instant::now();
- let png = super::png(i);
- let compression_took = compression_took.elapsed();
- let total = deser_took + render_took + compression_took;
- (
- Timings {
- deser_took,
- render_took,
- compression_took,
- total,
- },
- png,
- )
- })
- .await
- .unwrap()
+fn render(m: Map, deser_took: Duration) -> (Timings, Vec<u8>) {
+ let render_took = Instant::now();
+ let i = m.render();
+ let render_took = render_took.elapsed();
+ let compression_took = Instant::now();
+ let png = super::png(i);
+ let compression_took = compression_took.elapsed();
+ let total = deser_took + render_took + compression_took;
+ (
+ Timings {
+ deser_took,
+ render_took,
+ compression_took,
+ total,
+ },
+ png,
+ )
}
pub async fn find(
msg: &Message,
c: &serenity::client::Context,
-) -> Result<Option<(String, Map, Duration)>> {
+) -> Result<Option<(String, (Map, Box<[u8]>), Duration)>> {
match scour(msg).await? {
None => Ok(None),
Some((Err(e), _)) => {
@@ -108,12 +126,37 @@ pub async fn find(
}
}
+#[implicit_fn::implicit_fn]
pub async fn with(msg: &Message, c: &serenity::client::Context) -> Result<()> {
- let Some((_auth, m, deser_took)) = find(msg, c).await? else {
+ let Some((_auth, (m, b), deser_took)) = find(msg, c).await? else {
return Ok(());
};
let t = msg.channel_id.start_typing(&c.http);
- let (png, embed) = embed(m, deser_took).await;
+ let (png, embed) = match embed(m, deser_took).await {
+ Ok(x) => x,
+ Err(e) => {
+ use crate::emoji::named::*;
+
+ BENDN
+ .send_files(
+ &c,
+ [CreateAttachment::bytes(b, "file.msch")],
+ CreateMessage::new().content(format!(
+ "<@696196765564534825> panic `{e}` in {}\n",
+ msg.guild(&c.cache).map_or(
+ msg.guild_id
+ .map(_.get().to_string())
+ .unwrap_or(format!("dms with {}", msg.author.name)),
+ _.name.clone(),
+ )
+ )),
+ )
+ .await?;
+ msg.reply(c, format!("{CANCEL} there was an error while rendering this map.\nthis issue has been reported to bendn, who will hopefully take a look eventually."))
+ .await?;
+ return Ok(());
+ }
+ };
t.stop();
super::data::push_j(serde_json::json! {{
"locale": msg.author.locale.as_deref().unwrap_or("no locale"),
@@ -129,7 +172,7 @@ pub async fn with(msg: &Message, c: &serenity::client::Context) -> Result<()> {
Ok(())
}
-async fn embed(m: Map, deser_took: Duration) -> (CreateAttachment, CreateEmbed) {
+async fn embed(m: Map, deser_took: Duration) -> Result<(CreateAttachment, CreateEmbed), JoinError> {
let name = strip_colors(m.tags.get("name").or(m.tags.get("mapname")).unwrap());
let d = strip_colors(m.tags.get("description").map(|x| &**x).unwrap_or("?"));
let f = if m.width == m.height {
@@ -137,8 +180,8 @@ async fn embed(m: Map, deser_took: Duration) -> (CreateAttachment, CreateEmbed)
} else {
format!("{}×{}", m.height, m.width)
};
- let (timings, png) = render(m, deser_took).await;
- (
+ let (timings, png) = tokio::task::spawn_blocking(move || render(m, deser_took)).await?;
+ Ok((
CreateAttachment::bytes(png, "map.png"),
CreateEmbed::new()
.title(&name)
@@ -149,7 +192,7 @@ async fn embed(m: Map, deser_took: Duration) -> (CreateAttachment, CreateEmbed)
)))
.attachment("map.png")
.color(SUCCESS),
- )
+ ))
}
#[poise::command(
@@ -160,11 +203,24 @@ async fn embed(m: Map, deser_took: Duration) -> (CreateAttachment, CreateEmbed)
/// Renders map inside a message.
pub async fn render_message(c: super::Context<'_>, m: Message) -> Result<()> {
super::log(&c);
- let Some((_auth, m, deser_took)) = find(&m, c.serenity_context()).await? else {
+ let Some((_auth, (m, b), deser_took)) = find(&m, c.serenity_context()).await? else {
poise::say_reply(c, "no map").await?;
return Ok(());
};
- let (png, embed) = embed(m, deser_took).await;
+ let (png, embed) = match embed(m, deser_took).await {
+ Ok(x) => x,
+ Err(e) => {
+ BENDN.send_files(
+ &c,
+ [CreateAttachment::bytes(b, "map.msav")],
+ CreateMessage::new().content(format!("<@696196765564534825> failure: {e}")),
+ );
+ c.say("there was a problem. i have notified bendn about this issue.")
+ .await?;
+ return Ok(());
+ }
+ };
+
poise::send_reply(c, CreateReply::default().attachment(png).embed(embed)).await?;
Ok(())
}
diff --git a/src/bot/mod.rs b/src/bot/mod.rs
index 1044e87..519b552 100644
--- a/src/bot/mod.rs
+++ b/src/bot/mod.rs
@@ -188,10 +188,10 @@ async fn del(c: &serenity::prelude::Context,& Ch{ d:dir, repo: git, ..}: &Ch, de
static HOOK: OnceLock<Webhook> = OnceLock::new();
pub async fn hookup(c: &impl AsRef<Http>) {
- let v = Webhook::from_url(c, {
+ let v = Webhook::from_url(c,
&std::env::var("WEBHOOK")
.unwrap_or_else(|_| read_to_string("webhook").expect("wher webhook"))
- })
+ )
.await
.unwrap();
HOOK.get_or_init(|| v);
@@ -229,6 +229,18 @@ async fn handle_message(
.unwrap_or(new_message.author.name.clone());
let post = EXTRA.get(&new_message.channel_id.get()).map(|x| x.clone());
let (dir, l, repo) = sep(SPECIAL.get(&new_message.channel_id.get()).or(post.as_ref()));
+ if new_message.author.id.get()==OWNER&&new_message.content.contains("meow"){
+ new_message.author
+ .direct_message(
+ c,
+ poise::serenity_prelude::CreateMessage::new().content(format!(
+ "your match `meow` was satisfied on message ```\n{}\n``` {}",
+ new_message.content.replace('`', "​`"),
+ new_message.link()
+ )),
+ )
+ .await?;
+ }
let m = Msg {
author: who.clone(),
locale: new_message.author.locale.clone().unwrap_or("unknown locale".to_string()),
@@ -318,6 +330,7 @@ impl Bot {
FullEvent::Ready { .. } => {
println!("bot ready");
while SEEN.lock().await.len() < 5 {
+
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
}
let mut x = SEEN.lock().await.clone().into_iter().collect::<Vec<_>>();
@@ -868,6 +881,10 @@ pub fn png(p: fimg::Image<Vec<u8>, 3>) -> Vec<u8> {
)]
/// Pong!
pub async fn ping(c: Context<'_>) -> Result<()> {
+ // let p = Timestamp::now()
+ // .signed_duration_since(*c.created_at())
+ // .to_std()?
+ // .as_millis() as _;
log(&c);
use emoji::named::*;
let m = memory_stats::memory_stats().unwrap().physical_mem as f32 / (1 << 20) as f32;
@@ -881,13 +898,8 @@ pub async fn ping(c: Context<'_>) -> Result<()> {
// let m = (m / 0.1) + 0.5;
// let m = m.floor() * 0.1;
c.reply(format!(
- "pong!\n{DISCORD}{RIGHT}: {} — {HOST}: {m:.1}MiB - <:stopwatch:1361892467510870167><:world_processor:1307657404128690268> {util:.0}% — <:up:1307658579251167302><:time:1361892343199957022> {}",
- humantime::format_duration(Duration::from_millis(
- Timestamp::now()
- .signed_duration_since(*c.created_at())
- .to_std()?
- .as_millis() as _
- )),
+ "pong!\n{DISCORD}{RIGHT}: {}ms — {HOST}: {m:.1}MiB - <:stopwatch:1361892467510870167><:world_processor:1307657404128690268> {util:.0}% — <:up:1307658579251167302><:time:1361892343199957022> {}",
+ c.ping().await.as_millis(),
humantime::format_duration(Duration::from_secs(
Instant::now()
.duration_since(*super::START.get().unwrap())
@@ -937,7 +949,7 @@ pub async fn render_file(
_ = c.defer().await;
let Some(s) = schematic::from_attachments(std::slice::from_ref(&s)).await? else {
- match map::reply(&s).await? {
+ match map::reply(c, &s).await? {
ControlFlow::Break(x) => return Ok(drop(poise::send_reply(c, x).await?)),
ControlFlow::Continue(e) if e != "not a map." => {
return Ok(drop(poise::say_reply(c, e).await?))
@@ -1048,4 +1060,4 @@ pub async fn schembrowser_instructions(c: Context<'_>) -> Result<()> {
)
.await?;
Ok(())
-}
+} \ No newline at end of file
diff --git a/src/bot/sorter.rs b/src/bot/sorter.rs
index 8826b97..59617ec 100644
--- a/src/bot/sorter.rs
+++ b/src/bot/sorter.rs
@@ -1,9 +1,9 @@
use anyhow::Result;
use atools::prelude::*;
use block::SORTER;
-use fimg::{indexed::IndexedImage, Image};
+use fimg::{Image, indexed::IndexedImage};
use mindus::*;
-use poise::{serenity_prelude::*, ChoiceParameter};
+use poise::{ChoiceParameter, serenity_prelude::*};
use remapper::pal;
#[derive(ChoiceParameter)]
@@ -92,14 +92,15 @@ fn sort(
let (width, height) = (x.width() as usize, x.height() as usize);
let mut s = Schematic::new(width, height);
+ let q = unsafe { quant.raw() };
let pixels = (0..width)
.flat_map(|x_| (0..height).map(move |y| (x_, y)))
- .filter_map(|(x_, y_)| {
- match unsafe { quant.raw().buffer() }[(height - y_ - 1) * width + x_] {
+ .filter_map(
+ |(x_, y_)| match q.buffer()[(height - y_ - 1) * width + x_] {
0 => None,
x => Some(((x_, y_), x - 1)),
- }
- });
+ },
+ );
for ((x, y), i) in pixels {
s.set(
x,
diff --git a/src/bot/usage.md b/src/bot/usage.md
index f091955..31b348f 100644
--- a/src/bot/usage.md
+++ b/src/bot/usage.md
@@ -9,5 +9,6 @@ commands:
- `eval`: executes mlog. see `/help eval` for more info.
- `sorter`: creates sorter representations of images.
+- `mapper`: creates map representations of images.
bugs to be reported [here](https://github.com/bend-n/mindus/issues/new), or ping <@696196765564534825>.
diff --git a/src/main.rs b/src/main.rs
index a3d36be..cd236b7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
#![allow(incomplete_features)]
#![feature(
+ try_blocks,
let_chains,
generic_const_exprs,
iter_intersperse,