wip papermc cli stuff

This commit is contained in:
Jef Roosens 2026-04-13 22:44:17 +02:00
parent f2c4b97626
commit 63e1d170bc
Signed by: Jef Roosens
GPG key ID: 21FD3D77D56BAF49
5 changed files with 221 additions and 0 deletions

2
Cargo.lock generated
View file

@ -16,8 +16,10 @@ dependencies = [
"chrono",
"clap",
"figment",
"papermc-api",
"serde",
"signal-hook",
"ureq",
]
[[package]]

View file

@ -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"

View file

@ -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(),
}
}

210
alex/src/cli/papermc.rs Normal file
View file

@ -0,0 +1,210 @@
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 all versions PaperMC jars are available for
ListVersions,
/// List all available builds for a given version
ListBuilds(ListBuildsArgs),
/// 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 ListBuildsArgs {
/// Version to list builds for
version: 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::Show(ShowArgs {
version,
build: Some(build),
}) => show_build(&version, &build),
Commands::ListVersions => print_versions(),
Commands::ListBuilds(args) => print_builds(&args.version),
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!("Min. Java: {}", version.java.minimum_version);
println!("builds: {}", version.builds.len());
}
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 print_versions() {
let client = papermc_api::Client::new();
match client.project("paper").versions() {
Ok(versions) => {
for version in versions {
println!("{}", version.id);
}
}
Err(err) => {
println!("failed to query API: {err}");
}
}
}
fn print_builds(version: &str) {
let client = papermc_api::Client::new();
match client.project("paper").version(version).builds() {
Ok(builds) => {
for build in builds {
println!("{} ({})", build.id, 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}");
}
}

View file

@ -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),
}
}
}