smol bot
add addable repos
bendn 2024-09-01
parent 83d2b1a · commit 8c2f328
-rw-r--r--.gitignore4
-rw-r--r--src/bot/mod.rs475
-rw-r--r--src/bot/ownership.rs65
-rw-r--r--src/bot/repo.md14
-rw-r--r--src/bot/repos.rs254
-rw-r--r--src/expose.rs1
6 files changed, 526 insertions, 287 deletions
diff --git a/.gitignore b/.gitignore
index f6da125..d813308 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
token
Cargo.lock
repo
-html \ No newline at end of file
+html
+*.auth
+repos/ \ No newline at end of file
diff --git a/src/bot/mod.rs b/src/bot/mod.rs
index ed47c01..d76718a 100644
--- a/src/bot/mod.rs
+++ b/src/bot/mod.rs
@@ -1,6 +1,7 @@
mod logic;
mod map;
pub mod ownership;
+mod repos;
mod schematic;
pub mod search;
@@ -9,16 +10,40 @@ use dashmap::DashMap;
use mindus::data::DataWrite;
use mindus::Serializable;
use poise::{serenity_prelude::*, CreateReply};
+use repos::{Repo, REPOS, SPECIAL, THREADED};
use serenity::futures::StreamExt;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::fs::read_to_string;
use std::ops::ControlFlow;
use std::path::Path;
+use std::process::Stdio;
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() {
+ assert_eq!(
+ std::process::Command::new("git")
+ .current_dir("repos")
+ .arg("clone")
+ .args(["--depth", "5"])
+ .arg(auth)
+ .arg(format!("{k:x}"))
+ .stdout(Stdio::inherit())
+ .stderr(Stdio::inherit())
+ .status()
+ .unwrap()
+ .code()
+ .unwrap(),
+ 0
+ );
+ }
+ }
+}
+
#[derive(Debug)]
pub struct Data {
// message -> resp
@@ -44,16 +69,6 @@ const SUCCESS: (u8, u8, u8) = (34, 139, 34);
const PFX: char = '}';
-macro_rules! decl {
- ($($ch:literal $( => $item:literal : [$($labels: expr),* $(,)?])?),+ $(,)?) => {
- use emoji::to_mindustry::named::*;
- const THREADED: phf::Set<u64> = phf::phf_set! { $($ch,)+ };
- const SPECIAL: phf::Map<u64, Ch> = phf::phf_map! {
- $($($ch => Ch { d: $item, labels: &[$($labels,)+] })?),+
- };
- };
-}
-
fn tags(t: &[&str]) -> String {
if let [x, rest @ ..] = t {
let mut s = format!("[\"{x}\"");
@@ -67,101 +82,56 @@ fn tags(t: &[&str]) -> String {
}
}
-decl! {
- 925721957209636914u64 => "cryofluid" : [CRYOFLUID, CRYOFLUID_MIXER],
- 925721791475904533u64 => "graphite" : [GRAPHITE, GRAPHITE_PRESS],
- 925721824556359720u64 => "metaglass" : [METAGLASS, KILN],
- 925721863525646356u64 => "phase-fabric" : [PHASE_FABRIC, PHASE_WEAVER],
- 927036346869104693u64 => "plastanium" : [PLASTANIUM, PLASTANIUM_COMPRESSOR],
- 925736419983515688u64 => "pyratite" : [PYRATITE, PYRATITE_MIXER],
- 925736573037838397u64 => "blast-compound" : [BLAST_COMPOUND, BLAST_MIXER],
- 927793648417009676u64 => "scrap" : [DISASSEMBLER, SCRAP],
- 1198556531281637506u64 => "spore-press" : [OIL, SPORE_PRESS],
- 1200308146460180520u64 => "oil-extractor" : [OIL, OIL_EXTRACTOR],
- 1200301847387316317u64 => "rtg-gen" : [POWER, RTG_GENERATOR],
- 1200308292744921088u64 => "cultivator" : [SPORE_POD, CULTIVATOR],
- 1200305956689547324u64 => "graphite-multipress" : [GRAPHITE, MULTI_PRESS],
- 1200306409036857384u64 => "silicon-crucible" : [SILICON, SILICON_CRUCIBLE],
- 1198555991667646464u64 => "coal" : [COAL, COAL_CENTRIFUGE],
- 925721763856404520u64 => "silicon" : [SILICON, SILICON_SMELTER],
- 925721930814869524u64 => "surge-alloy" : [SURGE_ALLOY, SURGE_SMELTER],
- 1141034314163826879u64 => "defensive-outpost" : [""],
- 949529149800865862u64 => "drills" : [PRODUCTION],
- 925729855574794311u64 => "logic-schems" : [MICRO_PROCESSOR],
- 1185702384194818048u64 => "miscellaneous" : ["…"],
- 1018541701431836803u64 => "combustion-gen" : [POWER, COMBUSTION_GENERATOR],
- 927480650859184171u64 => "differential-gen" : [POWER, DIFFERENTIAL_GENERATOR],
- 925719985987403776u64 => "impact-reactor" : [POWER, IMPACT_REACTOR],
- 949740875817287771u64 => "steam-gen" : [POWER, STEAM_GENERATOR],
- 926163105694752811u64 => "thorium-reactor" : [POWER, THORIUM_REACTOR],
- 973234467357458463u64 => "carbide" : [CARBIDE, ""],
- 1198527267933007893u64 => "erekir-defensive-outpost" : [""],
- 973236445567410186u64 => "fissile-matter" : [FISSILE_MATTER, ""],
- 1147887958351945738u64 => "electrolyzer" : [HYDROGEN, OZONE, ""],
- 1202001032503365673u64 => "nitrogen" : [NITROGEN, ""],
- 1202001055349477426u64 => "cyanogen" : [CYANOGEN, ""],
- 1096157669112418454u64 => "mass-driver" : ["…", PLANET],
- 973234248054104115u64 => "oxide" : [OXIDE, ""],
- 973422874734002216u64 => "erekir-phase" : [PHASE_FABRIC, ""],
- 973369188800413787u64 => "ccc" : ["", POWER],
- 1218453338396430406u64 => "neoplasia-reactor": ["", POWER],
- 1218453292045172817u64 => "flux-reactor": ["", POWER],
- 1218452986788053012u64 => "pyrolisis-gen": ["", POWER],
- 1147722735305367572u64 => "silicon-arc" : [SILICON, ""],
- 974450769967341568u64 => "erekir-surge" : [SURGE_ALLOY, ""],
- 973241041685737532u64 => "erekir-units" : ["[#ff9266][]"],
- 1158818171139133490u64 => "unit-core" : [UNITS, CORE_NUCLEUS],
- 1158818324210274365u64 => "unit-delivery" : [UNITS, FLARE],
- 1158818598568075365u64 => "unit-raw" : [UNITS, PRODUCTION],
- 1142181013779398676u64 => "unit-sand" : [UNITS, SAND],
- 1222270513045438464u64 => "bore": [PRODUCTION],
- 1226407271978766356u64 => "pulveriser": [PULVERIZER, SAND],
- 1277138620863742003u64 => "melter": [MELTER, SLAG],
- 1277138532355543070u64 => "separator": [SEPARATOR, SCRAP],
-
- 1129391545418797147u64,
-}
-
#[derive(Copy, Clone, Debug)]
-struct Ch {
+pub struct Ch {
+ repo: u64,
d: &'static str,
labels: &'static [&'static str],
}
-fn sep(x: Option<&Ch>) -> (Option<&'static str>, Option<String>) {
- (x.map(|x| x.d), x.map(|x| tags(x.labels)))
+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]),
+ )
}
const OWNER: u64 = 696196765564534825;
#[poise::command(slash_command)]
-pub async fn scour(c: Context<'_>, ch: ChannelId) -> Result<()> {
- if c.author().id != OWNER {
- poise::say_reply(c, "access denied. this incident will be reported").await?;
- return Ok(());
- }
+/// This command reads all messages to find the schems.
+/// This command will possibly add denied schems.
+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!("repo/{d}"));
+ _ = 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 {
+ let (_, Some(tags), _) = sep(SPECIAL.get(&ch.get())) else {
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::insert(msg.id.get(), (msg.author.name.clone(), msg.author.id.get())).await;
- git::write(d, msg.id, x);
- git::commit(&who, &format!("add {:x}.msch", msg.id.get()));
+ 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;
}
}
- git::push();
+ repo.push();
h.edit(
c,
poise::CreateReply::default().content(format!(
@@ -200,98 +170,97 @@ where
}
}
-pub mod git {
- use mindus::data::DataWrite;
-
- use self::schematic::Schem;
-
- use super::*;
- pub fn schem(dir: &str, x: MessageId) -> std::io::Result<mindus::Schematic> {
- std::fs::read(path(dir, x))
- .map(|x| mindus::Schematic::deserialize(&mut mindus::data::DataRead::new(&x)).unwrap())
- }
-
- pub fn path(dir: &str, x: MessageId) -> std::path::PathBuf {
- Path::new("repo")
- .join(dir)
- .join(format!("{:x}.msch", x.get()))
- }
-
- pub fn gpath(dir: &str, x: MessageId) -> std::path::PathBuf {
- Path::new(dir).join(format!("{:x}.msch", x.get()))
- }
-
- pub fn has(dir: &str, x: MessageId) -> bool {
- path(dir, x).exists()
- }
-
- pub fn remove(dir: &str, x: MessageId) {
- assert!(std::process::Command::new("git")
- .current_dir("repo")
- .arg("rm")
- .arg("-q")
- .arg(gpath(dir, x))
- .status()
- .unwrap()
- .success());
- }
-
- pub fn commit(by: &str, msg: &str) {
- assert!(std::process::Command::new("git")
- .current_dir("repo")
- .args(["commit", "-q", "--author"])
- .arg(format!("{by} <@designit>",))
- .arg("-m")
- .arg(msg)
- .status()
- .unwrap()
- .success());
- }
-
- pub fn push() {
- assert!(std::process::Command::new("git")
- .current_dir("repo")
- .arg("push")
- .arg("-q")
- .status()
- .unwrap()
- .success())
- }
-
- pub fn write(dir: &str, x: MessageId, s: Schem) {
- _ = std::fs::create_dir(format!("repo/{dir}"));
- let mut v = DataWrite::default();
- s.serialize(&mut v).unwrap();
- std::fs::write(path(dir, x), v.consume()).unwrap();
- add();
- }
-
- pub fn add() {
- assert!(std::process::Command::new("git")
- .current_dir("repo")
- .arg("add")
- .arg(".")
- .status()
- .unwrap()
- .success());
- }
-}
-
const RM: (u8, u8, u8) = (242, 121, 131);
const AD: (u8, u8, u8) = (128, 191, 255);
const CAT: &str =
"https://cdn.discordapp.com/avatars/696196765564534825/6f3c605329ffb5cfb790343f59ed355d.webp";
+async fn handle_message(
+ c: &poise::serenity_prelude::Context,
+ new_message: &Message,
+ d: &Data,
+) -> Result<()> {
+ let who = new_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 m = Msg {
+ author: who.clone(),
+ avatar: new_message.author.avatar_url().unwrap_or(CAT.to_string()),
+ attachments: new_message.attachments.clone(),
+ content: new_message.content.clone(),
+ channel: new_message.channel_id,
+ };
+ let x = schematic::with(m, c, l).await?;
+ match x {
+ ControlFlow::Continue(())
+ if THREADED.contains(&new_message.channel_id.get())
+ || SPECIAL.contains_key(&new_message.channel_id.get()) =>
+ {
+ new_message.delete(c).await?;
+ return Ok(());
+ }
+ ControlFlow::Break((m, n, s)) => {
+ if THREADED.contains(&m.channel_id.get()) {
+ m.channel_id
+ .create_thread_from_message(
+ c,
+ m.id,
+ CreateThread::new(n)
+ .audit_log_reason("because yes")
+ .auto_archive_duration(AutoArchiveDuration::OneDay),
+ )
+ .await
+ .unwrap();
+ }
+ if let Some(dir) = dir
+ && let Some(repo) = repo
+ {
+ println!("adding {dir}");
+ // add :)
+ repo.own().await.insert(
+ new_message.id.get(),
+ (m.author.name.clone(), m.author.id.get()),
+ );
+ use emoji::named::*;
+ if repo.id == 925674713429184564 && !cfg!(debug_assertions) {
+ send(c,|x| x
+ .avatar_url(new_message.author.avatar_url().unwrap_or(CAT.to_string()))
+ .username(&who)
+ .embed(CreateEmbed::new().color(AD)
+ .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;
+ }
+ repo.write(dir, new_message.id, s);
+ repo.add();
+ repo.commit(&who, &format!("add {:x}.msch", new_message.id.get()));
+ repo.push();
+ new_message.react(c, emojis::get!(MERGE)).await?;
+ }
+ d.tracker.insert(new_message.id, m);
+ return Ok(());
+ }
+ _ => (),
+ };
+
+ // not tracked, as you cant add a attachment afterwwards.
+ map::with(new_message, c).await?;
+ Ok(())
+}
+
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"));
let f = poise::Framework::builder()
.options(poise::FrameworkOptions {
- commands: vec![logic::run(), lb(), bust_ghosts(), lb_no_vds(), ping(), help(), search::search(), search::file(), render(), render_file(), render_message()],
+ commands: vec![logic::run(), lb(), schembrowser_instructions(), lb_no_vds(), ping(), help(), scour(), search::search(), search::file(), render(), render_file(), render_message()],
event_handler: |c, e, _, d| {
Box::pin(async move {
match e {
@@ -301,23 +270,32 @@ impl Bot {
hookup(c.http()).await;
}
// :deny:, @vd
- FullEvent::ReactionAdd { add_reaction: Reaction { message_id, emoji: ReactionType::Custom { id,.. } ,channel_id,member: Some(Member{roles,nick,user,..}),..}} if *id == 1192388789952319499 && let Some(Ch {d:dir,..}) = SPECIAL.get(&channel_id.get()) && roles.contains(&RoleId::new(925676016708489227)) => {
+ 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())
+ && *id == git.deny_emoji
+ && git.auth(m)
+ && let Some(Ch {d:dir,..}) = SPECIAL.get(&channel_id.get())
+ => {
let m = c.http().get_message(*channel_id,* message_id).await?;
- if let Ok(s) = git::schem(dir,*message_id) {
+ if let Some(git) = REPOS.get(&guild_id.get()) && let Ok(s) = git.schem(dir,*message_id) {
let who = nick.as_deref().unwrap_or(&user.name);
- let own = ownership::erase(message_id.get()).await.unwrap();
- git::remove(dir, *message_id);
- git::commit(who, &format!("remove {:x}.msch", message_id.get()));
- git::push();
+ let own = ownership::get(*guild_id).await.erase(*message_id).unwrap();
+ git.remove(dir, *message_id);
+ git.commit(who, &format!("remove {:x}.msch", message_id.get()));
+ git.push();
_ = m.delete_reaction(c,Some(1174262682573082644.into()), emojis::get!(MERGE)).await;
_ = 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) {
send(c,|x| x
.avatar_url(user.avatar_url().unwrap_or(CAT.to_string()))
.username(who)
.embed(CreateEmbed::new().color(RM)
.description(format!("https://discord.com/channels/925674713429184564/{channel_id}/{message_id} {} {} (added by {own}) (`{:x}`)", emojis::get!(DENY), emoji::mindustry::to_discord(&strip_colors(s.tags.get("name").unwrap())), message_id.get())))
).await;
+ }
};
}
FullEvent::GuildCreate { guild ,..} => {
@@ -344,56 +322,12 @@ impl Bot {
}
FullEvent::Message { new_message } => {
if new_message.content.starts_with('!')
- || new_message.content.starts_with(PFX)
- || new_message.author.bot
+ || new_message.content.starts_with(PFX)
+ || new_message.author.bot
{
return Ok(());
}
- let who = new_message
- .author_nick(c)
- .await
- .unwrap_or(new_message.author.name.clone());
- let m = Msg {
- author: who.clone(),
- avatar: new_message. author.avatar_url().unwrap_or(CAT.to_string()),
- attachments: new_message.attachments.clone(),
- content: new_message.content.clone(),
- channel: new_message.channel_id,
- };
- let (dir, l) = sep(SPECIAL.get(&new_message.channel_id.get()));
- let x = schematic::with(m, c, l).await?;
- match x {
- ControlFlow::Continue(()) if THREADED.contains(&new_message.channel_id.get()) => {
- new_message.delete(c).await?;
- return Ok(());
- },
- ControlFlow::Break((m, n, s)) => {
- if THREADED.contains(&m.channel_id.get()) {
- m.channel_id.create_thread_from_message(c, m.id,CreateThread::new(n).audit_log_reason("because yes").auto_archive_duration(AutoArchiveDuration::OneDay)).await.unwrap();
- }
- if let Some(dir) = dir {
- // add :)
- ownership::insert(new_message.id.get(), (m.author.name.clone(), m.author.id.get())).await;
- send(c,|x| x
- .avatar_url(new_message.author.avatar_url().unwrap_or(CAT.to_string()))
- .username(&who)
- .embed(CreateEmbed::new().color(AD)
- .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;
- git::write(dir, new_message.id, s);
- git::add();
- git::commit(&who, &format!("add {:x}.msch", new_message.id.get()));
- git::push();
- new_message.react(c, emojis::get!(MERGE)).await?;
- }
- d.tracker.insert(new_message.id, m);
- return Ok(());
- },
- _ => (),
- };
-
- // not tracked, as you cant add a attachment afterwwards.
- map::with(new_message, c).await?;
+ handle_message(c, new_message, d).await?;
}
FullEvent::MessageUpdate {event: MessageUpdateEvent {
author: Some(author),
@@ -411,7 +345,7 @@ impl Bot {
.nick_in(c, guild_id)
.await
.unwrap_or(author.name.clone());
- let (dir, l) = sep(SPECIAL.get(&r.channel_id.get()));
+ let (dir, l, repo) = sep(SPECIAL.get(&r.channel_id.get()));
if let ControlFlow::Break((m,_,s)) = schematic::with(
Msg {
avatar: author.avatar_url().unwrap_or(CAT.to_string()),
@@ -426,17 +360,19 @@ impl Bot {
.await?
{
d.tracker.insert(*id, m);
- if let Some(dir) = dir && git::has(dir, *id) {
+ if let Some(dir) = dir && let Some(git) = repo && git.has(dir, *id) {
// update :)
+ if *guild_id == 925674713429184564 && !cfg!(debug_assertions) {
send(c,|x| x
.avatar_url(author.avatar_url().unwrap_or(CAT.to_string()))
.username(&who)
.embed(CreateEmbed::new().color(AD)
.description(format!("https://discord.com/channels/925674713429184564/{channel_id}/{id} {ROTATE} update {} (`{:x}.msch`)", emoji::mindustry::to_discord(&strip_colors(s.tags.get("name").unwrap())), id.get())))
).await;
- git::write(dir, *id, s);
- git::commit(&who,&format!("update {:x}.msch", id.get()));
- git::push();
+ }
+ git.write(dir, *id, s);
+ git.commit(&who,&format!("update {:x}.msch", id.get()));
+ git.push();
}
}
}
@@ -444,12 +380,14 @@ impl Bot {
FullEvent::MessageDelete {
deleted_message_id, channel_id, ..
} => {
- if let Some(Ch{ d:dir,..}) = SPECIAL.get(&channel_id.get()) {
- if let Ok(s) = git::schem(dir, *deleted_message_id) {
- let own = ownership::erase(deleted_message_id.get()).await.unwrap();
- git::remove(dir, *deleted_message_id);
- git::commit("plent", &format!("remove {:x}", deleted_message_id.get()));
- git::push();
+ 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)
@@ -457,6 +395,7 @@ impl Bot {
.footer(CreateEmbedFooter::new("message was deleted.")
))
).await;
+ }
};
}
@@ -481,8 +420,9 @@ impl Bot {
})
.setup(|ctx, _ready, _| {
Box::pin(async move {
- poise::builtins::register_globally(ctx, &[logic::run(), help(), ping(), render(), render_file(), render_message()]).await?;
- poise::builtins::register_in_guild(ctx, &[search::search(), bust_ghosts(), lb(), lb_no_vds(), search::file()], 925674713429184564.into()).await?;
+ poise::builtins::register_globally(ctx, &[logic::run(), help(), ping(), render(), schembrowser_instructions(), render_file(), render_message()]).await?;
+ poise::builtins::register_in_guild(ctx, &[scour()], 1110086242177142854.into()).await?;
+ poise::builtins::register_in_guild(ctx, &[search::search(), lb(), lb_no_vds(), search::file()], 925674713429184564.into()).await?;
println!("registered");
let tracker = Arc::new(DashMap::new());
let tc = Arc::clone(&tracker);
@@ -513,39 +453,39 @@ impl Bot {
}
}
-pub async fn missing() -> impl Iterator<Item = (MessageId, ChannelId)> {
- let lock = ownership::MAP.lock().await;
- search::files()
- .map(move |(x, ch)| {
- let f = search::flake(x.file_name().unwrap().to_str().unwrap());
- (lock.contains_key(&f), f, ch)
- })
- .filter_map(|(x, m, c)| (!x).then(|| (m.into(), c.into())))
-}
-
-#[poise::command(slash_command)]
-pub async fn bust_ghosts(c: Context<'_>) -> Result<()> {
- if c.author().id != OWNER {
- poise::say_reply(c, "access denied. this incident will be reported").await?;
- return Ok(());
- }
- let h = c.reply(emoji::named::LOCK_OPEN).await?;
- for (m, ch) in missing().await.collect::<Vec<_>>() {
- let ch = c.guild().unwrap().channels[&ch].clone();
- let User { id, name, .. } = match ch.message(c, m).await {
- Ok(x) => x.author,
- Err(_) => {
- // removes ghosts
- std::fs::remove_file(git::path(&SPECIAL[&ch.id.get()].d, m)).unwrap();
- continue;
- }
- };
- ownership::insert(m.into(), (name, id.get())).await;
- }
- h.edit(c, poise::CreateReply::default().content(emoji::named::LOCK))
- .await?;
- Ok(())
-}
+// pub async fn missing(r: &'static Repo) -> impl Iterator<Item = (MessageId, ChannelId)> {
+// let lock = r.own().await;
+// search::files()
+// .map(move |(x, ch)| {
+// let f = search::flake(x.file_name().unwrap().to_str().unwrap());
+// (lock.map.contains_key(&f), f, ch)
+// })
+// .filter_map(|(x, m, c)| (!x).then(|| (m.into(), c.into())))
+// }
+
+// #[poise::command(slash_command)]
+// pub async fn bust_ghosts(c: Context<'_>) -> Result<()> {
+// if c.author().id != OWNER {
+// poise::say_reply(c, "access denied. this incident will be reported").await?;
+// return Ok(());
+// }
+// let h = c.reply(emoji::named::LOCK_OPEN).await?;
+// for (m, ch) in missing().await.collect::<Vec<_>>() {
+// let ch = c.guild().unwrap().channels[&ch].clone();
+// let User { id, name, .. } = match ch.message(c, m).await {
+// Ok(x) => x.author,
+// Err(_) => {
+// // removes ghosts
+// std::fs::remove_file(git::path(&SPECIAL[&ch.id.get()].d, m)).unwrap();
+// continue;
+// }
+// };
+// ownership::insert(m.into(), (name, id.get())).await;
+// }
+// h.edit(c, poise::CreateReply::default().content(emoji::named::LOCK))
+// .await?;
+// Ok(())
+// }
#[poise::command(slash_command)]
pub async fn retag(c: Context<'_>, channel: ChannelId) -> Result<()> {
@@ -613,7 +553,7 @@ const VDS: &[u64] = &[
pub async fn leaderboard(c: Context<'_>, channel: Option<ChannelId>, vds: bool) -> Result<()> {
use emoji::named::*;
c.defer().await?;
- let lock = ownership::MAP.lock().await;
+ let lock = REPOS[&925674713429184564].own().await;
let process = |map: HashMap<u64, u16>| {
let mut v = map.into_iter().collect::<Vec<_>>();
v.sort_by_key(|(_, x)| *x);
@@ -636,7 +576,7 @@ pub async fn leaderboard(c: Context<'_>, channel: Option<ChannelId>, vds: bool)
let mut map = HashMap::new();
search::dir(ch.get())
.unwrap()
- .map(|y| lock[&search::flake(y.file_name().unwrap().to_str().unwrap()).into()].1)
+ .map(|y| lock.map[&search::flake(y.file_name().unwrap().to_str().unwrap()).into()].1)
.filter(|x| vds || !VDS.contains(x))
.for_each(|x| *map.entry(x).or_default() += 1);
poise::say_reply(
@@ -656,7 +596,7 @@ pub async fn leaderboard(c: Context<'_>, channel: Option<ChannelId>, vds: bool)
let mut map = std::collections::HashMap::new();
search::files()
.map(|(y, _)| {
- lock[&search::flake(y.file_name().unwrap().to_str().unwrap()).into()].1
+ lock.map[&search::flake(y.file_name().unwrap().to_str().unwrap()).into()].1
})
.filter(|x| vds || !VDS.contains(x))
.for_each(|x| *map.entry(x).or_default() += 1);
@@ -969,3 +909,20 @@ pub async fn render_message(c: Context<'_>, m: Message) -> Result<()> {
.await?;
Ok(())
}
+
+#[poise::command(
+ slash_command,
+ install_context = "Guild",
+ interaction_context = "Guild|PrivateChannel"
+)]
+/// Instructions on adding a schematic repository to YOUR server!
+pub async fn schembrowser_instructions(c: Context<'_>) -> Result<()> {
+ poise::send_reply(
+ c,
+ poise::CreateReply::default()
+ .content(include_str!("repo.md"))
+ .allowed_mentions(CreateAllowedMentions::default().empty_users().empty_roles()),
+ )
+ .await?;
+ Ok(())
+}
diff --git a/src/bot/ownership.rs b/src/bot/ownership.rs
index ab8639a..12a3fc3 100644
--- a/src/bot/ownership.rs
+++ b/src/bot/ownership.rs
@@ -1,33 +1,44 @@
use serenity::all::MessageId;
-use std::{collections::HashMap, sync::LazyLock};
-use tokio::sync::Mutex;
+use std::collections::HashMap;
+use tokio::sync::MutexGuard;
-pub static MAP: LazyLock<Mutex<HashMap<u64, (String, u64)>>> = LazyLock::new(|| {
- Mutex::new(serde_json::from_slice(&std::fs::read("repo/ownership.json").unwrap()).unwrap())
-});
-
-pub async fn insert(k: u64, v: (String, u64)) {
- let mut lock = MAP.lock().await;
- lock.insert(k, v);
- std::fs::write(
- "repo/ownership.json",
- serde_json::to_string_pretty(&*lock).unwrap(),
- )
- .unwrap();
+pub struct Ownership {
+ pub map: HashMap<u64, (String, u64)>,
+ path: &'static str,
}
-pub async fn get(k: u64) -> (String, u64) {
- MAP.lock().await[&k].clone()
+impl Ownership {
+ pub fn new(id: u64) -> Self {
+ let path = format!("repos/{id:x}/ownership.json").leak();
+ Self {
+ map: serde_json::from_slice(&std::fs::read(&path).unwrap_or_default())
+ .unwrap_or_default(),
+ path,
+ }
+ }
+
+ pub fn insert(&mut self, k: u64, v: (String, u64)) {
+ self.map.insert(k, v);
+ self.flush();
+ }
+ fn flush(&self) {
+ std::fs::write(&self.path, serde_json::to_string_pretty(&self.map).unwrap()).unwrap();
+ }
+ pub fn get(&self, k: u64) -> &(String, u64) {
+ self.map.get(&k).unwrap()
+ }
+ pub fn erase(&mut self, k: impl Into<u64>) -> Option<String> {
+ let x = self.map.remove(&k.into()).map(|(x, _)| x);
+ self.flush();
+ x
+ }
+
+ pub fn whos(&self, x: impl Into<MessageId>) -> &str {
+ &self.get(x.into().get()).0
+ }
}
-pub async fn erase(k: u64) -> Option<String> {
- let mut lock = MAP.lock().await;
- let x = lock.remove(&k).map(|(x, _)| x);
- std::fs::write(
- "repo/ownership.json",
- serde_json::to_string_pretty(&*lock).unwrap(),
- )
- .unwrap();
- x
+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(x: impl Into<MessageId>) -> String {
- get(x.into().get()).await.0
+pub async fn get(g: impl Into<u64>) -> MutexGuard<'static, Ownership> {
+ super::repos::REPOS[&g.into()].own().await
}
diff --git a/src/bot/repo.md b/src/bot/repo.md
new file mode 100644
index 0000000..f5bb680
--- /dev/null
+++ b/src/bot/repo.md
@@ -0,0 +1,14 @@
+# you want your own schematic repository? look no further.
+
+if you dont know what a schematic repository is, look at [design-it](<https://github.com/bend-n/design-it>).
+its technology that lets your community upload schematics to a repository, which can then be pulled down by the [schembrowser](<https://github.com/sbxte/mindustry-schematic-browser>) mod.
+
+now, if you want this, or somebody in your community is bugging you to add this, here are the steps.
+
+- <:right:1182119750155915264> invite bendn (<:discord:1182124785371729990> `bendn_` <@696196765564534825>) to your server.
+- create a <:github_square:1182121451655004221> github repository and account for your server, or ask bendn or somebody in your community to do it for you.
+- elect <:admin:1182128872435749005> administrators of the schematic repository, certain trusted users.
+- create a discord channel, or three, for your schematics to go in, along with tags for each channel if you need them.
+- add an emote <:deny:1192388789952319499> for the administrators to react with to bad schems to delete them (or dont, if you dont want the schematics to ever go away).
+- create a [github PAT](<https://github.com/settings/tokens/new?description=Schematic%20Repo&scopes=repo>) and send it to bendn (<:warning:1182119952048726066> privately!)
+- wait for bendn to add your repo to me!
diff --git a/src/bot/repos.rs b/src/bot/repos.rs
new file mode 100644
index 0000000..33c1a92
--- /dev/null
+++ b/src/bot/repos.rs
@@ -0,0 +1,254 @@
+use super::{ownership::Ownership, *};
+use tokio::sync::Mutex;
+
+#[derive(Copy, Clone)]
+pub enum Person {
+ Role(u64),
+ User(u64),
+}
+
+#[derive(Copy, Clone)]
+pub struct Repo {
+ pub id: u64,
+ // delete power
+ pub admins: &'static [Person],
+ /// power to `scour` and `retag` etc.
+ pub chief: u64,
+ pub deny_emoji: u64,
+ /// clone url: https://bend-n:github_pat_…@github.com/…
+ pub auth: &'static str,
+ ownership: &'static LazyLock<Mutex<Ownership>>,
+ // possibly posters?
+}
+
+use super::schematic::Schem;
+use mindus::data::DataWrite;
+impl Repo {
+ pub fn auth(&self, member: &Member) -> bool {
+ 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,
+ }))
+ }
+
+ pub async fn own(&self) -> tokio::sync::MutexGuard<Ownership> {
+ self.ownership.lock().await
+ }
+
+ pub fn schem(&self, dir: &str, x: MessageId) -> std::io::Result<mindus::Schematic> {
+ std::fs::read(self.path(dir, x))
+ .map(|x| mindus::Schematic::deserialize(&mut mindus::data::DataRead::new(&x)).unwrap())
+ }
+
+ pub fn path(&self, dir: &str, x: MessageId) -> std::path::PathBuf {
+ self.repopath()
+ .join(dir)
+ .join(format!("{:x}.msch", x.get()))
+ }
+
+ pub fn gpath(&self, dir: &str, x: MessageId) -> std::path::PathBuf {
+ Path::new(dir).join(format!("{:x}.msch", x.get()))
+ }
+
+ pub fn has(&self, dir: &str, x: MessageId) -> bool {
+ self.path(dir, x).exists()
+ }
+
+ pub fn repopath(&self) -> std::path::PathBuf {
+ Path::new("repos").join(format!("{:x}", self.id))
+ }
+
+ pub fn remove(&self, dir: &str, x: MessageId) {
+ assert!(std::process::Command::new("git")
+ .current_dir(self.repopath())
+ .arg("rm")
+ .arg("-q")
+ .arg("-f")
+ .arg(self.gpath(dir, x))
+ .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"])
+ .arg(format!("{by} <@designit>"))
+ .arg("-m")
+ .arg(msg)
+ .status()
+ .unwrap()
+ .success());
+ }
+
+ pub fn push(&self) {
+ assert!(std::process::Command::new("git")
+ .current_dir(self.repopath())
+ .arg("push")
+ .arg("-q")
+ .status()
+ .unwrap()
+ .success())
+ }
+
+ pub fn write(&self, dir: &str, x: MessageId, s: Schem) {
+ _ = std::fs::create_dir(self.repopath().join(dir));
+ let mut v = DataWrite::default();
+ s.serialize(&mut v).unwrap();
+ std::fs::write(self.path(dir, x), v.consume()).unwrap();
+ self.add();
+ }
+
+ pub fn add(&self) {
+ assert!(std::process::Command::new("git")
+ .current_dir(self.repopath())
+ .arg("add")
+ .arg(".")
+ .status()
+ .unwrap()
+ .success());
+ }
+}
+
+macro_rules! decl {
+ (
+ [$($threaded:literal)+ $(,)?];
+ $(
+ $repo:literal => [
+ $(
+ // xu64 => "dirname" : [label, label]
+ $ch:literal => $item:literal : [$($labels: expr),* $(,)?]
+ ),+ $(,)?
+ ];
+ )+
+ ) => {
+ use 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 },)?)+
+ };
+ };
+}
+macro_rules! person {
+ (&$x:literal) => {
+ Person::Role($x)
+ };
+ ($x:literal) => {
+ Person::User($x)
+ };
+}
+
+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,
+ admins: $admins,
+ chief: $chief,
+ deny_emoji: $deny,
+ auth: include_str!(concat!("../../", $repo, ".auth")),
+ ownership: &$repo_ident,
+ },)+
+ };
+ };
+}
+
+repos! {
+ DESIGN_IT 925674713429184564u64 => {
+ admins: &[person!(&925676016708489227)],
+ chief: 696196765564534825,
+ deny_emoji: 1192388789952319499u64,
+ },
+ ACP 1110086242177142854u64 => {
+ admins: &[person!(&1110439183190863913)],
+ chief: 696196765564534825,
+ deny_emoji: 1182469319641272370u64,
+ }
+}
+
+decl! {
+ [1129391545418797147u64];
+ 925674713429184564 => [
+925721957209636914u64 => "cryofluid" : [CRYOFLUID, CRYOFLUID_MIXER],
+925721791475904533u64 => "graphite" : [GRAPHITE, GRAPHITE_PRESS],
+925721824556359720u64 => "metaglass" : [METAGLASS, KILN],
+925721863525646356u64 => "phase-fabric" : [PHASE_FABRIC, PHASE_WEAVER],
+927036346869104693u64 => "plastanium" : [PLASTANIUM, PLASTANIUM_COMPRESSOR],
+925736419983515688u64 => "pyratite" : [PYRATITE, PYRATITE_MIXER],
+925736573037838397u64 => "blast-compound" : [BLAST_COMPOUND, BLAST_MIXER],
+927793648417009676u64 => "scrap" : [DISASSEMBLER, SCRAP],
+1198556531281637506u64 => "spore-press" : [OIL, SPORE_PRESS],
+1200308146460180520u64 => "oil-extractor" : [OIL, OIL_EXTRACTOR],
+1200301847387316317u64 => "rtg-gen" : [POWER, RTG_GENERATOR],
+1200308292744921088u64 => "cultivator" : [SPORE_POD, CULTIVATOR],
+1200305956689547324u64 => "graphite-multipress" : [GRAPHITE, MULTI_PRESS],
+1200306409036857384u64 => "silicon-crucible" : [SILICON, SILICON_CRUCIBLE],
+1198555991667646464u64 => "coal" : [COAL, COAL_CENTRIFUGE],
+925721763856404520u64 => "silicon" : [SILICON, SILICON_SMELTER],
+925721930814869524u64 => "surge-alloy" : [SURGE_ALLOY, SURGE_SMELTER],
+1141034314163826879u64 => "defensive-outpost" : [""],
+949529149800865862u64 => "drills" : [PRODUCTION],
+925729855574794311u64 => "logic-schems" : [MICRO_PROCESSOR],
+1185702384194818048u64 => "miscellaneous" : ["…"],
+1018541701431836803u64 => "combustion-gen" : [POWER, COMBUSTION_GENERATOR],
+927480650859184171u64 => "differential-gen" : [POWER, DIFFERENTIAL_GENERATOR],
+925719985987403776u64 => "impact-reactor" : [POWER, IMPACT_REACTOR],
+949740875817287771u64 => "steam-gen" : [POWER, STEAM_GENERATOR],
+926163105694752811u64 => "thorium-reactor" : [POWER, THORIUM_REACTOR],
+973234467357458463u64 => "carbide" : [CARBIDE, ""],
+1198527267933007893u64 => "erekir-defensive-outpost" : [""],
+973236445567410186u64 => "fissile-matter" : [FISSILE_MATTER, ""],
+1147887958351945738u64 => "electrolyzer" : [HYDROGEN, OZONE, ""],
+1202001032503365673u64 => "nitrogen" : [NITROGEN, ""],
+1202001055349477426u64 => "cyanogen" : [CYANOGEN, ""],
+1096157669112418454u64 => "mass-driver" : ["…", PLANET],
+973234248054104115u64 => "oxide" : [OXIDE, ""],
+973422874734002216u64 => "erekir-phase" : [PHASE_FABRIC, ""],
+973369188800413787u64 => "ccc" : ["", POWER],
+1218453338396430406u64 => "neoplasia-reactor": ["", POWER],
+1218453292045172817u64 => "flux-reactor": ["", POWER],
+1218452986788053012u64 => "pyrolisis-gen": ["", POWER],
+1147722735305367572u64 => "silicon-arc" : [SILICON, ""],
+974450769967341568u64 => "erekir-surge" : [SURGE_ALLOY, ""],
+973241041685737532u64 => "erekir-units" : ["[#ff9266][]"],
+1158818171139133490u64 => "unit-core" : [UNITS, CORE_NUCLEUS],
+1158818324210274365u64 => "unit-delivery" : [UNITS, FLARE],
+1158818598568075365u64 => "unit-raw" : [UNITS, PRODUCTION],
+1142181013779398676u64 => "unit-sand" : [UNITS, SAND],
+1222270513045438464u64 => "bore": [PRODUCTION],
+1226407271978766356u64 => "pulveriser": [PULVERIZER, SAND],
+1277138620863742003u64 => "melter": [MELTER, SLAG],
+1277138532355543070u64 => "separator": [SEPARATOR, SCRAP],
+ ];
+1110086242177142854u64 => [
+ 1276759410722738186u64 => "schems": ["plague"]
+ ];
+}
+
+macro_rules! chief {
+ ($c:ident) => {{
+ let repo = repos::REPOS[&$c.guild_id().unwrap().get()];
+ if repo.chief != $c.author().id.get() {
+ poise::send_reply(
+ $c,
+ poise::CreateReply::default()
+ .content(format!(
+ "access denied. only the chief <@{}> can use this command.",
+ repo.chief,
+ ))
+ .allowed_mentions(CreateAllowedMentions::default().empty_users().empty_roles()),
+ )
+ .await?;
+ return Ok(());
+ }
+ repo
+ }};
+}
+
+pub(crate) use chief;
diff --git a/src/expose.rs b/src/expose.rs
index a79d0e5..28d25e4 100644
--- a/src/expose.rs
+++ b/src/expose.rs
@@ -122,6 +122,7 @@ impl Server {
(
StatusCode::OK,
crate::bot::ownership::whos(
+ 925674713429184564,
match u64::from_str_radix(file.trim_end_matches(".msch"), 16) {
Ok(x) => x,
Err(_) => return (StatusCode::NOT_FOUND, "".into()),