smol bot
search
bendn 2024-01-13
parent ec4f4aa · commit 6ecc3b0
-rw-r--r--Cargo.toml1
-rw-r--r--src/bot/mod.rs9
-rw-r--r--src/bot/search.rs161
3 files changed, 167 insertions, 4 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a28b874..6fc373e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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)
+}