I am very satisfied with the results.
All checks were successful
Build and push container image / build (push) Successful in 10m9s
All checks were successful
Build and push container image / build (push) Successful in 10m9s
This commit is contained in:
parent
a870fe807a
commit
c81e682de0
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -822,13 +822,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kon"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
dependencies = [
|
||||
"cargo_toml",
|
||||
"gamedig",
|
||||
"once_cell",
|
||||
"poise",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serenity",
|
||||
"sysinfo",
|
||||
@ -1520,18 +1521,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.194"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
|
||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.194"
|
||||
version = "1.0.196"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
|
||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "kon"
|
||||
version = "0.1.13"
|
||||
version = "0.1.14"
|
||||
rust-version = "1.74"
|
||||
edition = "2021"
|
||||
|
||||
@ -12,6 +12,7 @@ gamedig = "0.4.1"
|
||||
once_cell = "1.19.0"
|
||||
poise = "0.6.1"
|
||||
reqwest = { version = "0.11.24", features = ["json"] }
|
||||
serde = "1.0.196"
|
||||
serde_json = "1.0.113"
|
||||
serenity = "0.12.0"
|
||||
sysinfo = "0.30.5"
|
||||
|
@ -11,16 +11,22 @@ use serenity::{
|
||||
Stream,
|
||||
StreamExt
|
||||
},
|
||||
all::Mentionable,
|
||||
builder::CreateActionRow,
|
||||
builder::CreateEmbed,
|
||||
};
|
||||
use poise::{
|
||||
CreateReply,
|
||||
serenity_prelude,
|
||||
serenity_prelude::ButtonStyle
|
||||
serenity_prelude::ButtonStyle,
|
||||
ChoiceParameter
|
||||
};
|
||||
|
||||
#[derive(Debug, poise::ChoiceParameter)]
|
||||
enum GameNames {
|
||||
#[name = "Minecraft"]
|
||||
Minecraft
|
||||
}
|
||||
|
||||
/// Manage the game servers for this guild
|
||||
#[poise::command(slash_command, subcommands("add", "remove", "update", "list"), subcommand_required, guild_only)]
|
||||
pub async fn gameserver(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
@ -32,25 +38,9 @@ pub async fn gameserver(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
pub async fn add(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "Server name as shown in-game or friendly name"] server_name: String,
|
||||
#[description = "Which game is this server running?"] game_name: String,
|
||||
#[channel_types("Text")] #[description = "Which channel should this server be restricted to?"] guild_channel: serenity_prelude::GuildChannel,
|
||||
#[description = "Which game is this server running?"] game_name: GameNames,
|
||||
#[description = "IP address/domain of the server (Include the port if it has one, e.g 127.0.0.1:8080)"] ip_address: String
|
||||
) -> Result<(), Error> {
|
||||
let unsupported_games_list = [
|
||||
"ATS",
|
||||
"ETS2",
|
||||
"Euro Truck Simulator 2",
|
||||
"American Truck Simulator",
|
||||
];
|
||||
if unsupported_games_list.contains(&game_name.as_str()) {
|
||||
ctx.send(CreateReply::default()
|
||||
.ephemeral(true)
|
||||
.content(format!("Sorry, `{}` is not supported yet due to database design.", game_name))
|
||||
).await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let action_row = CreateActionRow::Buttons(vec![
|
||||
serenity_prelude::CreateButton::new("add-confirm")
|
||||
.style(ButtonStyle::Success)
|
||||
@ -66,9 +56,8 @@ pub async fn add(
|
||||
.description(format!("
|
||||
**Server name:** `{}`
|
||||
**Game name:** `{}`
|
||||
**Channel:** {}
|
||||
**IP Address:** `{}`
|
||||
", server_name, game_name, guild_channel.mention(), ip_address))
|
||||
", server_name, game_name.name(), ip_address))
|
||||
.color(EMBED_COLOR)
|
||||
)
|
||||
.components(vec![action_row]);
|
||||
@ -76,7 +65,6 @@ pub async fn add(
|
||||
ctx.send(reply).await?;
|
||||
|
||||
while let Some(collector) = serenity_prelude::ComponentInteractionCollector::new(ctx)
|
||||
.channel_id(ctx.channel_id())
|
||||
.guild_id(ctx.guild_id().unwrap())
|
||||
.author_id(ctx.author().id)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
@ -86,8 +74,7 @@ pub async fn add(
|
||||
let result = Gameservers::add_server(
|
||||
ctx.guild_id().unwrap().into(),
|
||||
server_name.as_str(),
|
||||
game_name.as_str(),
|
||||
guild_channel.id.into(),
|
||||
game_name.name(),
|
||||
ip_address.as_str()
|
||||
).await;
|
||||
|
||||
@ -155,7 +142,6 @@ pub async fn remove(
|
||||
ctx.send(reply).await?;
|
||||
|
||||
while let Some(collector) = serenity_prelude::ComponentInteractionCollector::new(ctx)
|
||||
.channel_id(ctx.channel_id())
|
||||
.guild_id(ctx.guild_id().unwrap())
|
||||
.author_id(ctx.author().id)
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
@ -207,15 +193,13 @@ pub async fn remove(
|
||||
pub async fn update(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "Server name"] #[autocomplete = "ac_server_name"] server_name: String,
|
||||
#[description = "Game name"] game_name: String,
|
||||
#[description = "Channel"] #[channel_types("Text")] guild_channel: serenity_prelude::GuildChannel,
|
||||
#[description = "Game name"] game_name: GameNames,
|
||||
#[description = "IP address"] ip_address: String
|
||||
) -> Result<(), Error> {
|
||||
let result = Gameservers::update_server(
|
||||
ctx.guild_id().unwrap().into(),
|
||||
&server_name,
|
||||
&game_name,
|
||||
guild_channel.id.into(),
|
||||
&game_name.name(),
|
||||
&ip_address
|
||||
).await;
|
||||
|
||||
|
@ -5,21 +5,7 @@ use crate::{
|
||||
commands::gameserver::ac_server_name
|
||||
};
|
||||
|
||||
use gamedig::protocols::{
|
||||
valve::{
|
||||
Engine,
|
||||
Response,
|
||||
GatheringSettings
|
||||
},
|
||||
valve,
|
||||
minecraft,
|
||||
minecraft::RequestSettings,
|
||||
types::TimeoutSettings
|
||||
};
|
||||
use std::{
|
||||
str::FromStr,
|
||||
net::SocketAddr,
|
||||
time::Duration,
|
||||
collections::HashMap,
|
||||
env::var
|
||||
};
|
||||
@ -27,76 +13,35 @@ use reqwest::{
|
||||
Client,
|
||||
header::USER_AGENT
|
||||
};
|
||||
use tokio::{
|
||||
net::lookup_host,
|
||||
join
|
||||
};
|
||||
use tokio::join;
|
||||
use poise::CreateReply;
|
||||
use serenity::builder::CreateEmbed;
|
||||
use once_cell::sync::Lazy;
|
||||
use cargo_toml::Manifest;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
static PMS_BASE: Lazy<String> = Lazy::new(||
|
||||
var("WG_PMS").expect("Expected a \"WG_PMS\" in the envvar but none was found")
|
||||
);
|
||||
|
||||
fn query_ats_server() -> Result<Response, Error> {
|
||||
let server_ip = var("ATS_SERVER_IP").expect("Expected a \"ATS_SERVER_IP\" in the envvar but none was found");
|
||||
let addr = SocketAddr::from_str(&server_ip).unwrap();
|
||||
let engine = Engine::Source(None);
|
||||
let gather_settings = GatheringSettings {
|
||||
players: true,
|
||||
rules: false,
|
||||
check_app_id: false
|
||||
};
|
||||
|
||||
let read_timeout = Duration::from_secs(2);
|
||||
let write_timeout = Duration::from_secs(2);
|
||||
let retries = 1;
|
||||
let timeout_settings = TimeoutSettings::new(
|
||||
Some(read_timeout),
|
||||
Some(write_timeout),
|
||||
retries
|
||||
).unwrap();
|
||||
|
||||
let response = valve::query(
|
||||
&addr,
|
||||
engine,
|
||||
Some(gather_settings),
|
||||
Some(timeout_settings)
|
||||
);
|
||||
|
||||
Ok(response?)
|
||||
#[derive(Deserialize)]
|
||||
struct MinecraftQueryData {
|
||||
motd: MinecraftMotd,
|
||||
players: MinecraftPlayers,
|
||||
version: String,
|
||||
online: bool
|
||||
}
|
||||
|
||||
async fn query_gameserver(ip_address: &str) -> Result<minecraft::JavaResponse, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Querying {}", ip_address);
|
||||
#[derive(Deserialize)]
|
||||
struct MinecraftMotd {
|
||||
clean: Vec<String>
|
||||
}
|
||||
|
||||
let full_address = if ip_address.contains(':') {
|
||||
String::from(ip_address)
|
||||
} else {
|
||||
format!("{}:25565", ip_address)
|
||||
};
|
||||
|
||||
let addr = match SocketAddr::from_str(&full_address) {
|
||||
Ok(addr) => addr,
|
||||
Err(_) => {
|
||||
let mut addrs = lookup_host(&full_address).await?;
|
||||
addrs.next().ok_or("Address lookup failed")?
|
||||
}
|
||||
};
|
||||
|
||||
let response = minecraft::query_java(&addr, None, Some(RequestSettings {
|
||||
hostname: addr.to_string(),
|
||||
protocol_version: -1
|
||||
}));
|
||||
println!("{:?}", response);
|
||||
|
||||
match response {
|
||||
Ok(response) => Ok(response),
|
||||
Err(why) => Err(Box::new(why))
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct MinecraftPlayers {
|
||||
online: i32,
|
||||
max: i32
|
||||
}
|
||||
|
||||
async fn pms_serverstatus(url: &str) -> Result<Vec<Value>, Error> {
|
||||
@ -113,30 +58,26 @@ async fn pms_serverstatus(url: &str) -> Result<Vec<Value>, Error> {
|
||||
Ok(servers)
|
||||
}
|
||||
|
||||
/// Query the server statuses
|
||||
#[poise::command(slash_command, subcommands("ats", "wg", "mc"), subcommand_required)]
|
||||
pub async fn status(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
async fn gs_query_minecraft(server_ip: &str) -> Result<MinecraftQueryData, Error> {
|
||||
let bot_version = Manifest::from_path("Cargo.toml").unwrap().package.unwrap().version.unwrap();
|
||||
|
||||
let client = Client::new();
|
||||
let req = client.get(format!("https://api.mcsrvstat.us/2/{}", server_ip))
|
||||
.header(USER_AGENT, format!("Kon/{}/Rust", bot_version))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if req.status().is_success() {
|
||||
let data: MinecraftQueryData = req.json().await?;
|
||||
Ok(data)
|
||||
} else {
|
||||
return Err(Error::from("Failed to query the server."));
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the server status from ATS
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn ats(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
let embed = CreateEmbed::new().color(EMBED_COLOR);
|
||||
match query_ats_server() {
|
||||
Ok(response) => {
|
||||
ctx.send(CreateReply::default()
|
||||
.embed(embed
|
||||
.title("American Truck Simulator Server Status")
|
||||
.fields(vec![
|
||||
("Name", format!("{}", response.info.name), true),
|
||||
("Players", format!("{}/{}", response.info.players_online, response.info.players_maximum), true)
|
||||
])
|
||||
)).await?;
|
||||
}
|
||||
Err(why) => println!("Error querying the server: {:?}", why)
|
||||
}
|
||||
|
||||
/// Query the server statuses
|
||||
#[poise::command(slash_command, subcommands("wg", "gs"), subcommand_required)]
|
||||
pub async fn status(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -175,38 +116,48 @@ pub async fn wg(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the server data from given Minecraft Java server
|
||||
/// Retrieve the given server data from gameservers DB
|
||||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn mc(
|
||||
pub async fn gs(
|
||||
ctx: poise::Context<'_, (), Error>,
|
||||
#[description = "Server name"] #[autocomplete = "ac_server_name"] server_name: String
|
||||
) -> Result<(), Error> {
|
||||
let server = Gameservers::get_server_data(ctx.guild_id().unwrap().into(), &server_name).await;
|
||||
let server_data = Gameservers::get_server_data(ctx.guild_id().unwrap().into(), &server_name).await?;
|
||||
|
||||
match server {
|
||||
Ok(data) => {
|
||||
let name = &data[0];
|
||||
let game = &data[1];
|
||||
let ip = &data[2];
|
||||
// Extract values from a Vec above
|
||||
let game_name = &server_data[1];
|
||||
let ip_address = &server_data[2];
|
||||
|
||||
let query_result = query_gameserver(ip).await?;
|
||||
ctx.send(CreateReply::default()
|
||||
.embed(CreateEmbed::new()
|
||||
.title(format!("{} Server Status", name))
|
||||
.fields(vec![
|
||||
("Game", format!("{}", game), true),
|
||||
("Players", format!("{}/{}", query_result.players_online, query_result.players_maximum), true),
|
||||
("Version", format!("{}", query_result.game_version), true)
|
||||
])
|
||||
.color(EMBED_COLOR)
|
||||
)
|
||||
).await?;
|
||||
// ctx.send(CreateReply::default().content("aaa")).await?;
|
||||
match game_name.as_str() {
|
||||
"Minecraft" => {
|
||||
let result = gs_query_minecraft(ip_address).await?;
|
||||
let embed = CreateEmbed::new().color(EMBED_COLOR);
|
||||
|
||||
if result.online {
|
||||
let mut embed_fields = Vec::new();
|
||||
embed_fields.push(("Server IP".to_owned(), ip_address.to_owned(), true));
|
||||
embed_fields.push((format!("\u{200b}"), format!("\u{200b}"), true));
|
||||
embed_fields.push(("MOTD".to_owned(), format!("{}", result.motd.clean[0]), true));
|
||||
embed_fields.push(("Players".to_owned(), format!("**{}**/**{}**", result.players.online, result.players.max), true));
|
||||
embed_fields.push(("Version".to_owned(), result.version, true));
|
||||
|
||||
ctx.send(CreateReply::default()
|
||||
.embed(embed
|
||||
.title(server_name)
|
||||
.fields(embed_fields)
|
||||
)
|
||||
).await?;
|
||||
} else {
|
||||
ctx.send(CreateReply::default()
|
||||
.content(format!("Server **{}** (`{}`) is currently offline.", server_name, ip_address))
|
||||
).await?;
|
||||
}
|
||||
},
|
||||
Err(why) => {
|
||||
ctx.send(CreateReply::default().content(format!("Error retrieving the server data: {:?}", why))).await?;
|
||||
_ => {
|
||||
ctx.send(CreateReply::default().content("Game not supported yet.")).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -20,24 +20,12 @@ impl DatabaseController {
|
||||
}
|
||||
});
|
||||
|
||||
// MPServers
|
||||
client.batch_execute("
|
||||
CREATE TABLE IF NOT EXISTS mpservers (
|
||||
server_name VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
guild_owner BIGINT NOT NULL,
|
||||
is_active BOOLEAN NOT NULL,
|
||||
ip_address VARCHAR(255) NOT NULL,
|
||||
md5_code VARCHAR(255) NOT NULL
|
||||
);
|
||||
").await?;
|
||||
|
||||
// Gameservers
|
||||
client.batch_execute("
|
||||
CREATE TABLE IF NOT EXISTS gameservers (
|
||||
server_name VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
game_name VARCHAR(255) NOT NULL,
|
||||
guild_owner BIGINT NOT NULL,
|
||||
guild_channel BIGINT NOT NULL,
|
||||
ip_address VARCHAR(255) NOT NULL
|
||||
);
|
||||
").await?;
|
||||
|
@ -4,7 +4,6 @@ pub struct Gameservers {
|
||||
pub server_name: String,
|
||||
pub game_name: String,
|
||||
pub guild_owner: i64,
|
||||
pub guild_channel: i64,
|
||||
pub ip_address: String
|
||||
}
|
||||
|
||||
@ -22,7 +21,6 @@ impl Gameservers {
|
||||
server_name: row.get("server_name"),
|
||||
game_name: row.get("game_name"),
|
||||
guild_owner: row.get("guild_owner"),
|
||||
guild_channel: row.get("guild_channel"),
|
||||
ip_address: row.get("ip_address")
|
||||
});
|
||||
}
|
||||
@ -34,14 +32,13 @@ impl Gameservers {
|
||||
guild_id: u64,
|
||||
server_name: &str,
|
||||
game_name: &str,
|
||||
guild_channel: u64,
|
||||
ip_address: &str
|
||||
) -> Result<(), tokio_postgres::Error> {
|
||||
let client = DatabaseController::new().await?.client;
|
||||
client.execute("
|
||||
INSERT INTO gameservers (server_name, game_name, guild_owner, guild_channel, ip_address)
|
||||
INSERT INTO gameservers (server_name, game_name, guild_owner, ip_address)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
", &[&server_name, &game_name, &(guild_id as i64), &(guild_channel as i64), &ip_address]).await?;
|
||||
", &[&server_name, &game_name, &(guild_id as i64), &ip_address]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -60,31 +57,18 @@ impl Gameservers {
|
||||
guild_id: u64,
|
||||
server_name: &str,
|
||||
game_name: &str,
|
||||
guild_channel: u64,
|
||||
ip_address: &str
|
||||
) -> Result<(), tokio_postgres::Error> {
|
||||
let client = DatabaseController::new().await?.client;
|
||||
client.execute("
|
||||
UPDATE gameservers
|
||||
SET game_name = $1, guild_channel = $2, ip_address = $3
|
||||
WHERE guild_owner = $4 AND server_name = $5
|
||||
", &[&game_name, &(guild_channel as i64), &ip_address, &(guild_id as i64), &server_name]).await?;
|
||||
SET game_name = $1, ip_address = $2
|
||||
WHERE guild_owner = $3 AND server_name = $4
|
||||
", &[&game_name, &ip_address, &(guild_id as i64), &server_name]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// To be added at some point. Not sure if it's needed.
|
||||
/* pub async fn update_name(guild_id: u64, server_name: &str, new_name: &str) -> Result<(), tokio_postgres::Error> {
|
||||
let client = DatabaseController::new().await?.client;
|
||||
client.execute("
|
||||
UPDATE gameservers
|
||||
SET server_name = $1
|
||||
WHERE guild_owner = $2 AND server_name = $3
|
||||
", &[&new_name, &(guild_id as i64), &server_name]).await?;
|
||||
|
||||
Ok(())
|
||||
} */
|
||||
|
||||
pub async fn get_server_names(guild_id: u64) -> Result<Vec<String>, tokio_postgres::Error> {
|
||||
let client = DatabaseController::new().await?.client;
|
||||
let rows = client.query("
|
||||
|
Loading…
Reference in New Issue
Block a user