smol bot
custom gamemodes
bendn 2025-01-15
parent 13c509e · commit 6064fb2
-rw-r--r--.gitignore3
-rw-r--r--Cargo.toml22
-rw-r--r--src/bot/db.rs34
-rw-r--r--src/bot/mod.rs282
-rw-r--r--src/bot/ownership.rs15
-rw-r--r--src/bot/repos.rs104
-rw-r--r--src/expose.rs12
-rw-r--r--src/main.rs5
8 files changed, 311 insertions, 166 deletions
diff --git a/.gitignore b/.gitignore
index d813308..1003f76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@ Cargo.lock
repo
html
*.auth
-repos/ \ No newline at end of file
+repos/
+channels/
diff --git a/Cargo.toml b/Cargo.toml
index ec1a236..bdedade 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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;
}