smol bot
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/bot/mod.rs | 9 | ||||
| -rw-r--r-- | src/bot/search.rs | 161 |
3 files changed, 167 insertions, 4 deletions
@@ -32,6 +32,7 @@ oxipng = { version = "9.0.0", default-features = false } fimg = "0.4.26" phf = { version = "0.11.2", features = ["macros"] } emoji = { git = "https://github.com/Apricot-Conservation-Project/emoji", version = "0.1.0" } +rust-fuzzy-search = "0.1.1" [profile.release] strip = true diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 221779d..b1e672f 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,10 +1,10 @@ mod logic; mod map; mod schematic; +mod search; use anyhow::Result; use dashmap::DashMap; - use mindus::Serializable; use poise::serenity_prelude::*; use serenity::futures::StreamExt; @@ -159,7 +159,7 @@ impl Bot { std::env::var("TOKEN").unwrap_or_else(|_| read_to_string("token").expect("wher token")); let f: poise::Framework<Data, anyhow::Error> = poise::Framework::builder() .options(poise::FrameworkOptions { - commands: vec![logic::run(), help()], + commands: vec![logic::run(), help(), search::search(),search::find()], event_handler: |c, e, _, d| { Box::pin(async move { match e { @@ -312,9 +312,10 @@ impl Bot { }, ..Default::default() }) - .setup(|ctx, _ready, framework| { + .setup(|ctx, _ready, _| { Box::pin(async move { - poise::builtins::register_globally(ctx, &framework.options().commands).await?; + poise::builtins::register_globally(ctx, &[logic::run(), help()]).await?; + poise::builtins::register_in_guild(ctx, &[search::search(),search::find()], 925674713429184564.into()).await?; println!("registered"); let tracker = Arc::new(DashMap::new()); let tc = Arc::clone(&tracker); diff --git a/src/bot/search.rs b/src/bot/search.rs new file mode 100644 index 0000000..0a05dce --- /dev/null +++ b/src/bot/search.rs @@ -0,0 +1,161 @@ +use anyhow::Result; +use emoji::named::*; +use mindus::data::DataRead; +use mindus::{Schematic, Serializable}; +use poise::serenity_prelude::*; +use std::mem::MaybeUninit; +use std::path::Path; + +struct Dq<T, const N: usize> { + arr: [MaybeUninit<T>; N], + front: u8, + len: u8, +} + +impl<T: Copy, const N: usize> Dq<T, N> { + pub fn new(first: T) -> Self { + let mut dq = Dq { + arr: unsafe { MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init() }, + front: 0, + len: 1, + }; + dq.arr[0].write(first); + dq + } + + pub fn first(&mut self) -> T { + unsafe { self.arr.get_unchecked(self.front as usize).assume_init() } + } + + pub fn push_front(&mut self, elem: T) { + // sub 1 + match self.front { + 0 => self.front = N as u8 - 1, + n => self.front = n - 1, + } + self.len += 1; + unsafe { self.arr.get_unchecked_mut(self.front as usize).write(elem) }; + } + + pub fn iter(&self) -> impl Iterator<Item = T> + '_ { + self.arr + .iter() + .cycle() + .skip(self.front as _) + .take((self.len as usize).min(N)) + .map(|x| unsafe { x.assume_init() }) + } +} + +#[poise::command(slash_command)] +/// Find a schematic in the repo +pub async fn find( + c: super::Context<'_>, + #[description = "schematic name"] name: String, +) -> Result<()> { + let mut stack = Dq::<_, 5>::new(( + 0.0, + Data { + channel: 0, + message: 0, + }, + )); + c.defer().await?; + for (elem, data) in schems() { + let cmp = rust_fuzzy_search::fuzzy_compare(elem.tags.get("name").unwrap(), &name); + if stack.first().0 < cmp { + stack.push_front((cmp, data)); + } + } + if stack.iter().filter(|&(n, _)| n > 0.5).count() == 0 { + return c + .say(format!("{CANCEL} not found")) + .await + .map(|_| ()) + .map_err(Into::into); + } + c.say( + stack + .iter() + .filter(|&(n, _)| n > 0.5) + .map(|(_, Data { channel, message })| { + format!( + "{RIGHT} https://discord.com/channels/925674713429184564/{channel}/{message}" + ) + }) + .intersperse("\n".to_string()) + .fold(String::new(), |acc, x| acc + &x), + ) + .await + .map(|_| ()) + .map_err(Into::into) +} + +#[derive(Copy, Clone)] +pub struct Data { + channel: u64, + message: u64, +} + +pub fn schems() -> impl Iterator<Item = (Schematic, Data)> { + super::SPECIAL + .entries() + .filter_map(|(&ch, &dir)| { + std::fs::read_dir(Path::new("repo").join(dir)) + .ok() + .map(|x| (x, ch)) + }) + .map(|(fs, channel)| { + fs.filter_map(Result::ok).map(move |f| { + let dat = std::fs::read(f.path()).unwrap(); + let mut dat = DataRead::new(&dat); + let ts = Schematic::deserialize(&mut dat).unwrap(); + let p = f.path(); + let x = p.file_name().unwrap().to_string_lossy(); + ( + ts, + Data { + channel, + message: (u64::from_str_radix(&x[..x.len() - 5], 16).unwrap()), + }, + ) + }) + }) + .flatten() +} + +#[poise::command(slash_command)] +/// Search for a schematic in the repo +pub async fn search( + c: super::Context<'_>, + #[description = "base64 of the schematic"] base64: Option<String>, + #[description = "msch of the schematic"] msch: Option<Attachment>, +) -> Result<()> { + let s = match base64 + .and_then(|s| Schematic::deserialize_base64(&s).ok()) + .or(match msch { + Some(x) => x.download().await.ok().and_then(|x| { + let mut s = DataRead::new(&x); + Schematic::deserialize(&mut s).ok() + }), + None => None, + }) { + Some(x) => x, + None => return c.say("no schematic").await.map(|_| ()).map_err(Into::into), + }; + c.defer().await?; + + if let Some((_, Data { channel, message })) = schems().find(|(ts, _)| &s == ts) { + return c + .say(format!( + "{RIGHT} https://discord.com/channels/925674713429184564/{channel}/{message}", + )) + .await + .map(|_| ()) + .map_err(Into::into); + } + c.say(format!("{CANCEL} not found")) + .await + .map(|_| ()) + .map_err(Into::into) +} |