Compare commits

...

4 Commits

Author SHA1 Message Date
dac3092502 Added the ability to update mods 2024-06-03 16:28:49 +02:00
846b3d0edf Added the list and remove command 2024-06-03 13:51:59 +02:00
56f659a182 Rewrite 2024-05-30 16:04:30 +02:00
6f6c78d3ff Added the ability to remove mods 2024-05-28 17:21:43 +02:00
18 changed files with 573 additions and 193 deletions

95
Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
@@ -66,6 +66,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -80,9 +86,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.71"
version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
dependencies = [
"addr2line",
"cc",
@@ -169,7 +175,7 @@ version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [
"heck",
"heck 0.5.0",
"proc-macro2",
"quote",
"syn",
@@ -197,6 +203,18 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "comfy-table"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
dependencies = [
"crossterm 0.27.0",
"strum",
"strum_macros",
"unicode-width",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -229,6 +247,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.5.0",
"crossterm_winapi",
"libc",
"parking_lot",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
@@ -364,9 +395,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.28.1"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "h2"
@@ -393,6 +424,12 @@ version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
@@ -483,9 +520,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e"
checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56"
dependencies = [
"bytes",
"futures-channel",
@@ -528,7 +565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
dependencies = [
"bitflags 2.5.0",
"crossterm",
"crossterm 0.25.0",
"dyn-clone",
"fuzzy-matcher",
"fxhash",
@@ -670,9 +707,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.32.2"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
dependencies = [
"memchr",
]
@@ -731,13 +768,17 @@ dependencies = [
name = "packium"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"colored",
"comfy-table",
"inquire",
"reqwest",
"serde",
"serde_json",
"tokio",
"toml",
"url",
]
[[package]]
@@ -911,6 +952,12 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]]
name = "ryu"
version = "1.0.18"
@@ -1068,6 +1115,25 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
[[package]]
name = "strum_macros"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "2.0.66"
@@ -1324,6 +1390,7 @@ dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
@@ -1592,9 +1659,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6"
dependencies = [
"memchr",
]

View File

@@ -8,6 +8,10 @@ clap = { version = "4.5.4", features = [ "derive" ]}
toml = "0.8.13"
inquire = "0.7.5"
serde = { version = "1.0.203", features = [ "derive" ]}
serde_json = "1.0.117"
colored = "2.1.0"
reqwest = { version = "0.12.4", features = [ "json" ]}
tokio = { version = "1.37.0", features = [ "full" ] }
comfy-table = "7.1.1"
url = { version = "2.5.0", features = [ "serde" ] }
anyhow = "1.0.86"

View File

@@ -20,11 +20,14 @@ pub enum Commands {
Info,
/// Add a mod to the modpack
Add {
/// The name of the mod to add
name: String,
},
Add,
/// Remove a mod from the modpack
Remove,
/// Update a mod or all mods
Update,
/// Migrate to a other Minecraft version
Migrate,
}

24
src/commands/add.rs Normal file
View File

@@ -0,0 +1,24 @@
use crate::config;
use crate::modrinth::project::get_project_versions;
use crate::modrinth::search::search;
use inquire::{Select, Text};
pub async fn add() {
let config = config::Config::get().unwrap();
let query = Text::new("Search").prompt().unwrap();
let response = search(&query, &config.game_version, &config.loader)
.await
.unwrap();
let project = Select::new("Select a mod", response.hits).prompt().unwrap();
let project_versions = get_project_versions(&project.project_id, &config.game_version)
.await
.unwrap();
let project_version = project_versions.get(0).unwrap();
config::Config::add(project.project_id, project_version.id.to_owned()).unwrap();
}

33
src/commands/init.rs Normal file
View File

@@ -0,0 +1,33 @@
use crate::config;
use crate::modrinth::tags;
use inquire::Select;
pub async fn init(snaphots: bool) {
let mut game_versions = tags::get_minecraft_versions().await.unwrap();
if snaphots {
game_versions = game_versions
.into_iter()
.filter(|v| (&v.version_type == "snapshot" || &v.version_type == "release"))
.collect()
} else {
game_versions = game_versions
.into_iter()
.filter(|v| &v.version_type == "release")
.collect()
}
let options = game_versions.iter().map(|v| &v.version).collect();
let game_version = Select::new("Select a Minecraft version", options)
.prompt()
.unwrap();
let options = vec!["fabric", "quilt", "forge", "neoforge"];
let loader = Select::new("Select a mod loader", options)
.prompt()
.unwrap();
config::Config::init(game_version.to_string(), loader.to_string()).unwrap();
}

27
src/commands/list.rs Normal file
View File

@@ -0,0 +1,27 @@
use crate::{config, pack::Modpack};
use colored::*;
use comfy_table::{presets::NOTHING, Table};
pub async fn list() {
let config = config::Config::get().unwrap();
let modpack = Modpack::from_config(&config).await.unwrap();
let rows: Vec<Vec<ColoredString>> = modpack
.mods
.values()
.into_iter()
.map(|p| {
vec![
p.title.bold(),
p.project_id.dimmed(),
p.version.as_ref().unwrap().version_number.normal(),
]
})
.collect();
let mut table = Table::new();
table.load_preset(NOTHING).add_rows(rows);
println!("{table}");
}

5
src/commands/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod add;
pub mod init;
pub mod list;
pub mod remove;
pub mod update;

17
src/commands/remove.rs Normal file
View File

@@ -0,0 +1,17 @@
use crate::{config, pack::Modpack};
use colored::*;
use inquire::Select;
pub async fn remove() {
let mut config = config::Config::get().unwrap();
let modpack = Modpack::from_config(&config).await.unwrap();
let project = Select::new("Select a mod to remove", modpack.mods.values().collect())
.prompt()
.unwrap();
config.projects.remove(&project.project_id);
config.save().unwrap();
}

67
src/commands/update.rs Normal file
View File

@@ -0,0 +1,67 @@
use crate::{
config,
modrinth::{project::get_project_versions, versions::ProjectVersion},
pack::{Mod, Modpack},
};
use colored::*;
use inquire::MultiSelect;
use std::fmt;
struct Diff {
r#mod: Mod,
new_version: ProjectVersion,
}
impl fmt::Display for Diff {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} -> {}",
self.r#mod.title.bold(),
self.r#mod.version.as_ref().unwrap().version_number.red(),
self.new_version.version_number.green()
)
}
}
pub async fn update() {
let mut config = config::Config::get().unwrap();
let modpack = Modpack::from_config(&config).await.unwrap();
let mut diffs: Vec<Diff> = vec![];
// Find the newest compatible version of the mod
for (project_id, r#mod) in modpack.mods {
let project_versions = get_project_versions(&project_id, &config.game_version)
.await
.unwrap();
let new_version = project_versions.get(0).unwrap();
if r#mod.version.as_ref().unwrap().version_number == new_version.version_number {
continue;
}
diffs.push(Diff {
r#mod,
new_version: new_version.clone(),
})
}
if diffs.len() == 0 {
return println!("There are no mods left to update :)");
}
let updated_diffs = MultiSelect::new("Select mods to update", diffs)
.prompt()
.unwrap();
// Bumb the new versions to the config
for diff in updated_diffs {
config
.projects
.insert(diff.r#mod.project_id, diff.new_version.id.to_string());
}
config.save().unwrap();
}

