forge 1.16 and forge channels support
This commit is contained in:
parent
60df88dee1
commit
6db20e631b
4 changed files with 127 additions and 113 deletions
|
@ -11,7 +11,7 @@ clap = { version = "^2.33", features = ["yaml"] }
|
|||
image-base64 = "0.1.0"
|
||||
image = "0.23.9"
|
||||
asciify = "0.1.6"
|
||||
async-minecraft-ping = { git = "https://github.com/LordMZTE/async-minecraft-ping.git", tag = "v0.2.3" }
|
||||
async-minecraft-ping = { git = "https://github.com/LordMZTE/async-minecraft-ping.git", tag = "v0.2.4" }
|
||||
tokio = { version = "0.2.22", features = ["full"] }
|
||||
itertools = "0.9.0"
|
||||
termcolor = "1"
|
||||
|
|
129
src/args.yml
129
src/args.yml
|
@ -1,69 +1,72 @@
|
|||
name: "mcstat"
|
||||
about: "queries information about a minecraft server"
|
||||
args:
|
||||
- ip:
|
||||
help: "the ip of the server to ping"
|
||||
takes_value: true
|
||||
index: 1
|
||||
required: true
|
||||
- port:
|
||||
help: "the port of the server"
|
||||
long: "port"
|
||||
short: p
|
||||
default_value: "25565"
|
||||
takes_value: true
|
||||
- protocol-version:
|
||||
long: "protocol"
|
||||
help: "the protocol version to use"
|
||||
default_value: "751"
|
||||
takes_value: true
|
||||
- timeout:
|
||||
long: "timeout"
|
||||
short: t
|
||||
help: "the time before the server ping times out in milliseconds"
|
||||
takes_value: true
|
||||
default_value: "500"
|
||||
- raw:
|
||||
short: r
|
||||
help: "if supplied, the raw json response from the server will be printed"
|
||||
- mods:
|
||||
short: m
|
||||
help: "if supplied, a mod list will be printed"
|
||||
- modversions:
|
||||
short: v
|
||||
help: "if supplied, mods will also have their version info printed"
|
||||
requires: "mods"
|
||||
- ip:
|
||||
help: "the ip of the server to ping"
|
||||
takes_value: true
|
||||
index: 1
|
||||
required: true
|
||||
- port:
|
||||
help: "the port of the server"
|
||||
long: "port"
|
||||
short: p
|
||||
default_value: "25565"
|
||||
takes_value: true
|
||||
- protocol-version:
|
||||
long: "protocol"
|
||||
help: "the protocol version to use"
|
||||
default_value: "751"
|
||||
takes_value: true
|
||||
- timeout:
|
||||
long: "timeout"
|
||||
short: t
|
||||
help: "the time before the server ping times out in milliseconds"
|
||||
takes_value: true
|
||||
default_value: "500"
|
||||
- raw:
|
||||
short: r
|
||||
help: "if supplied, the raw json response from the server will be printed"
|
||||
- mods:
|
||||
short: m
|
||||
help: "if supplied, a mod list will be printed"
|
||||
- modversions:
|
||||
short: v
|
||||
help: "if supplied, mods will also have their version info printed"
|
||||
requires: "mods"
|
||||
- channels:
|
||||
long: "channels"
|
||||
help: "displays forge mod channels if the server sends them"
|
||||
|
||||
# IMAGE ARGS
|
||||
# TODO due to a bug in clap, the image argument is always required because size has a default value
|
||||
- image:
|
||||
short: "i"
|
||||
help: "if the server's favicon should be printed as ASCII art"
|
||||
required: false
|
||||
- color:
|
||||
short: "c"
|
||||
help: "if the favicon image should be printed with ANSI color formatting or monochrome"
|
||||
- size:
|
||||
short: "s"
|
||||
help: "the size of the image"
|
||||
takes_value: true
|
||||
default_value: "16"
|
||||
- deep:
|
||||
short: "d"
|
||||
help: "if provided the ascii image will have more different characters"
|
||||
- invert:
|
||||
short: "n"
|
||||
help: "inverts the ascii image thickness"
|
||||
# IMAGE ARGS
|
||||
# TODO due to a bug in clap, the image argument is always required because size has a default value
|
||||
- image:
|
||||
short: "i"
|
||||
help: "if the server's favicon should be printed as ASCII art"
|
||||
required: false
|
||||
- color:
|
||||
short: "c"
|
||||
help: "if the favicon image should be printed with ANSI color formatting or monochrome"
|
||||
- size:
|
||||
short: "s"
|
||||
help: "the size of the image"
|
||||
takes_value: true
|
||||
default_value: "16"
|
||||
- deep:
|
||||
short: "d"
|
||||
help: "if provided the ascii image will have more different characters"
|
||||
- invert:
|
||||
short: "n"
|
||||
help: "inverts the ascii image thickness"
|
||||
|
||||
groups:
|
||||
- img-flags:
|
||||
# TODO uncomment once bug is fixed
|
||||
# requires:
|
||||
# - image
|
||||
multiple: true
|
||||
required: false
|
||||
args:
|
||||
- color
|
||||
- size
|
||||
- deep
|
||||
- invert
|
||||
- img-flags:
|
||||
# TODO uncomment once bug is fixed
|
||||
# requires:
|
||||
# - image
|
||||
multiple: true
|
||||
required: false
|
||||
args:
|
||||
- color
|
||||
- size
|
||||
- deep
|
||||
- invert
|
||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -1,4 +1,5 @@
|
|||
use asciify::AsciiBuilder;
|
||||
use itertools::Itertools;
|
||||
|
||||
/// prints a table with the entries supplied
|
||||
/// the identifier at the start of each entry sets the type
|
||||
|
@ -84,3 +85,31 @@ pub fn remove_formatting(s: &str) -> String {
|
|||
}
|
||||
buf
|
||||
}
|
||||
|
||||
/// formats a iterator to a readable list
|
||||
///
|
||||
/// if `second_column`, the right strings will also be displayed
|
||||
pub fn get_table<'a>(
|
||||
entries: impl Iterator<Item = (&'a str, &'a str)> + Clone,
|
||||
second_column: bool,
|
||||
) -> String {
|
||||
// the width at which | characters should be placed this is the length of the
|
||||
// longest entry
|
||||
let max_width = if second_column {
|
||||
entries.clone().map(|m| m.0.len()).max().unwrap_or_default()
|
||||
} else {
|
||||
// this will not be used in case second_column is off so we just use 0
|
||||
0
|
||||
};
|
||||
|
||||
entries
|
||||
.map(|m| {
|
||||
if second_column {
|
||||
format!("{: <width$} | {}", m.0, m.1, width = max_width)
|
||||
} else {
|
||||
m.0.to_owned()
|
||||
}
|
||||
})
|
||||
.intersperse("\n".to_owned())
|
||||
.collect()
|
||||
}
|
||||
|
|
80
src/main.rs
80
src/main.rs
|
@ -11,11 +11,11 @@ use tokio::time;
|
|||
|
||||
use anyhow::{Context, Result};
|
||||
use asciify::AsciiBuilder;
|
||||
use async_minecraft_ping::{ConnectionConfig, ModInfo, ServerDescription, StatusResponse};
|
||||
use async_minecraft_ping::{ConnectionConfig, ServerDescription, StatusResponse};
|
||||
use clap::App;
|
||||
use image::ImageFormat;
|
||||
use itertools::Itertools;
|
||||
use mcstat::{remove_formatting, AsciiConfig};
|
||||
use mcstat::{remove_formatting, AsciiConfig, get_table};
|
||||
use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor};
|
||||
|
||||
/// this message is used if getting a value from the arguments fails
|
||||
|
@ -90,10 +90,10 @@ async fn main() -> Result<()> {
|
|||
.context("image size must be number")?;
|
||||
let mut image = None;
|
||||
|
||||
if let (Some(favicon), true) = (response.favicon, matches.is_present("image")) {
|
||||
if let (Some(favicon), true) = (&response.favicon, matches.is_present("image")) {
|
||||
// The image parsing and asciifying is done while the table is printing
|
||||
image = Some(tokio::spawn(asciify_base64_image(
|
||||
favicon,
|
||||
favicon.clone(),
|
||||
AsciiConfig {
|
||||
size: Some(image_size),
|
||||
colored: matches.is_present("color"),
|
||||
|
@ -114,7 +114,8 @@ async fn main() -> Result<()> {
|
|||
let player_sample = response
|
||||
.players
|
||||
.sample
|
||||
.unwrap_or_default()
|
||||
.as_ref()
|
||||
.unwrap_or(&vec![])
|
||||
.iter()
|
||||
.map(|p| p.name.as_str())
|
||||
.intersperse("\n")
|
||||
|
@ -124,12 +125,12 @@ async fn main() -> Result<()> {
|
|||
40;
|
||||
bo "Description" => none_if_empty!(remove_formatting(&response.description.get_text())),
|
||||
bo "Extra Description" => {
|
||||
if let ServerDescription::Big(big_desc) = response.description {
|
||||
let desc = big_desc.extra;
|
||||
if let ServerDescription::Big(big_desc) = &response.description {
|
||||
let desc = &big_desc.extra;
|
||||
if desc.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(desc.into_iter().map(|p| p.text).collect::<String>())
|
||||
Some(desc.into_iter().map(|p| p.text.clone()).collect::<String>())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
@ -137,14 +138,31 @@ async fn main() -> Result<()> {
|
|||
},
|
||||
bo "Player Sample" => none_if_empty!(remove_formatting(&player_sample)),
|
||||
lo "Server Version" => none_if_empty!(remove_formatting(&response.version.name)),
|
||||
l "Online Players" => response.players.online,
|
||||
l "Max Players" => response.players.max,
|
||||
bo "Mods" => if let (Some(mods), true) = (response.modinfo, matches.is_present("mods")) {
|
||||
Some(get_modlist(mods, matches.is_present("modversions")))
|
||||
l "Online Players" => &response.players.online,
|
||||
l "Max Players" => &response.players.max,
|
||||
bo "Mods" => if let (Some(mod_list), true) = (response.forge_mod_info(), matches.is_present("mods")) {
|
||||
Some(get_table(
|
||||
mod_list
|
||||
.iter()
|
||||
.sorted_by(|a, b| a.modid.cmp(&b.modid))
|
||||
.map(|m| (&*m.modid, &*m.version)),
|
||||
matches.is_present("modversions")
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
l "Server Protocol" => response.version.protocol,
|
||||
l "Server Protocol" => &response.version.protocol,
|
||||
bo "Forge Channels" => if let (true, Some(fd)) = (matches.is_present("channels"), response.forge_data) {
|
||||
Some(get_table(
|
||||
fd.channels
|
||||
.iter()
|
||||
.sorted_by(|a, b| a.res.cmp(&b.res))
|
||||
.map(|c| (&*c.res, &*c.version)),
|
||||
true
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(img) = image {
|
||||
|
@ -191,39 +209,3 @@ async fn asciify_base64_image(favicon: String, config: AsciiConfig) -> Result<St
|
|||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// formats a ModInfo to a readable list of mods
|
||||
///
|
||||
/// if `version_info`, the version of the mods will also be displayed
|
||||
fn get_modlist(list: ModInfo, version_info: bool) -> String {
|
||||
let infos = match list {
|
||||
ModInfo::Forge { mod_list: l } => l,
|
||||
};
|
||||
|
||||
// the width at which | characters should be placed this is the length of the
|
||||
// longest modid
|
||||
let max_width = if version_info {
|
||||
infos
|
||||
.iter()
|
||||
.map(|m| m.modid.len())
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
// this will not be used in case version_info is off so we just use 0
|
||||
0
|
||||
};
|
||||
|
||||
infos
|
||||
// we use into_iter instead of iter because a String cannot be collected from &String
|
||||
// and since we don't need infos again
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
if version_info {
|
||||
format!("{: <width$} | {}", m.modid, m.version, width = max_width)
|
||||
} else {
|
||||
m.modid
|
||||
}
|
||||
})
|
||||
.intersperse("\n".to_owned())
|
||||
.collect()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue