summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCara Salter <cara@devcara.com>2023-03-26 12:05:27 -0400
committerCara Salter <cara@devcara.com>2023-03-26 12:05:27 -0400
commitb59b5a9bcbc64902366f6da21c84cb20be61efb8 (patch)
treeae5dcf03ebfd4b9f7ff26e54195b4d31317b0597 /src
downloadmodpackman-ng-b59b5a9bcbc64902366f6da21c84cb20be61efb8.tar.gz
modpackman-ng-b59b5a9bcbc64902366f6da21c84cb20be61efb8.zip
initial commitHEADmaster
Diffstat (limited to 'src')
-rw-r--r--src/cli/install.rs8
-rw-r--r--src/cli/mod.rs2
-rw-r--r--src/cli/updates.rs39
-rw-r--r--src/errors.rs18
-rw-r--r--src/main.rs83
-rw-r--r--src/util.rs137
6 files changed, 287 insertions, 0 deletions
diff --git a/src/cli/install.rs b/src/cli/install.rs
new file mode 100644
index 0000000..a8ec5eb
--- /dev/null
+++ b/src/cli/install.rs
@@ -0,0 +1,8 @@
+use crate::errors::CliError;
+
+
+
+pub fn install() -> Result<(), CliError> {
+
+ Ok(())
+} \ No newline at end of file
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
new file mode 100644
index 0000000..04f7bac
--- /dev/null
+++ b/src/cli/mod.rs
@@ -0,0 +1,2 @@
+pub mod updates;
+pub mod install; \ No newline at end of file
diff --git a/src/cli/updates.rs b/src/cli/updates.rs
new file mode 100644
index 0000000..3e0bc5d
--- /dev/null
+++ b/src/cli/updates.rs
@@ -0,0 +1,39 @@
+use thirtyfour::{DesiredCapabilities, WebDriver};
+use tracing::{info, error};
+use std::{path::Path, process::exit};
+
+use toml::Value;
+use crate::{errors::CliError, util::{PackConfig, read_pack_lock, self}};
+
+pub async fn check_updates(cfg: PackConfig) -> Result<(), CliError> {
+ info!("Checking for pack updates for {} ({})", cfg.pack.name,cfg.pack.game_version);
+ let mut old_urls: Vec<String> = Vec::new();
+ if Path::new("./pack.lock").exists() {
+ let lock = read_pack_lock()?;
+
+ for url in lock.mod_versions.keys() {
+ old_urls.push((&lock.mod_versions.get(url).unwrap_or(&Value::String("none".to_string()))).to_string());
+ }
+ } else {
+ error!("No pack.lock found, can't check for updates to something that doesn't exist!");
+ exit(-1);
+ }
+
+ let mut num_updates: u32 = 0;
+
+ let caps = DesiredCapabilities::firefox();
+ let driver = WebDriver::new("http://localhost:9515", caps).await;
+
+ for url in old_urls {
+ info!("Checking for updates to {}", url);
+ }
+ Ok(())
+}
+
+
+pub async fn apply_updates(cfg: PackConfig) -> Result<(), CliError> {
+ info!("Fetching new mod versions and updating pack.lock...");
+ let mod_urls = util::find_updated_urls(cfg.mods.values(), cfg.pack.game_version).await?;
+
+ Ok(())
+} \ No newline at end of file
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..54b6f88
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,18 @@
+use std::num::ParseIntError;
+
+use thirtyfour::prelude::WebDriverError;
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum CliError {
+ #[error("Command Error: {0}")]
+ Cli(String),
+ #[error("I/O Error: {0}")]
+ IO(#[from] std::io::Error),
+ #[error("TOML Error: {0}")]
+ TomlDe(#[from] toml::de::Error),
+ #[error("WebDriver Error: {0}")]
+ WebDriver(#[from] WebDriverError),
+ #[error("Integer Parsing Error: {0}")]
+ ParseInt(#[from] ParseIntError)
+} \ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..0898daf
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,83 @@
+/*!
+ * Thinking about how best to design this...
+ *
+ * I want to do something different from the original modpackman, mostly in the realm of "i want to make it easier and more portable" (hence rust).
+ *
+ * ## Interface
+ * I want this to be a tad easier to use. There should be one binary to run that does both dependency fixing for modpacks and installation. That binary should have the following subcommands
+ * - apply_updates
+ * - check_updates
+ * - install
+ *
+ * All of these commands should assume that the CWD contains a valid pack.toml and that the pack is valid.
+ *
+ * I'm not sure how packaging this is going to work but it should be fun!
+ */
+
+
+
+use std::process::exit;
+
+use clap::{Parser, Subcommand};
+mod errors;
+mod cli;
+mod util;
+
+use cli::{updates::check_updates, install};
+use errors::CliError;
+use util::{PackConfig, read_pack_config};
+use tracing_subscriber::prelude::*;
+
+#[derive(Parser)]
+#[command(author, version, about)]
+struct Cli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand, Clone, Copy)]
+enum Commands {
+ ApplyUpdates { },
+ CheckUpdates { },
+ /// Installs CWD's pack definition into the local .minecraft folder
+ Install { }
+}
+
+#[tokio::main]
+async fn main() {
+ color_eyre::install().unwrap();
+ tracing_subscriber::registry()
+ .with(tracing_subscriber::EnvFilter::new(
+ std::env::var("RUST_LOG").unwrap_or_else(|_| "modpackman_ng=debug,hyper=debug".into()),
+ ))
+ .with(tracing_subscriber::fmt::layer())
+ .init();
+ let cli = Cli::parse();
+
+ let cfg: PackConfig = match read_pack_config() {
+ Ok(c) => c,
+ Err(e) => {
+ println!("Error reading config: {:?}", e);
+ exit(-1);
+ }
+ };
+
+ let res: Result<(), CliError> = match &cli.command {
+ Commands::ApplyUpdates { } => {
+ cli::updates::apply_updates(cfg).await
+ },
+ Commands::CheckUpdates { } => {
+ check_updates(cfg).await
+ },
+ Commands::Install { } => {
+ install::install()
+ }
+ };
+
+ match res {
+ Err(e) => {
+ println!("{:?}", e);
+ },
+ Ok(()) => { },
+ }
+} \ No newline at end of file
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..4e9f5a0
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,137 @@
+use std::fs;
+
+use serde::{Deserialize, Serialize};
+use thirtyfour::{DesiredCapabilities, WebDriver, By};
+use toml::Table;
+use tracing::{info, debug};
+
+use crate::errors::CliError;
+
+
+#[derive(Deserialize, Serialize)]
+pub struct PackConfig {
+ pub pack: Pack,
+ pub mods: Table,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct Pack {
+ pub name: String,
+ pub pack_base_url: String,
+ pub forge_url: String,
+ pub game_version: String,
+ pub java_args: String,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct PackLock {
+ pub global: LockGlobal,
+ pub mod_versions: Table,
+}
+
+#[derive(Deserialize, Serialize)]
+pub struct LockGlobal {
+ pub pack_version: u16,
+}
+
+pub fn read_pack_config() -> Result<PackConfig, CliError> {
+ let contents = fs::read_to_string("./pack.toml")?;
+
+ let res: PackConfig = toml::from_str(&contents)?;
+
+ Ok(res)
+}
+
+pub fn read_pack_lock() -> Result<PackLock, CliError> {
+ let contents = fs::read_to_string("./pack.lock")?;
+
+ let res = toml::from_str(&contents)?;
+
+ Ok(res)
+}
+
+pub async fn find_updated_urls(urls: toml::map::Values<'_>, game_version: String) -> Result<Vec<String>, CliError> {
+ let res: Vec<String> = Vec::new();
+ debug!("Attempting to find updated URLs");
+ debug!("Attempting to open webdriver");
+ let caps = DesiredCapabilities::firefox();
+ let driver = WebDriver::new("http://localhost:9515", caps).await?;
+ driver.close_window().await?;
+ debug!("Closed test window");
+
+ for url in urls {
+ find_url(url.to_string(), &game_version).await?;
+ }
+
+ Ok(res)
+}
+
+async fn find_url(homepage_url: String, game_version: &String) -> Result<String, CliError> {
+ if homepage_url.contains("curseforge") {
+ let caps = DesiredCapabilities::firefox();
+ let driver = WebDriver::new("http://localhost:9515", caps).await?;
+
+ find_cdn(&driver, homepage_url, game_version).await?;
+ driver.close_window().await?;
+ } else {
+
+ }
+
+ Ok(String::new())
+}
+
+
+#[derive(Debug)]
+struct RowInfo {
+ pub release_type: String,
+ pub filename: String,
+ pub cdn_id: String,
+ pub game_version: (u16, u16, u16)
+}
+
+/**
+ * Ugh...
+ */
+async fn find_cdn(driver: &WebDriver, homepage_url: String, game_version: &String) -> Result<String, CliError> {
+ let mut page_index = 0;
+ let best_row: RowInfo;
+ loop {
+ let mut url = homepage_url.clone();
+ url.push_str(&format!("/files/all?page={}", page_index));
+ driver.goto(&url).await?;
+
+ let mod_vers = driver.find(By::ClassName("listing")).await?.find_all(By::XPath("tbody/tr")).await?;
+
+ let mut rows: Vec<RowInfo> = Vec::new();
+ for entry in mod_vers {
+ let entry_cells = entry.find_all(By::Tag("td")).await?;
+ let release_type = entry_cells[0].text().await?;
+ let tmp0 = &entry_cells[1].find_all(By::Tag("a")).await?[0].text().await?;
+ let initial_filename = urlencoding::encode(tmp0);
+
+ let tmp = entry_cells[4].find(By::ClassName("mr-2")).await?.text().await?;
+ let tmp2: Vec<&str> = tmp.split(".").collect();
+ let version = (tmp2[0].clone().parse::<u16>()?, tmp2[1].clone().parse::<u16>()?, tmp2[2].parse::<u16>()?);
+
+ let tmp3 = entry_cells[1].find(By::Tag("a")).await?.prop("href").await?.unwrap();
+ let tmp4: Vec<&str> = tmp3.split("/").collect();
+ let cdn_id = *tmp4.last().unwrap();
+
+ if !(initial_filename.to_lowercase().contains("fabric")) || (initial_filename.to_lowercase().contains("forge")) {
+ rows.push(RowInfo {
+ release_type,
+ cdn_id: cdn_id.to_string(),
+ game_version: version,
+ filename: initial_filename.to_string()
+ })
+ }
+ }
+ rows.sort_by_key(|k| k.game_version);
+
+ let tuple_game_version_tmp: Vec<&str> = game_version.split(".").collect();
+ let tuple_game_version = (tuple_game_version_tmp[0].parse::<u16>()?, tuple_game_version_tmp[1].parse::<u16>()?, tuple_game_version_tmp[2].parse::<u16>()?);
+ let best_rows: Vec<&RowInfo> = rows.iter().filter(|x| x.game_version > tuple_game_version).collect();
+
+ info!("{:?}", best_rows);
+ }
+} \ No newline at end of file