50
src/config.rs Normal file
View File

@@ -0,0 +1,50 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub game_version: String,
pub loader: String,
pub projects: HashMap<String, String>,
}
impl Config {
pub fn get() -> Result<Self> {
let config_raw = fs::read_to_string(".packium.toml")?;
let config = toml::from_str(&config_raw)?;
Ok(config)
}
pub fn save(&self) -> Result<()> {
let config_toml = toml::to_string_pretty(&self)?;
fs::write(".packium.toml", &config_toml)?;
Ok(())
}
pub fn init(game_version: String, loader: String) -> Result<Self> {
let config = Self {
game_version,
loader,
projects: HashMap::new(),
};
config.save()?;
Ok(config)
}
pub fn add(project_id: String, version_id: String) -> Result<Self> {
let mut config = Self::get()?;
config.projects.insert(project_id, version_id);
config.save()?;
Ok(config)
}
}

View File

@@ -1,4 +1,6 @@
mod cli;
mod commands;
mod config;
mod modrinth;
mod pack;
@@ -10,10 +12,19 @@ async fn main() {
match &args.command {
cli::Commands::Init => {
pack::Pack::init().await;
commands::init::init(false).await;
}
cli::Commands::Add { name } => {
pack::Pack::add(name).await;
cli::Commands::Add => {
commands::add::add().await;
}
cli::Commands::Remove => {
commands::remove::remove().await;
}
cli::Commands::List => {
commands::list::list().await;
}
cli::Commands::Update => {
commands::update::update().await;
}
_ => (),
};

View File

@@ -1,72 +0,0 @@
use core::fmt;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub struct MinecraftVersion {
pub version: String,
pub version_type: String,
pub date: String,
pub major: bool,
}
#[derive(Deserialize, Debug)]
pub struct Mod {
pub title: String,
pub description: String,
pub project_id: String,
}
impl fmt::Display for Mod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.title)
}
}
#[derive(Deserialize, Debug)]
pub struct Search {
hits: Vec<Mod>,
}
#[derive(Deserialize, Debug)]
pub struct ProjectVersion {
pub name: String,
pub game_versions: Vec<String>,
pub version_type: String,
pub id: String,
pub project_id: String,
}
pub async fn get_minecraft_versions() -> Result<Vec<MinecraftVersion>, reqwest::Error> {
reqwest::get("https://api.modrinth.com/v2/tag/game_version")
.await?
.json::<Vec<MinecraftVersion>>()
.await
}
pub async fn search_projects(
query: String,
version: &String,
loader: &String,
) -> Result<Vec<Mod>, reqwest::Error> {
let url = format!("https://api.modrinth.com/v2/search?query={query}&facets=[[\"project_type:mod\"],[\"versions:{version}\"],[\"categories:{loader}\"]]");
let response = reqwest::get(url).await?.json::<Search>().await?;
Ok(response.hits)
}
pub async fn get_project_versions(
id: &String,
version: &String,
loader: &String,
) -> Result<Vec<ProjectVersion>, reqwest::Error> {
let url = format!("https://api.modrinth.com/v2/project/{id}/version?loaders=[\"{loader}\"]&game_versions=[\"{version}\"]");
let response = reqwest::get(url)
.await?
.json::<Vec<ProjectVersion>>()
.await?;
Ok(response)
}

6
src/modrinth/mod.rs Normal file
View File

@@ -0,0 +1,6 @@
pub mod project;
pub mod search;
pub mod tags;
pub mod versions;
pub const API_BASE_URL: &str = "https://api.modrinth.com/v2";

51
src/modrinth/project.rs Normal file
View File

@@ -0,0 +1,51 @@
use super::versions::ProjectVersion;
use super::API_BASE_URL;
use anyhow::{bail, Result};
use serde::Deserialize;
type ProjectVersionResponse = Vec<ProjectVersion>;
pub async fn get_project_versions(
project_id: &str,
game_version: &str,
) -> Result<ProjectVersionResponse> {
let url = format!(
"{API_BASE_URL}/project/{}/version?game_versions=[\"{}\"]",
project_id, game_version
);
let response = reqwest::get(url)
.await?
.json::<ProjectVersionResponse>()
.await;
match response {
Ok(r) => Ok(r),
Err(err) => bail!("Error with Modrinth API: {}", err.to_string()),
}
}
type ProjectResponse = Vec<Project>;
#[derive(Debug, Deserialize)]
pub struct Project {
slug: String,
pub title: String,
pub description: String,
pub downloads: i64,
pub id: String,
pub followers: i64,
}
pub async fn get_multiple_projects(version_ids: Vec<&String>) -> Result<ProjectResponse> {
let version_ids_json = serde_json::to_string(&version_ids)?;
let url = format!("{API_BASE_URL}/projects?ids={}", version_ids_json);
let response = reqwest::get(url).await?.json::<ProjectResponse>().await;
match response {
Ok(r) => Ok(r),
Err(err) => bail!("Error with Modrinth API: {}", err.to_string()),
}
}

