Too unmotivated to finalize, will do something else with DB at some point.
All checks were successful
Build and push container image / build (push) Successful in 10m41s

This commit is contained in:
toast 2024-02-01 18:00:57 +11:00
parent 76ad298af7
commit 776c3e2f1c
7 changed files with 235 additions and 71 deletions

2
Cargo.lock generated
View File

@ -822,7 +822,7 @@ dependencies = [
[[package]]
name = "kon"
version = "0.1.12"
version = "0.1.13"
dependencies = [
"cargo_toml",
"gamedig",

View File

@ -1,6 +1,6 @@
[package]
name = "kon"
version = "0.1.12"
version = "0.1.13"
rust-version = "1.74"
edition = "2021"

View File

@ -5,9 +5,15 @@ use crate::{
};
use serenity::{
futures::{
stream::iter,
future::ready,
Stream,
StreamExt
},
all::Mentionable,
builder::CreateActionRow,
builder::CreateEmbed
builder::CreateEmbed,
};
use poise::{
CreateReply,
@ -46,10 +52,10 @@ pub async fn add(
}
let action_row = CreateActionRow::Buttons(vec![
serenity_prelude::CreateButton::new("confirm")
serenity_prelude::CreateButton::new("add-confirm")
.style(ButtonStyle::Success)
.label("Yes"),
serenity_prelude::CreateButton::new("cancel")
serenity_prelude::CreateButton::new("add-cancel")
.style(ButtonStyle::Danger)
.label("No")
]);
@ -76,7 +82,7 @@ pub async fn add(
.timeout(std::time::Duration::from_secs(30))
.await
{
if collector.data.custom_id == "confirm" {
if collector.data.custom_id == "add-confirm" {
let result = Gameservers::add_server(
ctx.guild_id().unwrap().into(),
server_name.as_str(),
@ -93,6 +99,7 @@ pub async fn add(
ctx,
serenity_prelude::EditMessage::new()
.content("*Confirmed, added the server to database*")
.embeds(Vec::new())
.components(Vec::new())
).await?;
},
@ -101,17 +108,19 @@ pub async fn add(
ctx,
serenity_prelude::EditMessage::new()
.content(format!("*Error adding server to database: {:?}*", y))
.embeds(Vec::new())
.components(Vec::new())
).await?;
}
}
} else if collector.data.custom_id == "cancel" {
} else if collector.data.custom_id == "add-cancel" {
let mut msg = collector.message.clone();
msg.edit(
ctx,
serenity_prelude::EditMessage::new()
.content("*Command cancelled*")
.embeds(Vec::new())
.components(Vec::new())
).await?;
}
@ -122,16 +131,102 @@ pub async fn add(
/// Remove a game server from the database
#[poise::command(slash_command)]
pub async fn remove(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
ctx.send(CreateReply::default().content("Yet to be implemented.")).await?;
pub async fn remove(
ctx: poise::Context<'_, (), Error>,
#[description = "Server name"] #[autocomplete = "ac_server_name"] server_name: String
) -> Result<(), Error> {
let reply = CreateReply::default()
.embed(CreateEmbed::new()
.title("Are you sure you want to remove this server?")
.description(format!("**Server name:** `{}`", server_name))
.color(EMBED_COLOR)
)
.components(vec![
CreateActionRow::Buttons(vec![
serenity_prelude::CreateButton::new("delete-confirm")
.style(ButtonStyle::Success)
.label("Yes"),
serenity_prelude::CreateButton::new("delete-cancel")
.style(ButtonStyle::Danger)
.label("No")
])
]);
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))
.await
{
if collector.data.custom_id == "delete-confirm" {
let result = Gameservers::remove_server(ctx.guild_id().unwrap().into(), server_name.as_str()).await;
let mut msg = collector.message.clone();
match result {
Ok(_) => {
msg.edit(
ctx,
serenity_prelude::EditMessage::new()
.content("*Confirmed, removed the server from database*")
.embeds(Vec::new())
.components(Vec::new())
).await?;
},
Err(y) => {
msg.edit(
ctx,
serenity_prelude::EditMessage::new()
.content(format!("*Error removing server from database: {:?}*", y))
.embeds(Vec::new())
.components(Vec::new())
).await?;
}
}
} else if collector.data.custom_id == "delete-cancel" {
let mut msg = collector.message.clone();
msg.edit(
ctx,
serenity_prelude::EditMessage::new()
.content("*Command cancelled*")
.embeds(Vec::new())
.components(Vec::new())
).await?;
}
}
Ok(())
}
/// Update a game server in the database
#[poise::command(slash_command)]
pub async fn update(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
ctx.send(CreateReply::default().content("Yet to be implemented.")).await?;
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 = "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(),
&ip_address
).await;
match result {
Ok(_) => {
ctx.send(CreateReply::default().content("Updated the server in database.")).await?;
},
Err(y) => {
ctx.send(CreateReply::default().content(format!("Error updating the server in database: {:?}", y))).await?;
}
}
Ok(())
}
@ -158,3 +253,22 @@ pub async fn list(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(())
}
pub async fn ac_server_name<'a>(
ctx: poise::Context<'_, (), Error>,
partial: &'a str
) -> impl Stream<Item = String> + 'a {
let result = Gameservers::get_server_names(ctx.guild_id().unwrap().into()).await;
let names = match result {
Ok(names_vector) => names_vector,
Err(y) => {
println!("Error retrieving server names: {:?}", y);
Vec::new()
}
};
iter(names)
.filter(move |server_name| ready(server_name.starts_with(partial)))
.map(|server_name| server_name.to_string())
}

View File

@ -1,15 +1,20 @@
use crate::{
models::mpservers::MPServers,
Error,
EMBED_COLOR,
Error
models::gameservers::Gameservers,
commands::gameserver::ac_server_name
};
use gamedig::protocols::{
valve::{
Engine, GatheringSettings, Response
Engine,
Response,
GatheringSettings
},
types::TimeoutSettings,
valve
valve,
minecraft,
minecraft::RequestSettings,
types::TimeoutSettings
};
use std::{
str::FromStr,
@ -22,18 +27,21 @@ use reqwest::{
Client,
header::USER_AGENT
};
use tokio::{
net::lookup_host,
join
};
use poise::CreateReply;
use serenity::builder::CreateEmbed;
use once_cell::sync::Lazy;
use cargo_toml::Manifest;
use serde_json::Value;
use tokio::join;
static PMS_BASE: Lazy<String> = Lazy::new(||
var("WG_PMS").expect("Expected a \"WG_PMS\" in the envvar but none was found")
);
fn query_server() -> Result<Response, Error> {
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);
@ -62,6 +70,35 @@ fn query_server() -> Result<Response, Error> {
Ok(response?)
}
async fn query_gameserver(ip_address: &str) -> Result<minecraft::JavaResponse, Box<dyn std::error::Error + Send + Sync>> {
println!("Querying {}", ip_address);
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))
}
}
async fn pms_serverstatus(url: &str) -> Result<Vec<Value>, Error> {
let bot_version = Manifest::from_path("Cargo.toml").unwrap().package.unwrap().version.unwrap();
@ -77,7 +114,7 @@ async fn pms_serverstatus(url: &str) -> Result<Vec<Value>, Error> {
}
/// Query the server statuses
#[poise::command(slash_command, subcommands("ats", "wg", "fs"), subcommand_required)]
#[poise::command(slash_command, subcommands("ats", "wg", "mc"), subcommand_required)]
pub async fn status(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(())
}
@ -86,7 +123,7 @@ pub async fn status(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
#[poise::command(slash_command)]
pub async fn ats(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
let embed = CreateEmbed::new().color(EMBED_COLOR);
match query_server() {
match query_ats_server() {
Ok(response) => {
ctx.send(CreateReply::default()
.embed(embed
@ -138,17 +175,38 @@ pub async fn wg(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(())
}
/// Retrieve the data from Farming Simulator 22 server
/// Retrieve the server data from given Minecraft Java server
#[poise::command(slash_command, guild_only)]
pub async fn fs(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
// let embed = CreateEmbed::new().color(EMBED_COLOR);
let server = MPServers::get_server_ip(ctx.guild_id().unwrap().into(), "testserver").await?;
let ip = server.0;
let md5 = server.1;
pub async fn mc(
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;
ctx.send(CreateReply::default().content(format!("IP: {}\nMD5: {}", ip, md5))).await?;
match server {
Ok(data) => {
let name = &data[0];
let game = &data[1];
let ip = &data[2];
// ctx.send(CreateReply::default().content("This command is not yet implemented")).await?;
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?;
},
Err(why) => {
ctx.send(CreateReply::default().content(format!("Error retrieving the server data: {:?}", why))).await?;
}
}
Ok(())
}

View File

@ -8,7 +8,6 @@ pub struct Gameservers {
pub ip_address: String
}
#[allow(dead_code)]
impl Gameservers {
pub async fn list_servers(guild_id: u64) -> Result<Vec<Self>, tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
@ -74,7 +73,8 @@ impl Gameservers {
Ok(())
}
pub async fn update_name(guild_id: u64, server_name: &str, new_name: &str) -> Result<(), tokio_postgres::Error> {
// 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
@ -83,5 +83,37 @@ impl Gameservers {
", &[&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("
SELECT server_name FROM gameservers
WHERE guild_owner = $1
", &[&(guild_id as i64)]).await?;
let mut servers = Vec::new();
for row in rows {
servers.push(row.get("server_name"));
}
Ok(servers)
}
pub async fn get_server_data(guild_id: u64, server_name: &str) -> Result<Vec<String>, tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
let rows = client.query("
SELECT * FROM gameservers
WHERE guild_owner = $1 AND server_name = $2
", &[&(guild_id as i64), &server_name]).await?;
let mut server = Vec::new();
for row in rows {
server.push(row.get("server_name"));
server.push(row.get("game_name"));
server.push(row.get("ip_address"))
}
Ok(server)
}
}

View File

@ -1,2 +1 @@
pub mod mpservers;
pub mod gameservers;

View File

@ -1,39 +0,0 @@
use crate::controllers::database::DatabaseController;
// #[derive(Debug)]
pub struct MPServers {
pub server_name: String,
pub guild_owner: i64,
pub is_active: bool,
pub ip_address: String,
pub md5_code: String
}
#[allow(dead_code)]
impl MPServers {
pub async fn get_servers(guild_id: u64) -> Result<Self, tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
let row = client.query_one("
SELECT * FROM mpservers
WHERE guild_owner = $1
", &[&(guild_id as i64)]).await?;
Ok(Self {
server_name: row.get("server_name"),
guild_owner: row.get("guild_owner"),
is_active: row.get("is_active"),
ip_address: row.get("ip_address"),
md5_code: row.get("md5_code")
})
}
pub async fn get_server_ip(guild_id: u64, server_name: &str) -> Result<(String, String), tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
let row = client.query_one("
SELECT ip_address, md5_code FROM mpservers
WHERE guild_owner = $1 AND server_name = $2
", &[&(guild_id as i64), &server_name]).await?;
Ok((row.get("ip_address"), row.get("md5_code")))
}
}