smol bot
Diffstat (limited to 'src/bot/map.rs')
-rw-r--r--src/bot/map.rs136
1 files changed, 96 insertions, 40 deletions
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(())
}