diff --git a/Cargo.toml b/Cargo.toml index 22509fc..87fdfaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,3 +49,4 @@ thiserror = "1.0" ferinth = "2.10" furse = "1.5" home = "0.5" +bitflags = "2.4.2" diff --git a/src/add.rs b/src/add.rs deleted file mode 100644 index c0e8186..0000000 --- a/src/add.rs +++ /dev/null @@ -1,312 +0,0 @@ -use crate::{ - config::structs::{Mod, ModIdentifier, ModIdentifierRef, ModLoader, Profile}, - upgrade::{ - check::{game_version_check, mod_loader_check}, - mod_downloadable, - }, -}; -use reqwest::StatusCode; - -#[derive(thiserror::Error, Debug)] -#[error("{}: {}", self, .0)] -pub enum Error { - #[error( - "The developer of this project has denied third party applications from downloading it" - )] - /// The user can manually download the mod and place it in the `user` folder of the output directory to mitigate this. - /// However, they will have to manually update the mod. - DistributionDenied, - #[error("The project has already been added")] - AlreadyAdded, - #[error("The project does not exist")] - DoesNotExist, - #[error("The project is not compatible")] - Incompatible, - #[error("The project is not a mod")] - NotAMod, - #[error("Invalid identifier")] - InvalidIdentifier, - GitHubError(octocrab::Error), - ModrinthError(ferinth::Error), - CurseForgeError(furse::Error), -} -type Result = std::result::Result; - -impl From for Error { - fn from(err: furse::Error) -> Self { - if let furse::Error::ReqwestError(source) = &err { - if Some(StatusCode::NOT_FOUND) == source.status() { - Self::DoesNotExist - } else { - Self::CurseForgeError(err) - } - } else { - Self::CurseForgeError(err) - } - } -} - -impl From for Error { - fn from(err: ferinth::Error) -> Self { - if let ferinth::Error::ReqwestError(source) = &err { - if Some(StatusCode::NOT_FOUND) == source.status() { - Self::DoesNotExist - } else { - Self::ModrinthError(err) - } - } else { - Self::ModrinthError(err) - } - } -} - -impl From for Error { - fn from(err: octocrab::Error) -> Self { - if let octocrab::Error::GitHub { source, .. } = &err { - if &source.message == "Not Found" { - return Self::DoesNotExist; - } - } - Self::GitHubError(err) - } -} - -pub async fn add_multiple( - modrinth: &ferinth::Ferinth, - curseforge: &furse::Furse, - github: &octocrab::Octocrab, - profile: &mut Profile, - identifiers: Vec, -) -> (Vec, Vec<(String, Error)>) { - let mut success_names = Vec::new(); - let mut failures = Vec::new(); - - for identifier in identifiers { - match add_single( - modrinth, - curseforge, - github, - profile, - &identifier, - true, - true, - true, - ) - .await - { - Ok(name) => success_names.push(name), - Err(err) => failures.push(( - identifier, - if matches!(err, Error::ModrinthError(ferinth::Error::InvalidIDorSlug)) { - Error::InvalidIdentifier - } else { - err - }, - )), - } - } - (success_names, failures) -} - -#[allow(clippy::too_many_arguments)] -pub async fn add_single( - modrinth: &ferinth::Ferinth, - curseforge: &furse::Furse, - github: &octocrab::Octocrab, - profile: &mut Profile, - identifier: &str, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result { - if let Ok(project_id) = identifier.parse() { - self::curseforge( - curseforge, - project_id, - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await - } else if identifier.matches('/').count() == 1 { - let split = identifier.split('/').collect::>(); - - self::github( - &github.repos(split[0], split[1]), - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await - } else { - self::modrinth( - modrinth, - identifier, - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await - .map(|o| o.0) - } -} - -/// Check if the repo of `repo_handler` exists, releases mods, and is compatible with `profile`. -/// If so, add it to the `profile`. -/// -/// Returns the name of the repository to display to the user -pub async fn github( - repo_handler: &octocrab::repos::RepoHandler<'_>, - profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result { - let repo = repo_handler.get().await?; - let repo_name = (repo.owner.clone().unwrap().login, repo.name.clone()); - - // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == repo.name.to_lowercase() - || ModIdentifierRef::GitHubRepository(&repo_name) == mod_.identifier.as_ref() - }) { - return Err(Error::AlreadyAdded); - } - - if perform_checks { - let releases = repo_handler.releases().list().send().await?.items; - - // Check if jar files are released - if !releases - .iter() - .flat_map(|r| &r.assets) - .any(|a| a.name.ends_with(".jar")) - { - return Err(Error::NotAMod); - } - - // Check if the repo is compatible - mod_downloadable::get_latest_compatible_asset( - &releases, - profile.get_version(check_game_version), - profile.get_loader(check_game_version), - ) - .ok_or(Error::Incompatible)?; - } - - // Add it to the profile - profile.mods.push(Mod { - name: repo.name.trim().to_string(), - identifier: ModIdentifier::GitHubRepository(repo_name), - check_game_version, - check_mod_loader, - }); - - Ok(repo.name) -} - -use ferinth::structures::project::{DonationLink, ProjectType}; - -/// Check if the project of `project_id` exists, is a mod, and is compatible with `profile`. -/// If so, add it to the `profile`. -/// -/// Returns the project name and donation URLs to display to the user -pub async fn modrinth( - modrinth: &ferinth::Ferinth, - project_id: &str, - profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result<(String, Vec)> { - let project = modrinth.get_project(project_id).await?; - - // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == project.title.to_lowercase() - || ModIdentifierRef::ModrinthProject(&project.id) == mod_.identifier.as_ref() - }) { - Err(Error::AlreadyAdded) - - // Check if the project is a mod - } else if project.project_type != ProjectType::Mod { - Err(Error::NotAMod) - - // Check if the project is compatible - } else if !perform_checks // Short circuit if the checks should not be performed - || (game_version_check( - profile.get_version(check_game_version), - &project.game_versions, - ) && (mod_loader_check(profile.get_loader(check_mod_loader), &project.loaders) | ( - // Fabric backwards compatibility in Quilt - profile.mod_loader == ModLoader::Quilt && mod_loader_check(Some(ModLoader::Fabric), &project.loaders) - ))) - { - // Add it to the profile - profile.mods.push(Mod { - name: project.title.trim().to_string(), - identifier: ModIdentifier::ModrinthProject(project.id), - check_game_version, - check_mod_loader, - }); - - Ok((project.title, project.donation_urls)) - } else { - Err(Error::Incompatible) - } -} - -/// Check if the mod of `project_id` exists, is a mod, and is compatible with `profile`. -/// If so, add it to the `profile`. -/// -/// Returns the mod name to display to the user -pub async fn curseforge( - curseforge: &furse::Furse, - project_id: i32, - profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result { - let project = curseforge.get_mod(project_id).await?; - - // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == project.name.to_lowercase() - || ModIdentifier::CurseForgeProject(project.id) == mod_.identifier - }) { - Err(Error::AlreadyAdded) - - // Check if it can be downloaded by third-parties - } else if Some(false) == project.allow_mod_distribution { - Err(Error::DistributionDenied) - - // Check if the project is a Minecraft mod - } else if !project.links.website_url.as_str().contains("mc-mods") { - Err(Error::NotAMod) - - // Check if the project is compatible - } else { - if perform_checks { - mod_downloadable::get_latest_compatible_file( - curseforge.get_mod_files(project.id).await?, - profile.get_version(check_game_version), - profile.get_loader(check_game_version), - ) - .ok_or(Error::Incompatible)?; - } - - // Add it to the profile - profile.mods.push(Mod { - name: project.name.trim().to_string(), - identifier: ModIdentifier::CurseForgeProject(project.id), - check_game_version, - check_mod_loader, - }); - - Ok(project.name) - } -} diff --git a/src/add/curseforge.rs b/src/add/curseforge.rs new file mode 100644 index 0000000..cc09c97 --- /dev/null +++ b/src/add/curseforge.rs @@ -0,0 +1,85 @@ +use crate::{ + config::structs::{Mod, ModIdentifier, Profile}, + upgrade::mod_downloadable, +}; + +use super::Checks; + +fn project_exist(profile: &Profile, project: &furse::structures::mod_structs::Mod) -> bool { + profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == project.name.to_lowercase() + || ModIdentifier::CurseForgeProject(project.id) == mod_.identifier + }) +} + +fn distrubution_denied(project: &furse::structures::mod_structs::Mod) -> bool { + project.allow_mod_distribution.map_or(false, |b| !b) +} + +fn is_minecraft_mod(project: &furse::structures::mod_structs::Mod) -> bool { + project.links.website_url.as_str().contains("mc-mods") +} + +async fn is_project_compatible( + curseforge: &furse::Furse, + project: &furse::structures::mod_structs::Mod, + profile: &Profile, + check_game_version: bool, +) -> super::Result { + Ok(mod_downloadable::get_latest_compatible_file( + curseforge.get_mod_files(project.id).await?, + profile.get_version(check_game_version), + profile.get_loader(check_game_version), + ) + .is_some()) +} + +/// Check if the mod of `project_id` exists, is a mod, and is compatible with `profile`. +/// If so, add it to the `profile`. +/// +/// Returns the mod name to display to the user +pub async fn curseforge( + curseforge: &furse::Furse, + project_id: i32, + profile: &mut Profile, + checks: &Checks, +) -> super::Result { + let project = curseforge.get_mod(project_id).await?; + + // Check if project has already been added + if project_exist(profile, &project) { + return Err(super::Error::AlreadyAdded); + } + + // Check if it can be downloaded by third-parties + if distrubution_denied(&project) { + return Err(super::Error::DistributionDenied); + } + + // Check if the project is a Minecraft mod + if !is_minecraft_mod(&project) { + return Err(super::Error::NotAMod); + } + + // Check if the project is compatible + if checks.contains(Checks::ENABLED) + && !is_project_compatible( + curseforge, + &project, + profile, + checks.contains(Checks::GAME_VERSION), + ) + .await? + { + return Err(super::Error::Incompatible); + } + + // Add it to the profile + profile.mods.push(Mod::new( + project.name.trim(), + ModIdentifier::CurseForgeProject(project.id), + checks, + )); + + Ok(project.name) +} diff --git a/src/add/github.rs b/src/add/github.rs new file mode 100644 index 0000000..b3d87b0 --- /dev/null +++ b/src/add/github.rs @@ -0,0 +1,83 @@ +use octocrab::models::{repos::Release, Repository}; + +use crate::{ + config::structs::{Mod, ModIdentifier, ModIdentifierRef, Profile}, + upgrade::mod_downloadable, +}; + +use super::Checks; + +fn project_exist(profile: &Profile, repo: &Repository, repo_name: &(String, String)) -> bool { + profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == repo.name.to_lowercase() + || ModIdentifierRef::GitHubRepository(repo_name) == mod_.identifier.as_ref() + }) +} + +fn contains_jar_asset(releases: &[Release]) -> bool { + releases + .iter() + .flat_map(|r| &r.assets) + .any(|a| a.name.ends_with(".jar")) +} + +async fn is_project_compatible( + profile: &Profile, + releases: &[Release], + check_game_version: bool, +) -> super::Result { + Ok(mod_downloadable::get_latest_compatible_asset( + releases, + profile.get_version(check_game_version), + profile.get_loader(check_game_version), + ) + .is_some()) +} + +/// Check if the repo of `repo_handler` exists, releases mods, and is compatible with `profile`. +/// If so, add it to the `profile`. +/// +/// Returns the name of the repository to display to the user +pub async fn github( + repo_handler: &octocrab::repos::RepoHandler<'_>, + profile: &mut Profile, + checks: &Checks, +) -> super::Result { + let repo = repo_handler.get().await?; + let repo_name = ( + repo.owner + .clone() + .expect("Owner name not found in git repo") + .login, + repo.name.clone(), + ); + + // Check if project has already been added + if project_exist(profile, &repo, &repo_name) { + return Err(super::Error::AlreadyAdded); + } + + if checks.contains(Checks::ENABLED) { + let releases = repo_handler.releases().list().send().await?.items; + + // Check if jar files are released + if !contains_jar_asset(&releases) { + return Err(super::Error::NotAMod); + } + + // Check if the repo is compatible + if !is_project_compatible(profile, &releases, checks.contains(Checks::GAME_VERSION)).await? + { + return Err(super::Error::Incompatible); + } + } + + // Add it to the profile + profile.mods.push(Mod::new( + repo.name.trim(), + ModIdentifier::GitHubRepository(repo_name), + checks, + )); + + Ok(repo.name) +} diff --git a/src/add/mod.rs b/src/add/mod.rs new file mode 100644 index 0000000..f13471c --- /dev/null +++ b/src/add/mod.rs @@ -0,0 +1,287 @@ +use crate::config::structs::Profile; +use bitflags::bitflags; +use reqwest::StatusCode; + +pub mod curseforge; +pub mod github; +pub mod modrinth; + +#[derive(thiserror::Error, Debug)] +#[error("{}: {}", self, .0)] +pub enum Error { + #[error( + "The developer of this project has denied third party applications from downloading it" + )] + /// The user can manually download the mod and place it in the `user` folder of the output directory to mitigate this. + /// However, they will have to manually update the mod. + DistributionDenied, + #[error("The project has already been added")] + AlreadyAdded, + #[error("The project does not exist")] + DoesNotExist, + #[error("The project is not compatible")] + Incompatible, + #[error("The project is not a mod")] + NotAMod, + #[error("Invalid identifier")] + InvalidIdentifier, + GitHubError(octocrab::Error), + ModrinthError(ferinth::Error), + CurseForgeError(furse::Error), +} + +pub type Result = std::result::Result; + +bitflags! { + /// Represents a set of boolean flags used for checking conditions while adding Mods using + /// [ModProvider] + /// + /// The `Checks` struct is a bitflag representation of different checks that can be performed. + /// + /// # Examples + /// + /// ``` + /// use libium::Checks; + /// + /// // Create a set of checks + /// let checks = Checks::ENABLED | Checks::GAME_VERSION; + /// // or + /// let mut checks = Checks::empty(); + /// checks.insert(Checks::ENABLED); + /// + /// + /// // Check if a specific flag is set + /// if checks.contains(Checks::ENABLED) { + /// println!("The feature is enabled."); + /// } + /// + /// // Add additional checks + /// let updated_checks = checks | Checks::MOD_LOADER; + /// ``` + pub struct Checks: u8 { + /// Should we perform checks? + const ENABLED = 0b00000001; + /// Should we check game version? + const GAME_VERSION = 0b00000010; + /// Should we check mod loader? + const MOD_LOADER = 0b00000100; + } +} + +/// Collects all mod providers (i.e Modrinth, Cursefore and github wrappers) and abstracts away the +/// add method +pub struct ModProvider<'p> { + modrinth: &'p ferinth::Ferinth, + curseforge: &'p furse::Furse, + github: &'p octocrab::Octocrab, + checks: &'p Checks, + profile: &'p mut Profile, +} + +impl<'p> ModProvider<'p> { + /// Creates a new instance of `ModManager` with the provided API wrappers, checks, and profile. + /// + /// This function constructs a new `ModManager` instance, which serves as a utility for adding mods. + /// It takes API wrappers for Modrinth, CurseForge, and GitHub, along with references to checks + /// and a mutable reference to a profile. These components are used internally for + /// for adding mods, performing checks, and managing profiles. + /// + /// # Arguments + /// + /// * `modrinth` - A reference to the Modrinth API wrapper (`ferinth::Ferinth`). + /// * `curseforge` - A reference to the CurseForge API wrapper (`furse::Furse`). + /// * `github` - A reference to the GitHub API wrapper (`octocrab::Octocrab`). + /// * `checks` - Checks to perform while adding mods + /// * `profile` - The profile to make changes in + /// + /// # Returns + /// + /// A new instance of `ModProvider` configured with the provided components. + /// + /// # Example + /// + /// ``` + /// // Create API wrappers + /// let modrinth = Ferinth::new(); + /// let curseforge = Furse::new(); + /// let github = Octocrab::builder().build(); + /// + /// // Create checks and profile + /// let checks = Checks::empty(); + /// let mut profile = Profile::new(); + /// + /// // Create a new ModProvider instance + /// let mod_provider = ModProvider::new(&modrinth, &curseforge, &github, &checks, &mut profile); + /// ``` + pub fn new( + modrinth: &'p ferinth::Ferinth, + curseforge: &'p furse::Furse, + github: &'p octocrab::Octocrab, + checks: &'p Checks, + profile: &'p mut Profile, + ) -> Self { + Self { + modrinth, + curseforge, + github, + checks, + profile, + } + } + + /// Add a mod to the profile based on the identifier. + /// The identifier can be: + /// - A numeric ID representing a project on CurseForge. + /// - A GitHub repository identifier in the form "username/repository". + /// - Any other string, which is assumed to be a mod ID on Modrinth. + /// + /// # Arguments + /// + /// * `identifier` - A string representing the identifier of the mod. + /// + /// # Returns + /// + /// A Result containing a String representing the added mod's information, + /// or an error if the addition failed. + /// + /// # Examples + /// + /// ``` + /// let mod_provider = ModProvider::new(&modrinth, &curseforge, &github, &checks, &mut profile); + /// let result = manager.add("123456"); + /// assert!(result.is_ok()); + /// ``` + pub async fn add(&mut self, identifier: &str) -> Result { + if let Ok(project_id) = identifier.parse() { + self.curseforge(project_id).await + } else if identifier.matches('/').count() == 1 { + self.github(identifier).await + } else { + self.modrinth(identifier).await + } + } + + /// Fetches mod information from CurseForge using the provided project ID. + pub async fn curseforge(&mut self, project_id: i32) -> Result { + curseforge::curseforge(self.curseforge, project_id, self.profile, self.checks).await + } + + /// Fetches mod information from GitHub using the provided repository identifier. + pub async fn github(&mut self, identifier: &str) -> Result { + let split = identifier.split('/').collect::>(); + let repo_handler = self.github.repos(split[0], split[1]); + github::github(&repo_handler, self.profile, self.checks).await + } + + /// Fetches mod information from Modrinth using the provided identifier. + pub async fn modrinth(&mut self, identifier: &str) -> Result { + modrinth::modrinth(self.modrinth, identifier, self.profile, self.checks) + .await + .map(|o| o.0) + } +} + +impl From for Error { + fn from(err: furse::Error) -> Self { + if let furse::Error::ReqwestError(source) = &err { + if Some(StatusCode::NOT_FOUND) == source.status() { + Self::DoesNotExist + } else { + Self::CurseForgeError(err) + } + } else { + Self::CurseForgeError(err) + } + } +} + +impl From for Error { + fn from(err: ferinth::Error) -> Self { + if let ferinth::Error::ReqwestError(source) = &err { + if Some(StatusCode::NOT_FOUND) == source.status() { + Self::DoesNotExist + } else { + Self::ModrinthError(err) + } + } else { + Self::ModrinthError(err) + } + } +} + +impl From for Error { + fn from(err: octocrab::Error) -> Self { + if let octocrab::Error::GitHub { source, .. } = &err { + if &source.message == "Not Found" { + return Self::DoesNotExist; + } + } + Self::GitHubError(err) + } +} + +/// Adds multiple mods to the profile using the provided `ModProvider` and a list of identifiers. +/// +/// # Arguments +/// +/// * `mod_provider` - A mutable reference to a `ModProvider` instance used for adding mods. +/// * `identifiers` - A vector of strings representing mod identifiers to be added. +/// +/// # Returns +/// +/// A tuple containing two vectors: +/// - The names of the mods successfully added to the profile. +/// - Tuples of identifiers of mods that failed to be added along with the corresponding errors. +/// +/// # Examples +/// +/// ``` +/// async fn example(mod_provider: &mut ModProvider<'_>, identifiers: Vec) { +/// let (success_names, failures) = ModProvider::add_multiple(mod_provider, identifiers).await; +/// +/// println!("Successfully added mods: {:?}", success_names); +/// println!("Failed to add mods:"); +/// for (identifier, error) in failures { +/// println!("Identifier: {}, Error: {:?}", identifier, error); +/// } +/// } +/// ``` +pub async fn add_multiple<'p>( + mod_provider: &mut ModProvider<'p>, + identifiers: Vec, +) -> (Vec, Vec<(String, Error)>) { + let mut success_names = Vec::new(); + let mut failures = Vec::new(); + + for identifier in identifiers { + mod_provider + .add(&identifier) + .await + .map(|name| success_names.push(name)) + .map_err(|err| { + let ret_err = + if matches!(err, Error::ModrinthError(ferinth::Error::InvalidIDorSlug)) { + Error::InvalidIdentifier + } else { + err + }; + failures.push((identifier, ret_err)) + }) + .ok(); + } + (success_names, failures) +} + +#[deprecated(note = "use ModProvide::add() instead")] +pub async fn add_single( + modrinth: &ferinth::Ferinth, + curseforge: &furse::Furse, + github: &octocrab::Octocrab, + profile: &mut Profile, + identifier: &str, + checks: &Checks, +) -> Result { + ModProvider::new(modrinth, curseforge, github, checks, profile) + .add(identifier) + .await +} diff --git a/src/add/modrinth.rs b/src/add/modrinth.rs new file mode 100644 index 0000000..cb41f81 --- /dev/null +++ b/src/add/modrinth.rs @@ -0,0 +1,73 @@ +use ferinth::structures::project::{DonationLink, Project, ProjectType}; + +use crate::{ + config::structs::{Mod, ModIdentifier, ModIdentifierRef, ModLoader, Profile}, + upgrade::check::{game_version_check, mod_loader_check}, +}; + +use super::Checks; + +fn project_exist(profile: &Profile, project: &Project) -> bool { + profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == project.title.to_lowercase() + || ModIdentifierRef::ModrinthProject(&project.id) == mod_.identifier.as_ref() + }) +} + +fn project_is_mod(project: &Project) -> bool { + project.project_type == ProjectType::Mod +} + +fn check_mod_loader_fabric_backwards_compatible( + profile: &Profile, + project: &Project, + check_mod_loader: bool, +) -> bool { + mod_loader_check(profile.get_loader(check_mod_loader), &project.loaders) + || (profile.mod_loader == ModLoader::Quilt + && mod_loader_check(Some(ModLoader::Fabric), &project.loaders)) +} + +fn project_comatible(profile: &Profile, project: &Project, checks: &Checks) -> bool { + game_version_check( + profile.get_version(checks.contains(Checks::GAME_VERSION)), + &project.game_versions, + ) && check_mod_loader_fabric_backwards_compatible( + profile, + project, + checks.contains(Checks::MOD_LOADER), + ) +} + +/// Check if the project of `project_id` exists, is a mod, and is compatible with `profile`. +/// If so, add it to the `profile`. +/// +/// Returns the project name and donation URLs to display to the user +pub async fn modrinth( + modrinth: &ferinth::Ferinth, + project_id: &str, + profile: &mut Profile, + checks: &Checks, +) -> super::Result<(String, Vec)> { + let project = modrinth.get_project(project_id).await?; + + if project_exist(profile, &project) { + return Err(super::Error::AlreadyAdded); + } + + if !project_is_mod(&project) { + return Err(super::Error::NotAMod); + } + + if checks.contains(Checks::ENABLED) && !project_comatible(profile, &project, checks) { + return Err(super::Error::Incompatible); + } + + profile.mods.push(Mod::new( + project.title.trim(), + ModIdentifier::ModrinthProject(project.id), + checks, + )); + + Ok((project.title, project.donation_urls)) +} diff --git a/src/config/structs.rs b/src/config/structs.rs index 9155058..13bea7c 100644 --- a/src/config/structs.rs +++ b/src/config/structs.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use std::{path::PathBuf, str::FromStr}; +use crate::add::Checks; + #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct Config { /// The index of the active profile @@ -90,6 +92,17 @@ pub struct Mod { pub check_mod_loader: bool, } +impl Mod { + pub fn new(name: &str, identifier: ModIdentifier, checks: &Checks) -> Self { + Self { + name: name.into(), + identifier, + check_game_version: checks.contains(Checks::GAME_VERSION), + check_mod_loader: checks.contains(Checks::MOD_LOADER), + } + } +} + fn is_true(b: &bool) -> bool { *b }