forge 1.16 and forge channels support

This commit is contained in:
LordMZTE 2020-10-28 14:45:56 +01:00
parent 60df88dee1
commit 6db20e631b
4 changed files with 127 additions and 113 deletions

View File

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

View File

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

View File

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

View File

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