From 861e5fc7e54486d1ae9b2723f9f896162f59fd2c Mon Sep 17 00:00:00 2001 From: Louis Bailleau Date: Wed, 19 Jul 2023 23:15:45 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=9A=A7=20restructure=20curren?= =?UTF-8?q?t=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cargo/config.toml | 2 + Cargo.lock | 61 +++++++++-- Cargo.toml | 43 +++++--- src/constants.rs | 1 + src/{curse_api.rs => curse_api.rs.old} | 0 src/curse_api/minecraft.rs | 38 +++++++ src/curse_api/mod.rs | 28 +++++ src/curse_api/modloader.rs | 5 + src/errors.rs | 35 ++++++ src/main.rs | 144 ++----------------------- src/main.rs.old | 141 ++++++++++++++++++++++++ src/minecraft/minecraft.rs | 7 ++ src/minecraft/mod.rs | 2 + src/minecraft/modloader.rs | 7 ++ src/questions/mc_versions.rs | 57 ++++++++++ src/questions/mod.rs | 21 ++++ src/questions/modloader.rs | 40 +++++++ 17 files changed, 477 insertions(+), 155 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 src/constants.rs rename src/{curse_api.rs => curse_api.rs.old} (100%) create mode 100644 src/curse_api/minecraft.rs create mode 100644 src/curse_api/mod.rs create mode 100644 src/curse_api/modloader.rs create mode 100644 src/errors.rs create mode 100644 src/main.rs.old create mode 100644 src/minecraft/minecraft.rs create mode 100644 src/minecraft/mod.rs create mode 100644 src/minecraft/modloader.rs create mode 100644 src/questions/mc_versions.rs create mode 100644 src/questions/mod.rs create mode 100644 src/questions/modloader.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..e4c5685 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +CURSE_API_TOKEN = "$2a$10$FSMPrnX2TyC9kluMfAWvHuGqGxa7qKuvXpClTB/vB8LE3fVu9ic9e" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1681726..63d0ae0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,8 @@ dependencies = [ "reqwest", "serde", "serde_json", + "thiserror", + "time", "tokio", ] @@ -611,18 +613,18 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" dependencies = [ "proc-macro2", ] @@ -962,9 +964,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "syn" -version = "2.0.16" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -995,6 +997,53 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index a49234c..0914267 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,37 @@ [package] -name = "flowupdater-json-creator" +name = "flowupdater-json-creator" description = "Create JSON for flow-updater in a TUI" -authors = ["Zuygui", "Bricklou"] -license = "MIT" -version = "1.1.1" -edition = "2021" -readme = "README.md" -homepage = "https://github.com/zuygui/flowupdater-json-creator" -repository = "https://github.com/zuygui/flowupdater-json-creator" -keywords = ["cli", "tui", "json", "flow-updater", "flowupdater", "curseforge", "eternalapi"] -categories = ["command-line-utilities", "development-tools"] +authors = ["Zuygui", "Bricklou"] +license = "MIT" +version = "1.1.1" +edition = "2021" +readme = "README.md" +homepage = "https://github.com/zuygui/flowupdater-json-creator" +repository = "https://github.com/zuygui/flowupdater-json-creator" +keywords = [ + "cli", + "tui", + "json", + "flow-updater", + "flowupdater", + "curseforge", + "eternalapi", +] +categories = ["command-line-utilities", "development-tools"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] } -serde = { version = "1", features = ["derive"] } -reqwest = { version = "0.11", features = ["json", "rustls-tls"] } -serde_json = { version = "1.0.96" } -requestty = { version = "0.5.0" } +tokio = { version = "1", default-features = false, features = [ + "rt-multi-thread", + "macros", +] } +serde = { version = "1", features = ["derive"] } +reqwest = { version = "0.11", features = ["json", "rustls-tls"] } +serde_json = { version = "1.0.96" } +requestty = { version = "0.5.0" } +thiserror = { version = "1" } +time = { version = "0.3", features = ["serde", "serde-well-known"] } [profile.release] codegen-units = 1 diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..04b5a4f --- /dev/null +++ b/src/constants.rs @@ -0,0 +1 @@ +pub const TOKEN: &str = env!("CURSE_API_TOKEN"); \ No newline at end of file diff --git a/src/curse_api.rs b/src/curse_api.rs.old similarity index 100% rename from src/curse_api.rs rename to src/curse_api.rs.old diff --git a/src/curse_api/minecraft.rs b/src/curse_api/minecraft.rs new file mode 100644 index 0000000..ae82a5e --- /dev/null +++ b/src/curse_api/minecraft.rs @@ -0,0 +1,38 @@ +use crate::errors::CreatorError; + +use super::{CurseApi, CURSE_API_URL}; +use serde::Deserialize; +use time::OffsetDateTime; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MinecraftVersion { + pub id: u32, + pub game_version_id: u32, + pub version_string: String, + pub jar_download_url: String, + pub json_download_url: String, + pub approved: bool, + #[serde(with = "time::serde::rfc3339")] + pub date_modified: OffsetDateTime, + pub game_version_type_id: u32, + pub game_version_status: u32, + pub game_version_type_status: u32, +} + +#[derive(Debug, serde::Deserialize)] +pub struct MinecraftVersionsList { + pub data: Vec +} + +impl CurseApi { + pub async fn get_minecraft_versions(&self) -> Result { + let versions = self.http_client.get(format!("{}{}", CURSE_API_URL, "minecraft/version").as_str()) + .send() + .await? + .json::() + .await?; + + Ok(versions) + } +} \ No newline at end of file diff --git a/src/curse_api/mod.rs b/src/curse_api/mod.rs new file mode 100644 index 0000000..02edb4c --- /dev/null +++ b/src/curse_api/mod.rs @@ -0,0 +1,28 @@ +use reqwest::header::HeaderMap; + +use crate::errors::CreatorError; + +mod minecraft; +mod modloader; + +pub static CURSE_API_URL: &str = "https://api.curseforge.com/v1/"; + +pub struct CurseApi { + http_client: reqwest::Client, +} + +impl CurseApi { + pub fn new(api_token: S) -> Result + where + S: AsRef, + { + let mut headers = HeaderMap::new(); + headers.insert("X-Api-Key", api_token.as_ref().parse().unwrap()); + + let http_client = reqwest::Client::builder() + .default_headers(headers) + .build()?; + + Ok(Self { http_client }) + } +} diff --git a/src/curse_api/modloader.rs b/src/curse_api/modloader.rs new file mode 100644 index 0000000..77dc49d --- /dev/null +++ b/src/curse_api/modloader.rs @@ -0,0 +1,5 @@ +use super::CurseApi; + +impl CurseApi { + +} \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..b79146f --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,35 @@ +#[derive(thiserror::Error, Debug)] +pub enum CreatorError { + #[error("Invalid Mod Loader")] + InvalidModLoader, + + #[error("Invalid Minecraft Version")] + InvalidMinecraftVersion, + + #[error("Prompt Error: {0}")] + PromptError(requestty::ErrorKind), + + #[error("IO Error: {0}")] + IoError(std::io::Error), + + #[error("{0}")] + HttpError(reqwest::Error) +} + +impl From for CreatorError { + fn from(err: requestty::ErrorKind) -> Self { + CreatorError::PromptError(err) + } +} + +impl From for CreatorError { + fn from(err: std::io::Error) -> Self { + CreatorError::IoError(err) + } +} + +impl From for CreatorError { + fn from(err: reqwest::Error) -> Self { + CreatorError::HttpError(err) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 02ff9cb..ae6ed73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,141 +1,17 @@ -use curse_api::{CurseApiClient, CurseApiClientBuilder, CurseMod}; +use errors::CreatorError; +use questions::Questions; +mod errors; +mod questions; +mod minecraft; mod curse_api; -mod json_creator; +mod constants; #[tokio::main] -async fn main() -> Result<(), Box> { - let modloader = requestty::Question::select("modloader") - .message("What Mod Loader do you use ?") - .choices(vec!["Forge", "Fabric"]) - .build(); - - // get the answer list item index and text - let modloader = requestty::prompt_one(modloader)? - .as_list_item() - .unwrap() - .index; - - let possibles_versions = match modloader { - 0 => curse_api::FORGE_COMPATIBLES_VERSIONS.to_vec(), - 1 => curse_api::FABRIC_COMPATIBLES_VERSIONS.to_vec(), - _ => panic!("Invalid modloader"), - }; - - let minecraft_version = requestty::Question::select("minecraft_version") - .message("What Minecraft version do you use ?") - .choices(possibles_versions) - .build(); - - let binding = requestty::prompt_one(minecraft_version)?; - let minecraft_version = &binding.as_list_item().unwrap().text; - - let curse_client: CurseApiClient = CurseApiClientBuilder::new() - .with_api_token("$2a$10$FSMPrnX2TyC9kluMfAWvHuGqGxa7qKuvXpClTB/vB8LE3fVu9ic9e") - .with_game_version(minecraft_version.to_string()) - .with_mod_loader(match modloader { - 0 => curse_api::ModLoader::Forge, - 1 => curse_api::ModLoader::Fabric, - _ => { - panic!("Invalid modloader"); - } - }) - .build()?; - - let mut mod_list: Vec = Vec::new(); - let mut mod_added = false; // Nouvelle variable pour vérifier si au moins un mod a été ajouté - - loop { - let user_wanna_add_mod = check_if_user_wanna_add_mod(); - - if user_wanna_add_mod { - let mods = add_mod(); - if let Some(founded_mod) = select_founded_mod(curse_client.clone(), mods).await { - mod_list.push(founded_mod); - mod_added = true; // Un mod a été ajouté - } - } else { - break; - } - } - - if !mod_added { - println!("No mods were added."); - return Ok(()); - } - - json_creator::compile_mods_to_json(mod_list); - - println!("The JSON file is successfully created at : ./mods_list.json"); +async fn main() -> Result<(), CreatorError> { + let mut questions = Questions::new()?; + questions.ask_minecraft().await?; + questions.ask_modloader()?; Ok(()) } - -fn check_if_user_wanna_add_mod() -> bool { - let add_mod_question = requestty::Question::confirm("add_mod") - .message("Do you want to add a mod ?") - .build(); - - let binding = requestty::prompt_one(add_mod_question) - .unwrap() - .as_bool() - .unwrap(); - return binding; -} - -fn add_mod() -> String { - let what_mod = requestty::Question::input("what_mod") - .message("What mod do you want to add ?") - .build(); - - let binding = requestty::prompt_one(what_mod).unwrap(); - let mod_name = &binding.as_string(); - mod_name.unwrap().to_string() -} - -#[derive(Clone)] -struct Mod { - pub name: String, - pub id: isize, -} -async fn select_founded_mod(curse_client: CurseApiClient, query: String) -> Option { - let mods = curse_client - .search_mod(query) - .await - .ok()? - .data - .iter() - .map(|mod_| Mod { - name: mod_.name.clone(), - id: mod_.id.clone(), - }) - .collect::>(); - - if mods.is_empty() { - println!("No mods found."); - return None; - } - - let choices = requestty::Question::select("mod") - .message("Which mod do you want to add?") - .choices( - mods.iter() - .map(|mod_| mod_.name.clone()) - .collect::>(), - ) - .build(); - - let binding = requestty::prompt_one(choices).unwrap(); - let mod_data = &mods[binding.as_list_item().unwrap().index]; - - let file_id = curse_client - .get_mod_file_id(mod_data.id, mod_data.name.clone()) - .await - .ok()?; - - Some(CurseMod { - name: mod_data.name.clone(), - mod_id: mod_data.id.clone(), - file_id: file_id.file_id, - }) -} diff --git a/src/main.rs.old b/src/main.rs.old new file mode 100644 index 0000000..02ff9cb --- /dev/null +++ b/src/main.rs.old @@ -0,0 +1,141 @@ +use curse_api::{CurseApiClient, CurseApiClientBuilder, CurseMod}; + +mod curse_api; +mod json_creator; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let modloader = requestty::Question::select("modloader") + .message("What Mod Loader do you use ?") + .choices(vec!["Forge", "Fabric"]) + .build(); + + // get the answer list item index and text + let modloader = requestty::prompt_one(modloader)? + .as_list_item() + .unwrap() + .index; + + let possibles_versions = match modloader { + 0 => curse_api::FORGE_COMPATIBLES_VERSIONS.to_vec(), + 1 => curse_api::FABRIC_COMPATIBLES_VERSIONS.to_vec(), + _ => panic!("Invalid modloader"), + }; + + let minecraft_version = requestty::Question::select("minecraft_version") + .message("What Minecraft version do you use ?") + .choices(possibles_versions) + .build(); + + let binding = requestty::prompt_one(minecraft_version)?; + let minecraft_version = &binding.as_list_item().unwrap().text; + + let curse_client: CurseApiClient = CurseApiClientBuilder::new() + .with_api_token("$2a$10$FSMPrnX2TyC9kluMfAWvHuGqGxa7qKuvXpClTB/vB8LE3fVu9ic9e") + .with_game_version(minecraft_version.to_string()) + .with_mod_loader(match modloader { + 0 => curse_api::ModLoader::Forge, + 1 => curse_api::ModLoader::Fabric, + _ => { + panic!("Invalid modloader"); + } + }) + .build()?; + + let mut mod_list: Vec = Vec::new(); + let mut mod_added = false; // Nouvelle variable pour vérifier si au moins un mod a été ajouté + + loop { + let user_wanna_add_mod = check_if_user_wanna_add_mod(); + + if user_wanna_add_mod { + let mods = add_mod(); + if let Some(founded_mod) = select_founded_mod(curse_client.clone(), mods).await { + mod_list.push(founded_mod); + mod_added = true; // Un mod a été ajouté + } + } else { + break; + } + } + + if !mod_added { + println!("No mods were added."); + return Ok(()); + } + + json_creator::compile_mods_to_json(mod_list); + + println!("The JSON file is successfully created at : ./mods_list.json"); + + Ok(()) +} + +fn check_if_user_wanna_add_mod() -> bool { + let add_mod_question = requestty::Question::confirm("add_mod") + .message("Do you want to add a mod ?") + .build(); + + let binding = requestty::prompt_one(add_mod_question) + .unwrap() + .as_bool() + .unwrap(); + return binding; +} + +fn add_mod() -> String { + let what_mod = requestty::Question::input("what_mod") + .message("What mod do you want to add ?") + .build(); + + let binding = requestty::prompt_one(what_mod).unwrap(); + let mod_name = &binding.as_string(); + mod_name.unwrap().to_string() +} + +#[derive(Clone)] +struct Mod { + pub name: String, + pub id: isize, +} +async fn select_founded_mod(curse_client: CurseApiClient, query: String) -> Option { + let mods = curse_client + .search_mod(query) + .await + .ok()? + .data + .iter() + .map(|mod_| Mod { + name: mod_.name.clone(), + id: mod_.id.clone(), + }) + .collect::>(); + + if mods.is_empty() { + println!("No mods found."); + return None; + } + + let choices = requestty::Question::select("mod") + .message("Which mod do you want to add?") + .choices( + mods.iter() + .map(|mod_| mod_.name.clone()) + .collect::>(), + ) + .build(); + + let binding = requestty::prompt_one(choices).unwrap(); + let mod_data = &mods[binding.as_list_item().unwrap().index]; + + let file_id = curse_client + .get_mod_file_id(mod_data.id, mod_data.name.clone()) + .await + .ok()?; + + Some(CurseMod { + name: mod_data.name.clone(), + mod_id: mod_data.id.clone(), + file_id: file_id.file_id, + }) +} diff --git a/src/minecraft/minecraft.rs b/src/minecraft/minecraft.rs new file mode 100644 index 0000000..9620494 --- /dev/null +++ b/src/minecraft/minecraft.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub enum McVersion { + Latest, + Version(String), +} \ No newline at end of file diff --git a/src/minecraft/mod.rs b/src/minecraft/mod.rs new file mode 100644 index 0000000..086efe3 --- /dev/null +++ b/src/minecraft/mod.rs @@ -0,0 +1,2 @@ +pub mod modloader; +pub mod minecraft; \ No newline at end of file diff --git a/src/minecraft/modloader.rs b/src/minecraft/modloader.rs new file mode 100644 index 0000000..a6eab8f --- /dev/null +++ b/src/minecraft/modloader.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub enum ModLoaderType { + Fabric, + Forge, +} diff --git a/src/questions/mc_versions.rs b/src/questions/mc_versions.rs new file mode 100644 index 0000000..38c1556 --- /dev/null +++ b/src/questions/mc_versions.rs @@ -0,0 +1,57 @@ +use crate::{errors::CreatorError, minecraft::minecraft::McVersion}; + +use super::Questions; + +impl Questions { + pub async fn ask_minecraft(&mut self) -> Result<(), CreatorError> { + // Ask the user if they want to use the latest version or a specific one + let version_type = requestty::Question::select("version_type") + .message("What Minecraft version type would you like to use ?") + .choices(vec!["Latest","A Specific one"]) + .build(); + + // Get the answers list item index and text + let version_type = requestty::prompt_one(version_type)?; + let version_type = version_type.as_list_item(); + + if version_type.is_none() { + return Err(CreatorError::InvalidMinecraftVersion); + } + + let version_type = version_type.unwrap(); + + if version_type.text.is_empty() { + return Err(CreatorError::InvalidMinecraftVersion); + } + + if version_type.text == "Latest" { + self.mc_versions = McVersion::Latest; + return Ok(()); + } + + let versions = self.curse_api.get_minecraft_versions().await?; + let versions = versions.data.iter().map(|v| v.version_string.to_string()).collect::>(); + + let v = requestty::Question::select("minecraft") + .message("What Minecraft version would you like to use ?") + .page_size(10) + .choices(versions) + .build(); + + // Get the answers list item index and text + let result = requestty::prompt_one(v)?; + let result = result.as_list_item(); + + match result { + Some(item) => { + let version = item.text.to_string(); + self.mc_versions = McVersion::Version(version); + }, + None => { + return Err(CreatorError::InvalidMinecraftVersion); + } + } + + Ok(()) + } +} \ No newline at end of file diff --git a/src/questions/mod.rs b/src/questions/mod.rs new file mode 100644 index 0000000..de9ea51 --- /dev/null +++ b/src/questions/mod.rs @@ -0,0 +1,21 @@ +use crate::{minecraft::{modloader::ModLoaderType, minecraft::McVersion}, curse_api::CurseApi, constants, errors::CreatorError}; + +mod modloader; +mod mc_versions; + +pub struct Questions { + mod_loader: Option, + mc_versions: McVersion, + + curse_api: CurseApi, +} + +impl Questions { + pub fn new() -> Result { + Ok(Self { + mod_loader: None, + mc_versions: McVersion::Latest, + curse_api: CurseApi::new(constants::TOKEN)?, + }) + } +} \ No newline at end of file diff --git a/src/questions/modloader.rs b/src/questions/modloader.rs new file mode 100644 index 0000000..5e64c4a --- /dev/null +++ b/src/questions/modloader.rs @@ -0,0 +1,40 @@ +use crate::{questions::ModLoaderType, errors::CreatorError}; + +use super::Questions; + +impl Questions { + pub fn ask_modloader(&mut self) -> Result<(), CreatorError> { + let ml = requestty::Question::select("modloader") + .message("What Mod Loader would you like to use ?") + .choices(vec!["Fabric","Forge","None"]) + .build(); + + // Get the answers list item index and text + let result = requestty::prompt_one(ml)?; + let result = result.as_list_item(); + + match result { + Some(item) => { + match item.text.as_str() { + "Fabric" => { + self.mod_loader = Some(ModLoaderType::Fabric); + }, + "Forge" => { + self.mod_loader = Some(ModLoaderType::Forge); + }, + "None" => { + self.mod_loader = None; + }, + _ => { + return Err(CreatorError::InvalidModLoader); + } + } + }, + None => { + return Err(CreatorError::InvalidModLoader); + } + } + + Ok(()) + } +} \ No newline at end of file