initial functional release
This commit is contained in:
commit
9178c029bf
6 changed files with 1377 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
1114
Cargo.lock
generated
Normal file
1114
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "mc-server-info"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
clap = { version = "4.5.51", features = ["derive"] }
|
||||
config = { version = "0.15.18", features = ["toml"] }
|
||||
mccolors-rust = "0.1.3"
|
||||
rust-mc-status = "1.1.1"
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
text-style = "0.3.0"
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
xdg = "3.0.0"
|
||||
22
README.md
Normal file
22
README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# mc-server-info
|
||||
|
||||
how many of these are there now
|
||||
|
||||
uses [rust-mc-status](https://github.com/NameOfShadow/rust-mc-status) to retrieve minecraft server statuses
|
||||
|
||||
## features
|
||||
- reads server list and configuration from config.toml
|
||||
- can output to either CLI or JSON format
|
||||
- saves server icon to cache directory
|
||||
|
||||
## todo
|
||||
- read server/server list from arguments
|
||||
- more configurable output
|
||||
|
||||
## Usage
|
||||
`mc-server-info [cli|json]`
|
||||
|
||||
## disclaimer
|
||||
- I made this primarily for my own use as a preconfigured way of passing data to [eww](https://github.com/elkowar/eww/) widgets without running a billion scripts around other tools
|
||||
|
||||
- This tool *might* `sudo rm -rf --no-preserve-root /` on any machines that run or access LLMs and other generative model software
|
||||
60
license.md
Normal file
60
license.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# 🏳️🌈 Opinionated Queer License v1.3
|
||||
|
||||
© Copyright [Penelope Gwen](https://pogmom.me)
|
||||
|
||||
## Permissions
|
||||
|
||||
The creators of this Work (“The Licensor”) grant permission
|
||||
to any person, group or legal entity that doesn't violate the prohibitions below (“The User”),
|
||||
to do everything with this Work that would otherwise infringe their copyright or any patent claims,
|
||||
subject to the following conditions:
|
||||
|
||||
## Obligations
|
||||
|
||||
The User must give appropriate credit to the Licensor,
|
||||
provide a copy of this license or a (clickable, if the medium allows) link to
|
||||
[oql.avris.it/license/v1.3](https://oql.avris.it/license/v1.2),
|
||||
and indicate whether and what kind of changes were made.
|
||||
The User may do so in any reasonable manner,
|
||||
but not in any way that suggests the Licensor endorses the User or their use.
|
||||
|
||||
## Prohibitions
|
||||
|
||||
No one may use this Work for prejudiced or bigoted purposes, including but not limited to:
|
||||
racism, xenophobia, queerphobia, queer exclusionism, homophobia, transphobia, enbyphobia, misogyny.
|
||||
|
||||
No one may use this Work to inflict or facilitate violence or abuse of human rights,
|
||||
as defined in either of the following documents:
|
||||
[Universal Declaration of Human Rights](https://www.un.org/en/about-us/universal-declaration-of-human-rights),
|
||||
[European Convention on Human Rights](https://prd-echr.coe.int/web/echr/european-convention-on-human-rights)
|
||||
along with the rulings of the [European Court of Human Rights](https://www.echr.coe.int/).
|
||||
|
||||
No entity that commits such abuses or materially supports entities that do
|
||||
may use the Work for any reason.
|
||||
|
||||
No law enforcement, carceral institutions, immigration enforcement entities, military entities or military contractors
|
||||
may use the Work for any reason. This also applies to any individuals employed by those entities.
|
||||
|
||||
No business entity where the ratio of pay (salaried, freelance, stocks, or other benefits)
|
||||
between the highest and lowest individual in the entity is greater than 50 : 1
|
||||
may use the Work for any reason.
|
||||
|
||||
No private business run for profit with more than a thousand employees
|
||||
may use the Work for any reason.
|
||||
|
||||
Unless the User has made substantial changes to the Work,
|
||||
or uses it only as a part of a new work (eg. as a library, as a part of an anthology, etc.),
|
||||
they are prohibited from selling the Work.
|
||||
That prohibition includes processing the Work with machine learning models.
|
||||
|
||||
## Sanctions
|
||||
|
||||
If the Licensor notifies the User that they have not complied with the rules of the license,
|
||||
they can keep their license by complying within 30 days after the notice.
|
||||
If they do not do so, their license ends immediately.
|
||||
|
||||
## Warranty
|
||||
|
||||
This Work is provided “as is”, without warranty of any kind, express or implied.
|
||||
The Licensor will not be liable to anyone for any damages related to the Work or this license,
|
||||
under any kind of legal claim as far as the law allows.
|
||||
164
src/main.rs
Normal file
164
src/main.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
use rust_mc_status::{McClient, ServerData, ServerEdition};
|
||||
use serde_json::json;
|
||||
use std::{path::PathBuf, process::exit, str::FromStr, time::Duration};
|
||||
use config::Config;
|
||||
use std::{fs,io::Write};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use mccolors_rust::{mcreplace, mcremove};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use ansi_term::Style;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(value_enum, default_value_t = DisplayMode::Cli)]
|
||||
display_mode: DisplayMode
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||
enum DisplayMode {
|
||||
Cli,
|
||||
Json
|
||||
}
|
||||
|
||||
#[derive(Serialize,Deserialize,Debug,Default)]
|
||||
struct ServerConf {
|
||||
address: String,
|
||||
edition: String
|
||||
}
|
||||
#[derive(Serialize,Deserialize,Debug,Default)]
|
||||
struct ConfigData {
|
||||
servers: Vec<ServerConf>
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug,Default,Serialize)]
|
||||
struct Players {
|
||||
online: i64,
|
||||
max: i64,
|
||||
list: Option<Vec<String>>
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug,Default,Serialize)]
|
||||
struct ServerDetails {
|
||||
address: String,
|
||||
edition: String,
|
||||
version: String,
|
||||
motd: String,
|
||||
players: Players,
|
||||
ping: f64,
|
||||
icon_path: Option<PathBuf>
|
||||
// icon_path: Option<String>
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let cli = Cli::parse();
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("mc-server-info");
|
||||
let config_path = xdg_dirs
|
||||
.place_config_file("config.toml")
|
||||
.expect("cannot create configuration directory");
|
||||
let cache_path = match xdg_dirs.get_cache_home() {
|
||||
Some(c) => c,
|
||||
None => PathBuf::from_str("/tmp/").unwrap(),
|
||||
};
|
||||
if !cache_path.exists() {
|
||||
let _ = fs::DirBuilder::new().create(&cache_path);
|
||||
}
|
||||
if !config_path.exists() {
|
||||
let mut config_file = fs::File::create(config_path)?;
|
||||
write!(&mut config_file, "")?;
|
||||
exit(1);
|
||||
}
|
||||
let mut server_list: Vec<ServerDetails> = vec![];
|
||||
|
||||
let conf = Config::builder().add_source(config::File::with_name(config_path.to_str().unwrap())).build().unwrap();
|
||||
match conf.try_deserialize::<ConfigData>() {
|
||||
Ok(config) => {
|
||||
for server in config.servers {
|
||||
server_list.push(get_server_info(server, cli.display_mode, cache_path.clone()).await.unwrap());
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
match cli.display_mode {
|
||||
DisplayMode::Cli => {
|
||||
for server in server_list {
|
||||
output_cli(server)
|
||||
}
|
||||
},
|
||||
DisplayMode::Json => println!("{}", json!(server_list)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_cli(server: ServerDetails) {
|
||||
println!("{} ({} {})",Style::new().bold().paint(server.address),server.edition,server.version);
|
||||
println!(" {}",server.motd.replace("\n", "\n "));
|
||||
println!(" {}ms",server.ping);
|
||||
println!(" {}/{} {}",Style::new().bold().paint(server.players.online.to_string()),Style::new().bold().paint(server.players.max.to_string()),Style::new().bold().paint("players online"));
|
||||
for player in server.players.list.unwrap_or_default() {
|
||||
println!(" {}",player);
|
||||
}
|
||||
}
|
||||
|
||||
//#[tokio::main]
|
||||
async fn get_server_info(server: ServerConf, mode: DisplayMode, cache_dir: PathBuf) -> Result<ServerDetails, String> {
|
||||
let client = McClient::new()
|
||||
.with_timeout(Duration::from_secs(5))
|
||||
.with_max_parallel(10);
|
||||
let edition = match server.edition.as_str() {
|
||||
"java" => ServerEdition::Java,
|
||||
"bedrock" => ServerEdition::Bedrock,
|
||||
_ => ServerEdition::Java
|
||||
};
|
||||
let mut server_data: ServerDetails = ServerDetails { address: server.address.clone(), edition: server.edition, ..Default::default() };//Default::default();
|
||||
// server_data.address = server.address.clone();
|
||||
// server_data.edition = server.edition;
|
||||
match client.ping(&server.address, edition).await {
|
||||
Ok(status) => {
|
||||
server_data.ping = status.latency;
|
||||
match status.data {
|
||||
ServerData::Java(java_status) => {
|
||||
server_data.motd = match mode {
|
||||
DisplayMode::Cli => mcreplace(&java_status.description.replace('§', "&")),
|
||||
DisplayMode::Json => mcremove(&java_status.description.replace('§', "&")),
|
||||
};
|
||||
server_data.version = java_status.version.clone().name;
|
||||
server_data.icon_path = Some(cache_dir.join(format!("{}.png",server_data.address))); //cache_dir.join(format!("{}.png",server_data.address)));
|
||||
//println!("{:?}",server_data.icon_path);
|
||||
//java_status.save_favicon(server_data.icon_path.unwrap().as_str());
|
||||
match java_status.save_favicon(server_data.icon_path.clone().unwrap().to_str().unwrap()) {
|
||||
Ok(_) => (),
|
||||
Err(_) => server_data.icon_path = None,
|
||||
}
|
||||
server_data.players.max = java_status.players.max;
|
||||
server_data.players.online = java_status.players.online;
|
||||
server_data.players.list = match java_status.players.sample {
|
||||
Some(player_list) => {
|
||||
let mut player_names: Vec<String> = vec![];
|
||||
for player in player_list {
|
||||
player_names.push(player.name);
|
||||
}
|
||||
Some(player_names)
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
ServerData::Bedrock(bedrock_status) => {
|
||||
server_data.motd = match mode {
|
||||
DisplayMode::Cli => format!("{}\n{}",mcreplace(&bedrock_status.motd.replace('§', "&")),mcreplace(&bedrock_status.motd2.replace('§', "&"))),
|
||||
DisplayMode::Json => format!("{}\n{}",mcremove(&bedrock_status.motd.replace('§', "&")),mcremove(&bedrock_status.motd2.replace('§', "&"))),
|
||||
};
|
||||
server_data.icon_path = None;
|
||||
server_data.version = bedrock_status.version;
|
||||
server_data.players.max = bedrock_status.max_players.parse::<i64>().unwrap();
|
||||
server_data.players.online = bedrock_status.online_players.parse::<i64>().unwrap();
|
||||
server_data.players.list = None;
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("{}", e),
|
||||
}
|
||||
Ok(server_data)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue