diff --git a/Cargo.lock b/Cargo.lock index 5e28afd..3003383 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,6 +548,18 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gray_matter" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf2fb99fac0b821a4e61c61abff076324bb0e5c3b4a83815bbc3518a38971ad" +dependencies = [ + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "h2" version = "0.3.22" @@ -807,6 +819,12 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -1209,6 +1227,7 @@ dependencies = [ "color-eyre", "dotenvy", "env_logger", + "gray_matter", "log", "octocrab", "once_cell", @@ -1885,6 +1904,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.4.13" @@ -2411,6 +2439,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 1854b90..a7d48aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,14 @@ edition = "2021" repository = "https://github.com/PrismLauncher/refraction" license = "GPL-3.0-or-later" readme = "README.md" +build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[build-dependencies] +gray_matter = "0.2.6" +poise = "0.5.7" +serde = "1.0.193" +serde_json = "1.0.108" [dependencies] color-eyre = "0.6.2" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..647d720 --- /dev/null +++ b/build.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; +use std::path::Path; +use std::{env, fs}; + +use gray_matter::{engine, Matter}; + +include!("src/tags.rs"); + +/// generate the ChoiceParameter enum and tag data we will use in the `tag` command +#[allow(dead_code)] +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let generated = Path::new(&out_dir).join("generated.rs"); + + let tag_files: Vec = fs::read_dir(TAG_DIR) + .unwrap() + .map(|f| f.unwrap().file_name().to_string_lossy().to_string()) + .collect(); + + let tags: Vec = tag_files + .clone() + .into_iter() + .map(|name| { + let file_name = format!("{TAG_DIR}/{name}"); + let content = fs::read_to_string(&file_name).unwrap(); + + let matter = Matter::::new(); + let frontmatter: TagFrontmatter = matter + .parse(&content) + .data + .unwrap() + .deserialize() + .unwrap_or_else(|e| { + // actually handling the error since this is the most likely thing to fail -getchoo + panic!( + "Failed to parse file {file_name}! Here's what it looked like:\n{content}\n\nReported Error:\n{e}\n", + ) + }); + + Tag { + content, + file_name: name, + frontmatter, + } + }) + .collect(); + + let aliases: HashMap> = tags + .iter() + .filter_map(|t| { + t.frontmatter + .aliases + .clone() + .map(|aliases| (t.file_name.clone(), aliases)) + }) + .collect(); + + let formatted_names: Vec = tags + .iter() + .flat_map(|t| { + let mut res = Vec::from([t.file_name.replace(".md", "").replace('-', "_")]); + if let Some(tag_aliases) = aliases.get(&t.file_name) { + res.append(&mut tag_aliases.clone()) + } + + res + }) + .collect(); + + let tag_choice = format!( + r#" + #[allow(non_camel_case_types, clippy::upper_case_acronyms)] + #[derive(Clone, Debug, poise::ChoiceParameter)] + pub enum TagChoice {{ + {} + }}"#, + formatted_names.join(",\n") + ); + + let to_str = format!( + r#" + impl TagChoice {{ + fn as_str(&self) -> &str {{ + match &self {{ + {} + }} + }} + }} + "#, + formatted_names + .iter() + .map(|n| { + let file_name = n.replace('_', "-") + ".md"; + + // assume this is an alias if we can't match the file name + let name = if tag_files.contains(&file_name) { + file_name + } else { + aliases + .iter() + .find(|a| a.1.contains(n)) + .unwrap() + .0 + .to_string() + }; + + format!("Self::{n} => \"{name}\",") + }) + .collect::>() + .join("\n") + ); + + let contents = Vec::from([tag_choice, to_str]).join("\n\n"); + + fs::write(generated, contents).unwrap(); + println!( + "cargo:rustc-env=TAGS={}", + // make sure we can deserialize with env! at runtime + serde_json::to_string(&tags).unwrap() + ); +} diff --git a/src/commands/general/mod.rs b/src/commands/general/mod.rs index c322c73..f9385d4 100644 --- a/src/commands/general/mod.rs +++ b/src/commands/general/mod.rs @@ -3,9 +3,11 @@ mod members; mod rory; mod say; mod stars; +mod tag; pub use joke::joke; pub use members::members; pub use rory::rory; pub use say::say; pub use stars::stars; +pub use tag::tag; diff --git a/src/commands/general/tag.rs b/src/commands/general/tag.rs new file mode 100644 index 0000000..d312bb5 --- /dev/null +++ b/src/commands/general/tag.rs @@ -0,0 +1,57 @@ +#![allow(non_camel_case_types, clippy::upper_case_acronyms)] +use crate::tags::Tag; +use crate::{consts, Context}; +use std::env; + +use color_eyre::eyre::{eyre, Result}; +use once_cell::sync::Lazy; +use poise::serenity_prelude::{Color, User}; + +include!(concat!(env!("OUT_DIR"), "/generated.rs")); +static TAGS: Lazy> = Lazy::new(|| serde_json::from_str(env!("TAGS")).unwrap()); + +/// Send a tag +#[poise::command(slash_command)] +pub async fn tag( + ctx: Context<'_>, + #[description = "the copypasta you want to send"] name: TagChoice, + user: Option, +) -> Result<()> { + let tag_file = name.as_str(); + let tag = TAGS + .iter() + .find(|t| t.file_name == tag_file) + .ok_or_else(|| eyre!("Tried to get non-existent tag: {tag_file}"))?; + + let frontmatter = &tag.frontmatter; + + ctx.send(|m| { + if let Some(user) = user { + m.content(format!("<@{}>", user.id)); + } + + m.embed(|e| { + if let Some(color) = &frontmatter.color { + let color = *consts::COLORS + .get(color.as_str()) + .unwrap_or(&Color::default()); + e.color(color); + } + + if let Some(image) = &frontmatter.image { + e.image(image); + } + + if let Some(fields) = &frontmatter.fields { + for field in fields { + e.field(&field.name, &field.value, field.inline); + } + } + + e + }) + }) + .await?; + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 67e2b1e..cbb13c1 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -13,6 +13,7 @@ pub fn to_global_commands() -> Vec> { general::rory(), general::say(), general::stars(), + general::tag(), moderation::ban_user(), moderation::kick_user(), ] diff --git a/src/consts.rs b/src/consts.rs index 63d5e12..1c305fe 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,14 +1,15 @@ use std::collections::HashMap; use once_cell::sync::Lazy; +use poise::serenity_prelude::Color; -pub static COLORS: Lazy> = Lazy::new(|| { +pub static COLORS: Lazy> = Lazy::new(|| { HashMap::from([ - ("red", (239, 68, 68)), - ("green", (34, 197, 94)), - ("blue", (96, 165, 250)), - ("yellow", (253, 224, 71)), - ("orange", (251, 146, 60)), + ("red", Color::from((239, 68, 68))), + ("green", Color::from((34, 197, 94))), + ("blue", Color::from((96, 165, 250))), + ("yellow", Color::from((253, 224, 71))), + ("orange", Color::from((251, 146, 60))), ]) }); diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index bf60a5f..7d80556 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -9,8 +9,8 @@ mod eta; pub async fn handle( ctx: &Context, event: &Event<'_>, - framework: FrameworkContext<'_, Data, Report>, - data: &Data, + _framework: FrameworkContext<'_, Data, Report>, + _data: &Data, ) -> Result<()> { match event { Event::Ready { data_about_bot } => { diff --git a/src/main.rs b/src/main.rs index 6d5b31b..b26863d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod commands; mod config; mod consts; mod handlers; +mod tags; mod utils; type Context<'a> = poise::Context<'a, Data, Report>; diff --git a/src/tags.rs b/src/tags.rs new file mode 100644 index 0000000..a174fab --- /dev/null +++ b/src/tags.rs @@ -0,0 +1,21 @@ +use poise::serenity_prelude::EmbedField; +use serde::{Deserialize, Serialize}; + +#[allow(dead_code)] +pub const TAG_DIR: &str = "tags"; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TagFrontmatter { + pub title: String, + pub aliases: Option>, + pub color: Option, + pub image: Option, + pub fields: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Tag { + pub content: String, + pub file_name: String, + pub frontmatter: TagFrontmatter, +} diff --git a/tags/FTB.md b/tags/FTB.md index 4e49dbc..f78a016 100644 --- a/tags/FTB.md +++ b/tags/FTB.md @@ -1,7 +1,6 @@ --- title: Can I install/download FTB packs directly from Prism? color: orange -aliases: ['FTB'] --- You cannot download FTB packs directly from Prism Launcher. diff --git a/tags/log.md b/tags/log.md index c0a9756..8299879 100644 --- a/tags/log.md +++ b/tags/log.md @@ -1,7 +1,7 @@ --- title: Upload Logs color: orange -aliases: ['sendlog', 'logs', '🪵'] +aliases: ['sendlog', 'logs'] image: https://cdn.discordapp.com/attachments/1031694870756204566/1156971972232740874/image.png ---