mirror of
https://github.com/LordMZTE/mcstat.git
synced 2024-05-03 13:21:10 +02:00
forge 1.16 and forge channels support
This commit is contained in:
parent
60df88dee1
commit
6db20e631b
|
@ -11,7 +11,7 @@ clap = { version = "^2.33", features = ["yaml"] }
|
||||||
image-base64 = "0.1.0"
|
image-base64 = "0.1.0"
|
||||||
image = "0.23.9"
|
image = "0.23.9"
|
||||||
asciify = "0.1.6"
|
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"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
termcolor = "1"
|
termcolor = "1"
|
||||||
|
|
129
src/args.yml
129
src/args.yml
|
@ -1,69 +1,72 @@
|
||||||
name: "mcstat"
|
name: "mcstat"
|
||||||
about: "queries information about a minecraft server"
|
about: "queries information about a minecraft server"
|
||||||
args:
|
args:
|
||||||
- ip:
|
- ip:
|
||||||
help: "the ip of the server to ping"
|
help: "the ip of the server to ping"
|
||||||
takes_value: true
|
takes_value: true
|
||||||
index: 1
|
index: 1
|
||||||
required: true
|
required: true
|
||||||
- port:
|
- port:
|
||||||
help: "the port of the server"
|
help: "the port of the server"
|
||||||
long: "port"
|
long: "port"
|
||||||
short: p
|
short: p
|
||||||
default_value: "25565"
|
default_value: "25565"
|
||||||
takes_value: true
|
takes_value: true
|
||||||
- protocol-version:
|
- protocol-version:
|
||||||
long: "protocol"
|
long: "protocol"
|
||||||
help: "the protocol version to use"
|
help: "the protocol version to use"
|
||||||
default_value: "751"
|
default_value: "751"
|
||||||
takes_value: true
|
takes_value: true
|
||||||
- timeout:
|
- timeout:
|
||||||
long: "timeout"
|
long: "timeout"
|
||||||
short: t
|
short: t
|
||||||
help: "the time before the server ping times out in milliseconds"
|
help: "the time before the server ping times out in milliseconds"
|
||||||
takes_value: true
|
takes_value: true
|
||||||
default_value: "500"
|
default_value: "500"
|
||||||
- raw:
|
- raw:
|
||||||
short: r
|
short: r
|
||||||
help: "if supplied, the raw json response from the server will be printed"
|
help: "if supplied, the raw json response from the server will be printed"
|
||||||
- mods:
|
- mods:
|
||||||
short: m
|
short: m
|
||||||
help: "if supplied, a mod list will be printed"
|
help: "if supplied, a mod list will be printed"
|
||||||
- modversions:
|
- modversions:
|
||||||
short: v
|
short: v
|
||||||
help: "if supplied, mods will also have their version info printed"
|
help: "if supplied, mods will also have their version info printed"
|
||||||
requires: "mods"
|
requires: "mods"
|
||||||
|
- channels:
|
||||||
|
long: "channels"
|
||||||
|
help: "displays forge mod channels if the server sends them"
|
||||||
|
|
||||||
# IMAGE ARGS
|
# IMAGE ARGS
|
||||||
# TODO due to a bug in clap, the image argument is always required because size has a default value
|
# TODO due to a bug in clap, the image argument is always required because size has a default value
|
||||||
- image:
|
- image:
|
||||||
short: "i"
|
short: "i"
|
||||||
help: "if the server's favicon should be printed as ASCII art"
|
help: "if the server's favicon should be printed as ASCII art"
|
||||||
required: false
|
required: false
|
||||||
- color:
|
- color:
|
||||||
short: "c"
|
short: "c"
|
||||||
help: "if the favicon image should be printed with ANSI color formatting or monochrome"
|
help: "if the favicon image should be printed with ANSI color formatting or monochrome"
|
||||||
- size:
|
- size:
|
||||||
short: "s"
|
short: "s"
|
||||||
help: "the size of the image"
|
help: "the size of the image"
|
||||||
takes_value: true
|
takes_value: true
|
||||||
default_value: "16"
|
default_value: "16"
|
||||||
- deep:
|
- deep:
|
||||||
short: "d"
|
short: "d"
|
||||||
help: "if provided the ascii image will have more different characters"
|
help: "if provided the ascii image will have more different characters"
|
||||||
- invert:
|
- invert:
|
||||||
short: "n"
|
short: "n"
|
||||||
help: "inverts the ascii image thickness"
|
help: "inverts the ascii image thickness"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
- img-flags:
|
- img-flags:
|
||||||
# TODO uncomment once bug is fixed
|
# TODO uncomment once bug is fixed
|
||||||
# requires:
|
# requires:
|
||||||
# - image
|
# - image
|
||||||
multiple: true
|
multiple: true
|
||||||
required: false
|
required: false
|
||||||
args:
|
args:
|
||||||
- color
|
- color
|
||||||
- size
|
- size
|
||||||
- deep
|
- deep
|
||||||
- invert
|
- invert
|
||||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -1,4 +1,5 @@
|
||||||
use asciify::AsciiBuilder;
|
use asciify::AsciiBuilder;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
/// prints a table with the entries supplied
|
/// prints a table with the entries supplied
|
||||||
/// the identifier at the start of each entry sets the type
|
/// the identifier at the start of each entry sets the type
|
||||||
|
@ -84,3 +85,31 @@ pub fn remove_formatting(s: &str) -> String {
|
||||||
}
|
}
|
||||||
buf
|
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 anyhow::{Context, Result};
|
||||||
use asciify::AsciiBuilder;
|
use asciify::AsciiBuilder;
|
||||||
use async_minecraft_ping::{ConnectionConfig, ModInfo, ServerDescription, StatusResponse};
|
use async_minecraft_ping::{ConnectionConfig, ServerDescription, StatusResponse};
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use image::ImageFormat;
|
use image::ImageFormat;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use mcstat::{remove_formatting, AsciiConfig};
|
use mcstat::{remove_formatting, AsciiConfig, get_table};
|
||||||
use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor};
|
use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor};
|
||||||
|
|
||||||
/// this message is used if getting a value from the arguments fails
|
/// 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")?;
|
.context("image size must be number")?;
|
||||||
let mut image = None;
|
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
|
// The image parsing and asciifying is done while the table is printing
|
||||||
image = Some(tokio::spawn(asciify_base64_image(
|
image = Some(tokio::spawn(asciify_base64_image(
|
||||||
favicon,
|
favicon.clone(),
|
||||||
AsciiConfig {
|
AsciiConfig {
|
||||||
size: Some(image_size),
|
size: Some(image_size),
|
||||||
colored: matches.is_present("color"),
|
colored: matches.is_present("color"),
|
||||||
|
@ -114,7 +114,8 @@ async fn main() -> Result<()> {
|
||||||
let player_sample = response
|
let player_sample = response
|
||||||
.players
|
.players
|
||||||
.sample
|
.sample
|
||||||
.unwrap_or_default()
|
.as_ref()
|
||||||
|
.unwrap_or(&vec![])
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| p.name.as_str())
|
.map(|p| p.name.as_str())
|
||||||
.intersperse("\n")
|
.intersperse("\n")
|
||||||
|
@ -124,12 +125,12 @@ async fn main() -> Result<()> {
|
||||||
40;
|
40;
|
||||||
bo "Description" => none_if_empty!(remove_formatting(&response.description.get_text())),
|
bo "Description" => none_if_empty!(remove_formatting(&response.description.get_text())),
|
||||||
bo "Extra Description" => {
|
bo "Extra Description" => {
|
||||||
if let ServerDescription::Big(big_desc) = response.description {
|
if let ServerDescription::Big(big_desc) = &response.description {
|
||||||
let desc = big_desc.extra;
|
let desc = &big_desc.extra;
|
||||||
if desc.is_empty() {
|
if desc.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(desc.into_iter().map(|p| p.text).collect::<String>())
|
Some(desc.into_iter().map(|p| p.text.clone()).collect::<String>())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -137,14 +138,31 @@ async fn main() -> Result<()> {
|
||||||
},
|
},
|
||||||
bo "Player Sample" => none_if_empty!(remove_formatting(&player_sample)),
|
bo "Player Sample" => none_if_empty!(remove_formatting(&player_sample)),
|
||||||
lo "Server Version" => none_if_empty!(remove_formatting(&response.version.name)),
|
lo "Server Version" => none_if_empty!(remove_formatting(&response.version.name)),
|
||||||
l "Online Players" => response.players.online,
|
l "Online Players" => &response.players.online,
|
||||||
l "Max Players" => response.players.max,
|
l "Max Players" => &response.players.max,
|
||||||
bo "Mods" => if let (Some(mods), true) = (response.modinfo, matches.is_present("mods")) {
|
bo "Mods" => if let (Some(mod_list), true) = (response.forge_mod_info(), matches.is_present("mods")) {
|
||||||
Some(get_modlist(mods, matches.is_present("modversions")))
|
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 {
|
} else {
|
||||||
None
|
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 {
|
if let Some(img) = image {
|
||||||
|
@ -191,39 +209,3 @@ async fn asciify_base64_image(favicon: String, config: AsciiConfig) -> Result<St
|
||||||
|
|
||||||
Ok(out)
|
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