feat(cli): add commands to list, view and download PaperMC builds
This commit is contained in:
parent
f2c4b97626
commit
d38cb4ce39
6 changed files with 230 additions and 0 deletions
|
|
@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased](https://git.rustybever.be/Chewing_Bever/alex/src/branch/dev)
|
||||
|
||||
### Added
|
||||
|
||||
* CLI commands to interact with the PaperMC API
|
||||
* list and view available Minecraft versions and builds per version
|
||||
* Download jars to simplify manual server updating
|
||||
|
||||
## [0.4.2](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.4.2)
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -16,8 +16,10 @@ dependencies = [
|
|||
"chrono",
|
||||
"clap",
|
||||
"figment",
|
||||
"papermc-api",
|
||||
"serde",
|
||||
"signal-hook",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition.workspace = true
|
|||
|
||||
[dependencies]
|
||||
backup = { path = "../backup" }
|
||||
papermc-api = { path = "../papermc-api" }
|
||||
|
||||
chrono.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
@ -13,3 +14,4 @@ serde.workspace = true
|
|||
clap = { version = "4.5.37", features = ["derive", "env"] }
|
||||
signal-hook = "0.3.15"
|
||||
figment = { version = "0.10.10", features = ["env", "toml"] }
|
||||
ureq = "3.3.0"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
mod backup;
|
||||
mod config;
|
||||
mod papermc;
|
||||
mod run;
|
||||
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
|
@ -72,6 +73,9 @@ pub enum Commands {
|
|||
Run(RunCli),
|
||||
/// Interact with the backup system without starting a server
|
||||
Backup(BackupArgs),
|
||||
/// Interact with the PaperMC API and download new JARs
|
||||
#[command(name = "papermc")]
|
||||
PaperMC(papermc::Cli),
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
|
|
@ -81,6 +85,7 @@ impl Cli {
|
|||
match &self.command {
|
||||
Commands::Run(args) => args.run(self, &config),
|
||||
Commands::Backup(args) => Ok(args.run(&config)?),
|
||||
Commands::PaperMC(cli) => cli.run(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
213
alex/src/cli/papermc.rs
Normal file
213
alex/src/cli/papermc.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use chrono::Local;
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Show information or a specific version or build
|
||||
Show(ShowArgs),
|
||||
/// List the available versions, or builds for a specific version
|
||||
List(ListArgs),
|
||||
/// Download the jar for a specific build
|
||||
Download(DownloadBuildArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct ShowArgs {
|
||||
/// Version to show information for
|
||||
version: String,
|
||||
|
||||
/// Build within version to show information for
|
||||
build: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct ListArgs {
|
||||
/// If provided, list the available builds for this version
|
||||
version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct DownloadBuildArgs {
|
||||
/// Version of build to download
|
||||
version: String,
|
||||
/// Build number for build to download
|
||||
build: String,
|
||||
|
||||
/// Path to store the new JAR file in; stores JAR in the local directory if not specified
|
||||
#[arg(short, long, value_name = "OUT_PATH")]
|
||||
out: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn run(&self) -> crate::Result<()> {
|
||||
match &self.command {
|
||||
Commands::Show(ShowArgs {
|
||||
version,
|
||||
build: None,
|
||||
}) => show_version(&version),
|
||||
Commands::List(ListArgs { version: None }) => list_versions(),
|
||||
Commands::List(ListArgs {
|
||||
version: Some(version),
|
||||
}) => list_builds(version),
|
||||
Commands::Show(ShowArgs {
|
||||
version,
|
||||
build: Some(build),
|
||||
}) => show_build(&version, &build),
|
||||
Commands::Download(args) => {
|
||||
download_build(&args.version, &args.build, args.out.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn show_version(version_str: &str) {
|
||||
let client = papermc_api::Client::new();
|
||||
|
||||
let version = match client.project("paper").version(version_str).info() {
|
||||
Ok(version) => version,
|
||||
Err(err) => {
|
||||
println!("failed to query API: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("id : {}", version.id);
|
||||
println!("status : {}", version.support_status);
|
||||
println!("builds : {}", version.builds.len());
|
||||
println!("Min. Java : {}", version.java.minimum_version);
|
||||
println!("Java flags: {}", version.java.recommended_flags.join(" "))
|
||||
}
|
||||
|
||||
fn show_build(version_str: &str, build_str: &str) {
|
||||
let client = papermc_api::Client::new();
|
||||
|
||||
let build = match client
|
||||
.project("paper")
|
||||
.version(version_str)
|
||||
.build(build_str)
|
||||
{
|
||||
Ok(build) => build,
|
||||
Err(err) => {
|
||||
println!("failed to query API: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("id : {}", build.id);
|
||||
println!("time : {}", build.time.with_timezone(&Local));
|
||||
println!("channel: {}", build.channel);
|
||||
println!("commits:");
|
||||
|
||||
for commit in build.commits {
|
||||
println!("- SHA : {}", commit.sha);
|
||||
println!(" time : {}", commit.time.with_timezone(&Local));
|
||||
println!(" message: {}", commit.message);
|
||||
}
|
||||
|
||||
println!("downloads:");
|
||||
|
||||
for (name, download) in build.downloads.iter() {
|
||||
println!(" {name}:");
|
||||
println!(" name: {}", download.name);
|
||||
println!(" size: {}", download.size);
|
||||
println!(" URL : {}", download.url);
|
||||
println!(" checksums:");
|
||||
|
||||
for (name, value) in download.checksums.iter() {
|
||||
println!(" - {}: {}", name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_versions() {
|
||||
let client = papermc_api::Client::new();
|
||||
|
||||
match client.project("paper").versions() {
|
||||
Ok(versions) => {
|
||||
for version in versions {
|
||||
println!("{} ({})", version.id, version.support_status);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("failed to query API: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_builds(version: &str) {
|
||||
let client = papermc_api::Client::new();
|
||||
|
||||
match client.project("paper").version(version).builds() {
|
||||
Ok(builds) => {
|
||||
for build in builds {
|
||||
println!("- id : {}", build.id);
|
||||
println!(" time : {}", build.time.with_timezone(&Local));
|
||||
println!(" channel: {}", build.channel);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("failed to query API: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn download_build(version: &str, build: &str, out_path: Option<&Path>) {
|
||||
let client = papermc_api::Client::new();
|
||||
let build = match client.project("paper").version(version).build(build) {
|
||||
Ok(build) => build,
|
||||
Err(err) => {
|
||||
println!("failed to query API: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let filename = format!("paper-{}-{}.jar", version, build.id);
|
||||
let dest_path = match out_path {
|
||||
Some(path) if path.is_dir() => path.join(filename),
|
||||
Some(path) => path.to_path_buf(),
|
||||
None => PathBuf::from(filename),
|
||||
};
|
||||
|
||||
let download_url = match build
|
||||
.downloads
|
||||
.get("server:default")
|
||||
.or(build.downloads.values().next())
|
||||
{
|
||||
Some(download) => &download.url,
|
||||
None => {
|
||||
println!("no download URLs found for build.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut f = match std::fs::File::create(dest_path) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
println!("failed to create destination file: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut res = match ureq::get(download_url).call() {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
println!("failed to download file: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut reader = res.body_mut().as_reader();
|
||||
if let Err(err) = std::io::copy(&mut reader, &mut f) {
|
||||
println!("failed to download file: {err}");
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||
pub enum Error {
|
||||
IO(io::Error),
|
||||
Figment(figment::Error),
|
||||
Other(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
|
|
@ -13,6 +14,7 @@ impl fmt::Display for Error {
|
|||
match self {
|
||||
Error::IO(err) => write!(fmt, "{}", err),
|
||||
Error::Figment(err) => write!(fmt, "{}", err),
|
||||
Error::Other(err) => write!(fmt, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue