smol bot
custom gamemodes
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Cargo.toml | 22 | ||||
| -rw-r--r-- | src/bot/db.rs | 34 | ||||
| -rw-r--r-- | src/bot/mod.rs | 282 | ||||
| -rw-r--r-- | src/bot/ownership.rs | 15 | ||||
| -rw-r--r-- | src/bot/repos.rs | 104 | ||||
| -rw-r--r-- | src/expose.rs | 12 | ||||
| -rw-r--r-- | src/main.rs | 5 |
8 files changed, 311 insertions, 166 deletions
@@ -4,4 +4,5 @@ Cargo.lock repo html *.auth -repos/
\ No newline at end of file +repos/ +channels/ @@ -23,9 +23,6 @@ poise = { git = "https://github.com/fgardt/poise", branch = "feat/user_apps" } anyhow = "1.0.75" regex = { version = "1.8.4", features = ["std"], default-features = false } mindus = { version = "5.0.7", features = [], default-features = false } -flate2 = { version = "1.0", features = [ - "cloudflare_zlib", -], default-features = false } lemu = { features = ["diagnose"], default-features = false, version = "0.2.0" } dashmap = "5.5.3" oxipng = { version = "9.0.0", default-features = false } @@ -43,18 +40,27 @@ axum = { version = "0.6.18", features = [ "tokio", "http1", "macros", -], default-features = false } +], default-features = false, optional = true } serde_json = "1.0.122" serde = "1.0.204" atools = "0.1.5" -# edg = { path = "../edg" } httpdate = "1.0.3" pollster = "0.3.0" btparse-stable = "0.1.2" cpu-monitor = "0.1.1" exoquant = "0.2.0" -image = { version = "0.25.5", features = ["bmp", "jpeg", "png", "webp"], default-features = false } +image = { version = "0.25.5", features = [ + "bmp", + "jpeg", + "png", + "webp", +], default-features = false } car = "0.1.1" +kv = "0.24.0" +sled = { version = "0.34.7", features = ["compression"] } + +[features] +server = ["axum"] [build-dependencies] emojib = { git = "https://github.com/Apricot-Conservation-Project/emoji", features = [ @@ -62,8 +68,9 @@ emojib = { git = "https://github.com/Apricot-Conservation-Project/emoji", featur ], package = "emoji" } [profile.release] -strip = true +# strip = true lto = "thin" +debug = 2 [profile.dev.package.mindus] opt-level = 3 @@ -76,3 +83,4 @@ debug-assertions = false [patch.crates-io] serenity = { git = "https://github.com/serenity-rs/serenity" } mindus = { git = "https://github.com/bend-n/mindus" } +fimg = { git = "https://github.com/bend-n/fimg" } diff --git a/src/bot/db.rs b/src/bot/db.rs new file mode 100644 index 0000000..84ed07e --- /dev/null +++ b/src/bot/db.rs @@ -0,0 +1,34 @@ +// from moderatior +use std::sync::LazyLock; + +use kv::*; + +fn cfg() -> kv::Config { + kv::Config { + path: "./channels".into(), + temporary: false, + use_compression: true, + flush_every_ms: None, + cache_capacity: None, + segment_size: None, + } +} +static DB: LazyLock<Store> = LazyLock::new(|| Store::new(cfg()).unwrap()); +static BU: LazyLock<Bucket<Integer, Vec<u8>>> = LazyLock::new(|| DB.bucket(None).unwrap()); + +pub fn set(k: u64, v: u64) { + BU.set(&k.into(), &v.to_le_bytes().to_vec()).unwrap(); + BU.flush().unwrap(); +} +pub fn remove(k: u64) -> Option<u64> { + BU.remove(&k.into()) + .ok() + .flatten() + .map(|x| u64::from_le_bytes(x.try_into().unwrap())) + .inspect(|_| { + BU.flush().unwrap(); + }) +} +pub fn sz() -> f32 { + DB.size_on_disk().unwrap() as f32 / (1 << 20) as f32 +} 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(()) } diff --git a/src/bot/ownership.rs b/src/bot/ownership.rs index 100dda6..cd503d8 100644 --- a/src/bot/ownership.rs +++ b/src/bot/ownership.rs @@ -2,13 +2,16 @@ use serenity::all::MessageId; use std::collections::HashMap; use tokio::sync::MutexGuard; +use super::repos::Repo; + +#[derive(Debug)] pub struct Ownership { pub map: HashMap<u64, (String, u64)>, path: &'static str, } impl Ownership { - pub fn new(id: u64) -> Self { - let path = format!("repos/{id:x}/ownership.json").leak(); + pub fn new(id: &'static str) -> Self { + let path = format!("repos/{id}/ownership.json").leak(); Self { map: serde_json::from_slice(&std::fs::read(&path).unwrap_or_default()) .unwrap_or_default(), @@ -36,9 +39,9 @@ impl Ownership { &self.get(x.into().get()).0 } } -pub async fn whos(g: u64, x: impl Into<u64>) -> String { - super::repos::REPOS[&g].own().await.get(x.into()).0.clone() +pub async fn whos(repo: &'static Repo, x: impl Into<u64>) -> String { + repo.own().await.get(x.into()).0.clone() } -pub async fn get(g: impl Into<u64>) -> MutexGuard<'static, Ownership> { - super::repos::REPOS[&g.into()].own().await +pub async fn get(g: &'static Repo) -> MutexGuard<'static, Ownership> { + g.own().await } diff --git a/src/bot/repos.rs b/src/bot/repos.rs index 1cdd1fd..d59c2aa 100644 --- a/src/bot/repos.rs +++ b/src/bot/repos.rs @@ -1,15 +1,14 @@ use super::{ownership::Ownership, *}; use tokio::sync::Mutex; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum Person { Role(u64), User(u64), } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Repo { - pub id: u64, // delete power pub admins: &'static [Person], /// power to `scour` and `retag` etc. @@ -17,15 +16,23 @@ pub struct Repo { pub deny_emoji: u64, /// clone url: https://bend-n:github_pat_…@github.com/… pub auth: &'static str, + pub name: &'static str, ownership: &'static LazyLock<Mutex<Ownership>>, // possibly posters? } +impl std::cmp::PartialEq for Repo { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + use super::schematic::Schem; use mindus::data::DataWrite; impl Repo { pub fn auth(&self, member: &Member) -> bool { - self.chief == member.user.id.get() + OWNER == member.user.id.get() + || self.chief == member.user.id.get() || (self.admins.iter().any(|&x| match x { Person::Role(x) => member.roles.contains(&RoleId::new(x)), Person::User(x) => member.user.id.get() == x, @@ -56,7 +63,7 @@ impl Repo { } pub fn repopath(&self) -> std::path::PathBuf { - Path::new("repos").join(format!("{:x}", self.id)) + Path::new("repos").join(format!("{}", self.name)) } pub fn remove(&self, dir: &str, x: MessageId) { @@ -71,10 +78,20 @@ impl Repo { .success()); } + pub fn pull(&self) { + assert!(std::process::Command::new("git") + .current_dir(self.repopath()) + .arg("pull") + .arg("-q") + .status() + .unwrap() + .success()); + } + pub fn commit(&self, by: &str, msg: &str) { assert!(std::process::Command::new("git") .current_dir(self.repopath()) - .args(["commit", "-q", "--author"]) + .args(["commit", "--no-gpg-sign", "-q", "--author"]) .arg(format!("{by} <@designit>")) .arg("-m") .arg(msg) @@ -84,6 +101,7 @@ impl Repo { } pub fn push(&self) { + #[cfg(not(debug_assertions))] assert!(std::process::Command::new("git") .current_dir(self.repopath()) .arg("push") @@ -116,18 +134,24 @@ macro_rules! decl { ( [$($threaded:literal)+ $(,)?]; $( - $repo:literal => [ + $repo:ident => [ $( // xu64 => "dirname" : [label, label] $ch:literal => $item:literal : [$($labels: expr),* $(,)?] - ),+ $(,)? + ),* + + $(forum $chf:literal => $itemf:literal),* + ]; )+ ) => { use crate::emoji::to_mindustry::named::*; - pub const THREADED: phf::Set<u64> = phf::phf_set! { $($threaded,)+ }; - pub const SPECIAL: phf::Map<u64, Ch> = phf::phf_map! { - $($($ch => Ch { d: $item, labels: &[$($labels,)+], repo: $repo },)?)+ + pub static THREADED: phf::Set<u64> = phf::phf_set! { $($threaded,)+ }; + pub static SPECIAL: phf::Map<u64, Ch> = phf::phf_map! { + $($($ch => Ch { d: $item, ty: Type::Basic(&[$($labels,)+]), repo: &$repo },)?)* + }; + pub static FORUMS: phf::Map<u64, Ch> = phf::phf_map! { + $($($chf => Ch { d: $itemf, ty: Type::Forum(()), repo: &$repo },)*)+ }; }; } @@ -142,39 +166,42 @@ macro_rules! person { macro_rules! repos { ( - $($repo_ident:ident $repo:literal => { admins: $admins:expr, chief: $chief:expr, deny_emoji: $deny:expr,} $(,)?),+ - ) => { - - $(static $repo_ident: LazyLock<Mutex<Ownership>> = LazyLock::new(|| Mutex::new(super::ownership::Ownership::new($repo)));)+ - pub static REPOS: phf::Map<u64, Repo> = phf::phf_map! { - $($repo => Repo { - id: $repo, + $($repo_ident:ident => { admins: $admins:expr, chief: $chief:expr, deny_emoji: $deny:expr,} $(,)?),+ + ) => { paste::paste! { + $(static [<$repo_ident _OWN>]: LazyLock<Mutex<Ownership>> = LazyLock::new(|| Mutex::new(super::ownership::Ownership::new(stringify!($repo_ident))));)+ + $(pub static $repo_ident: Repo = Repo { admins: $admins, chief: $chief, deny_emoji: $deny, - auth: include_str!(concat!("../../", $repo, ".auth")), - ownership: &$repo_ident, - },)+ - }; - }; + name: stringify!($repo_ident), + auth: include_str!(concat!("../../", stringify!($repo_ident), ".auth")), + ownership: &[<$repo_ident _OWN>], + };)+ + pub static ALL: &[&Repo] = &[$(&$repo_ident),+]; + }}; } repos! { - DESIGN_IT 925674713429184564u64 => { + DESIGN_IT => { admins: &[person!(&925676016708489227)], chief: 696196765564534825, deny_emoji: 1192388789952319499u64, }, - ACP 1110086242177142854u64 => { + ACP => { admins: &[person!(&1110439183190863913)], chief: 696196765564534825, deny_emoji: 1182469319641272370u64, - } + }, + MISC => { + admins: &[person!(&925676016708489227)], + chief: 705503407179431937, + deny_emoji: 1192388789952319499u64, + }, } decl! { [1129391545418797147u64]; - 925674713429184564 => [ + DESIGN_IT => [ 925721957209636914u64 => "cryofluid" : [CRYOFLUID, CRYOFLUID_MIXER], 925721791475904533u64 => "graphite" : [GRAPHITE, GRAPHITE_PRESS], 925721824556359720u64 => "metaglass" : [METAGLASS, KILN], @@ -224,16 +251,33 @@ decl! { 1222270513045438464u64 => "bore": [PRODUCTION], 1226407271978766356u64 => "pulveriser": [PULVERIZER, SAND], 1277138620863742003u64 => "melter": [MELTER, SLAG], -1277138532355543070u64 => "separator": [SEPARATOR, SCRAP], +1277138532355543070u64 => "separator": [SEPARATOR, SCRAP] + ]; +MISC => [ +forum 1297452357549821972u64 => "s-defensive-outpost", +forum 1297451239449038931u64 => "s-drill-pump", +forum 1297449976015618109u64 => "s-miscellaneous", +forum 1297448333895405588u64 => "s-power", +forum 1297437167647461446u64 => "s-resources", +forum 1297449040438493195u64 => "s-units", +forum 1297463948999790642u64 => "e-bore-pump", +forum 1297460753145659453u64 => "e-defensive-outpost", +forum 1297464596810174508u64 => "e-miscellaneous", +forum 1297463058092003328u64 => "e-power", +forum 1297462381298843680u64 => "e-resources", +forum 1297463616035098654u64 => "e-units" ]; -1110086242177142854u64 => [ +ACP => [ 1276759410722738186u64 => "schems": ["plague"] ]; } macro_rules! chief { ($c:ident) => {{ - let repo = repos::REPOS[&$c.guild_id().unwrap().get()]; + let repo = repos::SPECIAL + .get(&$c.id()) + .ok_or(anyhow::anyhow!("not repo"))? + .repo; if repo.chief != $c.author().id.get() { poise::send_reply( $c, diff --git a/src/expose.rs b/src/expose.rs index d80aef9..812629b 100644 --- a/src/expose.rs +++ b/src/expose.rs @@ -122,7 +122,7 @@ impl Server { ( StatusCode::OK, crate::bot::ownership::whos( - 925674713429184564, + &crate::bot::repos::DESIGN_IT, match u64::from_str_radix(file.trim_end_matches(".msch"), 16) { Ok(x) => x, Err(_) => return (StatusCode::NOT_FOUND, "".into()), @@ -143,11 +143,9 @@ impl Server { } }), ); - tokio::spawn(async move { - AxumServer::bind(&addr) - .serve(router.into_make_service()) - .await - .unwrap(); - }); + AxumServer::bind(&addr) + .serve(router.into_make_service()) + .await + .unwrap() } } diff --git a/src/main.rs b/src/main.rs index 4963eae..6d43592 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,14 +12,19 @@ emojib::the_crate! {} use std::{net::SocketAddr, sync::OnceLock, time::Instant}; +#[cfg(feature = "server")] mod expose; #[macro_use] mod bot; static START: OnceLock<Instant> = OnceLock::new(); #[tokio::main(flavor = "current_thread")] async fn main() { + println!("check clones"); + bot::clone(); START.get_or_init(Instant::now); + #[cfg(feature = "server")] expose::Server::spawn(<SocketAddr as std::str::FromStr>::from_str("0.0.0.0:2000").unwrap()) .await; + #[cfg(not(feature = "server"))] bot::Bot::spawn().await; } |