smol bot
Diffstat (limited to 'src/bot/mod.rs')
-rw-r--r--src/bot/mod.rs282
1 files changed, 167 insertions, 115 deletions
diff --git a/src/bot/mod.rs b/src/bot/mod.rs
index 1a7ccd8..a1854f2 100644
--- a/src/bot/mod.rs
+++ b/src/bot/mod.rs
@@ -1,18 +1,18 @@
mod logic;
mod map;
pub mod ownership;
-mod repos;
+pub mod repos;
mod schematic;
pub mod search;
mod sorter;
+mod db;
use crate::emoji;
use anyhow::Result;
use dashmap::DashMap;
-use mindus::data::DataWrite;
use mindus::Serializable;
use poise::{serenity_prelude::*, CreateReply};
-use repos::{Repo, REPOS, SPECIAL, THREADED};
+use repos::{Repo, FORUMS, SPECIAL, THREADED};
use serenity::futures::StreamExt;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
@@ -24,16 +24,16 @@ use std::sync::{Arc, LazyLock, OnceLock};
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
-fn clone() {
- for (k, repos::Repo { auth, .. }) in repos::REPOS.entries() {
- if !Path::new(&format!("repos/{k:x}")).exists() {
+pub fn clone() {
+ for repos::Repo { auth, name, .. } in repos::ALL {
+ if !Path::new(&format!("repos/{name}")).exists() {
assert_eq!(
std::process::Command::new("git")
.current_dir("repos")
.arg("clone")
.args(["--depth", "5"])
.arg(auth)
- .arg(format!("{k:x}"))
+ .arg(format!("{name}"))
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
@@ -71,7 +71,7 @@ const SUCCESS: (u8, u8, u8) = (34, 139, 34);
const PFX: char = '}';
-fn tags(t: &[&str]) -> String {
+fn tags<T: std::fmt::Display>(t: &[T]) -> String {
if let [x, rest @ ..] = t {
let mut s = format!("[\"{x}\"");
for elem in rest {
@@ -84,18 +84,28 @@ fn tags(t: &[&str]) -> String {
}
}
-#[derive(Copy, Clone, Debug)]
+#[derive(Clone, Debug)]
pub struct Ch {
- repo: u64,
+ repo: &'static Repo,
d: &'static str,
- labels: &'static [&'static str],
+ ty: Type,
+}
+#[derive(Clone, Debug)]
+enum Type {
+ Basic(&'static [&'static str]),
+ Assembled(String),
+ Forum(()), // &'static phf::Map<&'static str, &'static [&'static str]>
}
fn sep(x: Option<&Ch>) -> (Option<&'static str>, Option<String>, Option<&Repo>) {
(
x.map(|x| x.d),
- x.map(|x| tags(x.labels)),
- x.map(|x| &REPOS[&x.repo]),
+ x.and_then(|x| match x.ty.clone() {
+ Type::Basic(x) => Some(tags(x)),
+ Type::Forum(_) => None,
+ Type::Assembled(x) => Some(x),
+ }),
+ x.map(|x| x.repo),
)
}
@@ -107,30 +117,35 @@ pub async fn scour(
c: Context<'_>,
#[description = "the channel in question"] ch: ChannelId,
) -> Result<()> {
- let g = c.guild_id().unwrap();
let repo = repos::chief!(c);
let mut n = 0;
let d = SPECIAL[&ch.get()].d;
let h = c.say(format!("scouring {d}...")).await?;
- _ = std::fs::create_dir(format!("repos/{:x}/{d}", repo.id));
- let mut msgs = ch.messages_iter(c).boxed();
- while let Some(msg) = msgs.next().await {
- let Ok(msg) = msg else {
- continue;
- };
- let (_, Some(tags), _) = sep(SPECIAL.get(&ch.get())) else {
+ _ = std::fs::create_dir(format!("repos/{}/{d}", repo.name));
+ let Ch { d, repo, ty, .. } = SPECIAL.get(&ch.get()).unwrap();
+ match ty {
+ Type::Basic(tg) => {
+ let mut msgs = ch.messages_iter(c).boxed();
+ let tags = tags(tg);
+ while let Some(msg) = msgs.next().await {
+ let Ok(msg) = msg else {
+ continue;
+ };
+ if let Ok(Some(mut x)) = schematic::from((&msg.content, &msg.attachments)).await {
+ x.schem.tags.insert("labels".into(), tags.clone());
+ let who = msg.author_nick(c).await.unwrap_or(msg.author.name.clone());
+ ownership::get(repo)
+ .await
+ .insert(msg.id.get(), (msg.author.name.clone(), msg.author.id.get()));
+ repo.write(d, msg.id, x);
+ repo.commit(&who, &format!("add {:x}.msch", msg.id.get()));
+ msg.react(c, emojis::get!(MERGE)).await?;
+ n += 1;
+ }
+ }
+ }
+ _ => {
unreachable!()
- };
- if let Ok(Some(mut x)) = schematic::from((&msg.content, &msg.attachments)).await {
- x.schem.tags.insert("labels".into(), tags);
- let who = msg.author_nick(c).await.unwrap_or(msg.author.name.clone());
- ownership::get(g)
- .await
- .insert(msg.id.get(), (msg.author.name.clone(), msg.author.id.get()));
- repo.write(d, msg.id, x);
- repo.commit(&who, &format!("add {:x}.msch", msg.id.get()));
- msg.react(c, emojis::get!(MERGE)).await?;
- n += 1;
}
}
repo.push();
@@ -144,6 +159,25 @@ pub async fn scour(
Ok(())
}
+async fn del(c: &serenity::prelude::Context,& Ch{ d:dir, repo: git, ..}: &Ch, deleted_message_id: u64) {
+ use crate::emoji::named::*;
+ if let Ok(s) = git.schem(dir, deleted_message_id.into()){
+ let own = git.own().await.erase(deleted_message_id).unwrap();
+ git.remove(dir, deleted_message_id.into());
+ git.commit("plent", &format!("remove {deleted_message_id:x}"));
+ git.push();
+ if git == &repos::DESIGN_IT && !cfg!(debug_assertions) {
+ send(c,|x| x
+ .username("plent")
+ .embed(CreateEmbed::new().color(RM)
+ .description(format!("{CANCEL} remove {} (added by {own}) (`{:x}.msch`)", emoji::mindustry::to_discord(&strip_colors(s.tags.get("name").unwrap())), deleted_message_id))
+ .footer(CreateEmbedFooter::new("message was deleted.")
+ ))
+ ).await;
+ };
+ }
+}
+
static HOOK: OnceLock<Webhook> = OnceLock::new();
pub async fn hookup(c: &impl AsRef<Http>) {
@@ -186,7 +220,11 @@ async fn handle_message(
.author_nick(c)
.await
.unwrap_or(new_message.author.name.clone());
- let (dir, l, repo) = sep(SPECIAL.get(&new_message.channel_id.get()));
+ let post = EXTRA.get(&new_message.channel_id.get()).map(|x| x.clone());
+ if post.is_some() {
+ println!("recv message on thread")
+ }
+ let (dir, l, repo) = sep(SPECIAL.get(&new_message.channel_id.get()).or(post.as_ref()));
let m = Msg {
author: who.clone(),
avatar: new_message.author.avatar_url().unwrap_or(CAT.to_string()),
@@ -226,7 +264,7 @@ async fn handle_message(
(new_message.author.name.clone(), new_message.author.id.get()),
);
use emoji::named::*;
- if repo.id == 925674713429184564 && !cfg!(debug_assertions) {
+ if repo.name == "DESIGN_IT" && !cfg!(debug_assertions) {
send(c,|x| x
.avatar_url(new_message.author.avatar_url().unwrap_or(CAT.to_string()))
.username(&who)
@@ -234,6 +272,10 @@ async fn handle_message(
.description(format!("https://discord.com/channels/925674713429184564/{}/{} {ADD} add {} (`{:x}.msch`)", m.channel_id,m.id, emoji::mindustry::to_discord(&strip_colors(s.tags.get("name").unwrap())), new_message.id.get())))
).await;
}
+ if post.is_some() {
+ EXTRA.remove(&new_message.channel_id.get());
+ db::set(new_message.channel_id.get(), new_message.id.get());
+ }
repo.write(dir, new_message.id, s);
repo.add();
repo.commit(&who, &format!("add {:x}.msch", new_message.id.get()));
@@ -252,13 +294,11 @@ async fn handle_message(
}
static SEEN: LazyLock<Mutex<HashSet<(GuildId, u64, String, UserId)>>> =
LazyLock::new(|| Mutex::new(HashSet::new()));
-
+static EXTRA: LazyLock<DashMap<u64, Ch>> = LazyLock::new(DashMap::new);
pub struct Bot;
impl Bot {
pub async fn spawn() {
use emoji::named::*;
- println!("check clones");
- clone();
println!("bot startup");
let tok =
std::env::var("TOKEN").unwrap_or_else(|_| read_to_string("token").expect("wher token"));
@@ -293,17 +333,33 @@ impl Bot {
// let User{id,name:owner_name,..} = c.http().get_user(*owner_id).await.unwrap();
}
// :deny:, @vd
- FullEvent::ReactionAdd {
- add_reaction: Reaction { message_id, guild_id: Some(guild_id), emoji: ReactionType::Custom { id,.. } ,channel_id,member: Some( m @ Member{ nick,user,..}),..}}
- if let Some(git) = REPOS.get(&guild_id.get())
+ FullEvent::ReactionAdd { add_reaction: Reaction {
+ message_id,
+ emoji: ReactionType::Custom { id, .. },
+ channel_id,
+ member: Some(m @ Member {
+ nick,
+ user,
+ .. }), .. }
+ }
+ if let Some(Ch {
+ d: dir,
+ repo: git,
+ .. }) = SPECIAL.get(&channel_id.get()).or(
+ channel_id.to_channel(c.http()).await
+ .ok().and_then(|x| x.guild())
+ .and_then(|x| x.parent_id)
+ .and_then(|x| FORUMS.get(&x.get())),
+ )
&& *id == git.deny_emoji
&& git.auth(m)
- && let Some(Ch {d:dir,..}) = SPECIAL.get(&channel_id.get())
=> {
+ // repos::ALL.into_iter().filter(|x|x.own().await.get(k))
let m = c.http().get_message(*channel_id,* message_id).await?;
- if let Some(git) = REPOS.get(&guild_id.get()) && let Ok(s) = git.schem(dir,*message_id) {
+ if let Ok(s) = git.schem(dir,*message_id) {
+ _ = db::remove(channel_id.get());
let who = nick.as_deref().unwrap_or(&user.name);
- let own = ownership::get(*guild_id).await.erase(*message_id).unwrap();
+ let own = ownership::get(git).await.erase(*message_id).unwrap();
git.remove(dir, *message_id);
git.commit(who, &format!("remove {:x}.msch", message_id.get()));
git.push();
@@ -311,7 +367,7 @@ impl Bot {
m.delete_reaction(c,Some(1174262682573082644.into()), ReactionType::Custom { animated: false, id: 1192316518395039864.into(), name: Some("merge".into()) }).await.unwrap();
m.react(c,emojis::get!(DENY)).await?;
// only design-it has a webhook (possibly subject to future change)
- if *guild_id == 925674713429184564 && !cfg!(debug_assertions) {
+ if git.name == "DESIGN_IT" && !cfg!(debug_assertions) {
send(c,|x| x
.avatar_url(user.avatar_url().unwrap_or(CAT.to_string()))
.username(who)
@@ -329,6 +385,13 @@ impl Bot {
return Ok(());
}
handle_message(c, new_message, d).await?;
+ },
+ FullEvent::ThreadCreate { thread } if let Some(Ch{repo, d, ty: Type::Forum(_)}) = repos::FORUMS.get(&thread.parent_id.unwrap().get()) => {
+ let tg = thread.guild(c).unwrap().channels[&thread.parent_id.unwrap()].available_tags.iter()
+ .filter(|x| {
+ thread.applied_tags.contains(&x.id)
+ }).map(|x| x.name.clone()).collect::<Vec<_>>();
+ EXTRA.insert(thread.id.get(), Ch { repo, d, ty: Type::Assembled(tags(&*tg)) });
}
FullEvent::MessageUpdate {event: MessageUpdateEvent {
author: Some(author),
@@ -377,26 +440,18 @@ impl Bot {
}
}
}
+ FullEvent::ThreadDelete { thread, .. } if let Some(ch) = FORUMS.get(&thread.parent_id.get()) && let Some(deleted_message_id) = db::remove(thread.id.get()) => del(&c, ch, deleted_message_id).await,
FullEvent::MessageDelete {
deleted_message_id, channel_id, ..
} => {
- if let Some(&Ch{ d:dir, repo, ..}) = SPECIAL.get(&channel_id.get()) {
- let git = &REPOS[&repo];
- if let Ok(s) = git.schem(dir, *deleted_message_id) {
- let own = git.own().await.erase(deleted_message_id.get()).unwrap();
- git.remove(dir, *deleted_message_id);
- git.commit("plent", &format!("remove {:x}", deleted_message_id.get()));
- git.push();
- if repo == 925674713429184564 && !cfg!(debug_assertions) {
- send(c,|x| x
- .username("plent")
- .embed(CreateEmbed::new().color(RM)
- .description(format!("{CANCEL} remove {} (added by {own}) (`{:x}.msch`)", emoji::mindustry::to_discord(&strip_colors(s.tags.get("name").unwrap())), deleted_message_id.get()))
- .footer(CreateEmbedFooter::new("message was deleted.")
- ))
- ).await;
- }
- };
+ if let Some(ch) = SPECIAL.get(&channel_id.get()).or(
+ channel_id.to_channel(c.http()).await
+ .ok().and_then(|x| x.guild())
+ .and_then(|x| x.parent_id)
+ .and_then(|x| FORUMS.get(&x.get()))
+ ) {
+ _ = db::remove(channel_id.get());
+ del(&c, ch, deleted_message_id.get()).await;
}
if let Some((_, r)) = d.tracker.remove(deleted_message_id) {
r.delete(c).await.unwrap();
@@ -508,24 +563,24 @@ impl Bot {
// Ok(())
// }
-#[poise::command(slash_command)]
-pub async fn retag(c: Context<'_>, channel: ChannelId) -> Result<()> {
- if c.author().id != OWNER {
- poise::say_reply(c, "access denied. this incident will be reported").await?;
- return Ok(());
- }
- c.defer().await?;
- let tags = tags(SPECIAL[&channel.get()].labels);
- for schem in search::dir(channel.get()).unwrap() {
- let mut s = search::load(&schem);
- let mut v = DataWrite::default();
- s.tags.insert("labels".into(), tags.clone());
- s.serialize(&mut v)?;
- std::fs::write(schem, v.consume())?;
- }
- c.reply(emoji::named::OK).await?;
- Ok(())
-}
+// #[poise::command(slash_command)]
+// pub async fn retag(c: Context<'_>, channel: ChannelId) -> Result<()> {
+// if c.author().id != OWNER {
+// poise::say_reply(c, "access denied. this incident will be reported").await?;
+// return Ok(());
+// }
+// c.defer().await?;
+// let tags = tags(SPECIAL[&channel.get()].labels);
+// for schem in search::dir(channel.get()).unwrap() {
+// let mut s = search::load(&schem);
+// let mut v = DataWrite::default();
+// s.tags.insert("labels".into(), tags.clone());
+// s.serialize(&mut v)?;
+// std::fs::write(schem, v.consume())?;
+// }
+// c.reply(emoji::named::OK).await?;
+// Ok(())
+// }
// dbg!(m
// .iter()
@@ -574,7 +629,7 @@ const VDS: &[u64] = &[
pub async fn leaderboard(c: Context<'_>, channel: Option<ChannelId>, vds: bool) -> Result<()> {
use emoji::named::*;
c.defer().await?;
- let lock = REPOS[&925674713429184564].own().await;
+ let lock = repos::DESIGN_IT.own().await;
let process = |map: HashMap<u64, u16>| {
let mut v = map.into_iter().collect::<Vec<_>>();
v.sort_by_key(|(_, x)| *x);
@@ -588,41 +643,38 @@ pub async fn leaderboard(c: Context<'_>, channel: Option<ChannelId>, vds: bool)
out
};
- match channel {
- Some(ch) => {
- let Some(x) = SPECIAL.get(&ch.get()) else {
- poise::say_reply(c, format!("{CANCEL} not a schem channel")).await?;
- return Ok(());
- };
- let mut map = HashMap::new();
- search::dir(ch.get())
- .unwrap()
- .map(|y| lock.map[&search::flake(y.file_name().unwrap().to_str().unwrap())].1)
- .filter(|x| vds || !VDS.contains(x))
- .for_each(|x| *map.entry(x).or_default() += 1);
- poise::say_reply(
- c,
- format!(
- "## Leaderboard of {}\n{}",
- x.labels
- .join("")
- .chars()
- .map(|x| emoji::mindustry::TO_DISCORD[&x])
- .collect::<String>(),
- process(map)
- ),
- )
- }
- None => {
- let mut map = std::collections::HashMap::new();
- search::files()
- .map(|(y, _)| lock.map[&search::flake(y.file_name().unwrap().to_str().unwrap())].1)
- .filter(|x| vds || !VDS.contains(x))
- .for_each(|x| *map.entry(x).or_default() += 1);
- poise::say_reply(c, format!("## Leaderboard\n{}", process(map)))
- }
- }
- .await?;
+ // match channel {
+ // Some(ch) => {
+ // let Some(x) = SPECIAL.get(&ch.get()) else {
+ // poise::say_reply(c, format!("{CANCEL} not a schem channel")).await?;
+ // return Ok(());
+ // };
+ // let mut map = HashMap::new();
+ // search::dir(ch.get())
+ // .unwrap()
+ // .map(|y| lock.map[&search::flake(y.file_name().unwrap().to_str().unwrap())].1)
+ // .filter(|x| vds || !VDS.contains(x))
+ // .for_each(|x| *map.entry(x).or_default() += 1);
+ // poise::say_reply(
+ // c,
+ // format!(
+ // "## Leaderboard of {}\n{}",
+ // x.labels
+ // .join("")
+ // .chars()
+ // .map(|x| emoji::mindustry::TO_DISCORD[&x])
+ // .collect::<String>(),
+ // process(map)
+ // ),
+ // )
+ // }
+ // None => {
+ let mut map = std::collections::HashMap::new();
+ search::files()
+ .map(|(y, _)| lock.map[&search::flake(y.file_name().unwrap().to_str().unwrap())].1)
+ .filter(|x| vds || !VDS.contains(x))
+ .for_each(|x| *map.entry(x).or_default() += 1);
+ poise::say_reply(c, format!("## Leaderboard\n{}", process(map))).await?;
Ok(())
}