70
src/modrinth/search.rs Normal file
View File

@@ -0,0 +1,70 @@
use super::API_BASE_URL;
use anyhow::{bail, Result};
use serde::Deserialize;
use std::fmt;
#[derive(Deserialize, Debug)]
pub struct Response {
pub hits: Vec<SearchHit>,
offset: i64,
limit: i64,
total_hits: i64,
}
#[derive(Deserialize, Debug)]
pub struct SearchHit {
pub slug: String,
pub title: String,
pub description: String,
pub client_side: String,
pub server_side: String,
pub project_type: String,
pub downloads: i64,
pub project_id: String,
pub author: String,
pub versions: Vec<String>,
pub follows: i64,
}
impl fmt::Display for SearchHit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.title)
}
}
pub async fn search(query: &str, game_version: &str, loader: &str) -> Result<Response> {
let game_version_facet = format!("versions:{game_version}");
let loader_facet = format!("categories:{loader}");
let facets: Vec<Vec<&str>> = vec![
vec!["project_type:mod"],
vec![&game_version_facet],
vec![&loader_facet],
];
let facets_json = serde_json::to_string(&facets)?;
let url = format!(
"{API_BASE_URL}/search?query={}&facets={}",
query, facets_json
);
let response = reqwest::get(url).await?.json::<Response>().await;
match response {
Ok(r) => Ok(r),
Err(err) => bail!("Error with Modrinth API: {}", err.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_search() {
let response = search("Fabric API", "1.20.4", "fabric").await;
assert!(response.is_ok())
}
}

36
src/modrinth/tags.rs Normal file
View File

@@ -0,0 +1,36 @@
use super::API_BASE_URL;
use anyhow::{bail, Result};
use serde::Deserialize;
type Response = Vec<MinecraftVersion>;
#[derive(Debug, Deserialize)]
pub struct MinecraftVersion {
pub version: String,
pub version_type: String,
date: String,
major: bool,
}
pub async fn get_minecraft_versions() -> Result<Response> {
let url = format!("{API_BASE_URL}/tag/game_version");
let response = reqwest::get(url).await?.json::<Response>().await;
match response {
Ok(r) => Ok(r),
Err(err) => bail!("Error with Modrinth API: {}", err.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_game_versions() {
let response = get_minecraft_versions().await;
assert!(response.is_ok())
}
}

31
src/modrinth/versions.rs Normal file
View File

@@ -0,0 +1,31 @@
use super::API_BASE_URL;
use anyhow::{bail, Result};
use serde::Deserialize;
type Response = Vec<ProjectVersion>;
#[derive(Debug, Deserialize, Clone)]
pub struct ProjectVersion {
pub name: String,
pub version_number: String,
pub game_versions: Vec<String>,
pub version_type: String,
pub loaders: Vec<String>,
pub featured: bool,
pub id: String,
pub project_id: String,
pub downloads: i64,
}
pub async fn get_multiple_versions(version_ids: Vec<&String>) -> Result<Response> {
let version_ids_json = serde_json::to_string(&version_ids)?;
let url = format!("{API_BASE_URL}/versions?ids={}", version_ids_json);
let response = reqwest::get(url).await?.json::<Response>().await;
match response {
Ok(r) => Ok(r),
Err(err) => bail!("Error with Modrinth API: {}", err.to_string()),
}
}

View File

@@ -1,118 +1,68 @@
use crate::modrinth;
use colored::*;
use inquire::Select;
use serde::{Deserialize, Serialize};
use std::{
fmt::{Display, Formatter},
fs,
use crate::{
config,
modrinth::{
project::{get_multiple_projects, Project},
versions::{get_multiple_versions, ProjectVersion},
},
};
use anyhow::Result;
use std::collections::HashMap;
use std::fmt;
#[derive(Serialize, Deserialize)]
pub struct Pack {
info: Info,
mods: Vec<Mod>,
pub struct Modpack {
pub game_version: String,
pub loader: String,
pub mods: HashMap<String, Mod>,
}
#[derive(Serialize, Deserialize)]
pub struct Mod {
id: String,
version_id: String,
pub project_id: String,
pub title: String,
pub description: String,
pub version: Option<ProjectVersion>,
}
#[derive(Serialize, Deserialize)]
pub struct Info {
loader: Modloader,
minecraft_version: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum Modloader {
Fabric,
Quilt,
Forge,
Neoforge,
}
impl Modloader {
const VARIANTS: &'static [Modloader] =
&[Self::Fabric, Self::Quilt, Self::Forge, Self::Neoforge];
}
impl Display for Modloader {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{self:?}")
impl Mod {
fn from_project(project: Project) -> Self {
Self {
project_id: project.id,
title: project.title,
description: project.description,
version: None,
}
}
}
impl Pack {
pub fn get() -> Self {
let pack_string = fs::read_to_string(".packium.toml").unwrap();
toml::from_str(&pack_string).unwrap()
impl fmt::Display for Mod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.title)
}
}
pub async fn init() {
println!("Fetching Minecraft information...");
impl Modpack {
pub async fn from_config(config: &config::Config) -> Result<Self> {
let projects = get_multiple_projects(config.projects.keys().collect())
.await
.unwrap();
let versions = get_multiple_versions(config.projects.values().collect())
.await
.unwrap();
let versions = modrinth::get_minecraft_versions().await.unwrap();
let versions = versions
.iter()
.filter(|x| x.version_type == "release".to_string())
.map(|x| &x.version)
let mut projects_map: HashMap<String, Mod> = projects
.into_iter()
.map(|p| (p.id.clone(), Mod::from_project(p)))
.collect();
let modloader = Select::new("Modloader?", Modloader::VARIANTS.to_vec())
.prompt()
.unwrap();
for version in versions {
if let Some(p) = projects_map.get_mut(&version.project_id) {
p.version = Some(version)
}
}
let version = Select::new("Minecraft version?", versions)
.prompt()
.unwrap();
let pack = Pack {
info: Info {
minecraft_version: version.to_owned(),
loader: modloader,
},
mods: vec![],
};
let pack_toml = toml::to_string_pretty(&pack).unwrap();
std::fs::write(".packium.toml", pack_toml).unwrap();
}
pub async fn add(name: &String) {
let mut pack = Self::get();
let mods = modrinth::search_projects(
name.to_owned(),
&pack.info.minecraft_version,
&pack.info.loader.to_string().to_lowercase(),
)
.await
.unwrap();
let project = Select::new("Choose a mod", mods).prompt().unwrap();
let versions = modrinth::get_project_versions(
&project.project_id,
&pack.info.minecraft_version,
&pack.info.loader.to_string().to_lowercase(),
)
.await
.unwrap();
let version = versions.get(0).unwrap();
pack.mods.push(Mod {
id: project.project_id.clone(),
version_id: version.id.clone(),
});
let pack_toml = toml::to_string_pretty(&pack).unwrap();
std::fs::write(".packium.toml", pack_toml).unwrap();
Ok(Modpack {
game_version: config.game_version.clone(),
loader: config.loader.clone(),
mods: projects_map,
})
}
}