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]] [[package]]
name = "kon" name = "kon"
version = "0.1.12" version = "0.1.13"
dependencies = [ dependencies = [
"cargo_toml", "cargo_toml",
"gamedig", "gamedig",

View File

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

View File

@ -5,9 +5,15 @@ use crate::{
}; };
use serenity::{ use serenity::{
futures::{
stream::iter,
future::ready,
Stream,
StreamExt
},
all::Mentionable, all::Mentionable,
builder::CreateActionRow, builder::CreateActionRow,
builder::CreateEmbed builder::CreateEmbed,
}; };
use poise::{ use poise::{
CreateReply, CreateReply,
@ -46,10 +52,10 @@ pub async fn add(
} }
let action_row = CreateActionRow::Buttons(vec![ let action_row = CreateActionRow::Buttons(vec![
serenity_prelude::CreateButton::new("confirm") serenity_prelude::CreateButton::new("add-confirm")
.style(ButtonStyle::Success) .style(ButtonStyle::Success)
.label("Yes"), .label("Yes"),
serenity_prelude::CreateButton::new("cancel") serenity_prelude::CreateButton::new("add-cancel")
.style(ButtonStyle::Danger) .style(ButtonStyle::Danger)
.label("No") .label("No")
]); ]);
@ -76,7 +82,7 @@ pub async fn add(
.timeout(std::time::Duration::from_secs(30)) .timeout(std::time::Duration::from_secs(30))
.await .await
{ {
if collector.data.custom_id == "confirm" { if collector.data.custom_id == "add-confirm" {
let result = Gameservers::add_server( let result = Gameservers::add_server(
ctx.guild_id().unwrap().into(), ctx.guild_id().unwrap().into(),
server_name.as_str(), server_name.as_str(),
@ -93,6 +99,7 @@ pub async fn add(
ctx, ctx,
serenity_prelude::EditMessage::new() serenity_prelude::EditMessage::new()
.content("*Confirmed, added the server to database*") .content("*Confirmed, added the server to database*")
.embeds(Vec::new())
.components(Vec::new()) .components(Vec::new())
).await?; ).await?;
}, },
@ -101,17 +108,19 @@ pub async fn add(
ctx, ctx,
serenity_prelude::EditMessage::new() serenity_prelude::EditMessage::new()
.content(format!("*Error adding server to database: {:?}*", y)) .content(format!("*Error adding server to database: {:?}*", y))
.embeds(Vec::new())
.components(Vec::new()) .components(Vec::new())
).await?; ).await?;
} }
} }
} else if collector.data.custom_id == "cancel" { } else if collector.data.custom_id == "add-cancel" {
let mut msg = collector.message.clone(); let mut msg = collector.message.clone();
msg.edit( msg.edit(
ctx, ctx,
serenity_prelude::EditMessage::new() serenity_prelude::EditMessage::new()
.content("*Command cancelled*") .content("*Command cancelled*")
.embeds(Vec::new())
.components(Vec::new()) .components(Vec::new())
).await?; ).await?;
} }
@ -122,16 +131,102 @@ pub async fn add(
/// Remove a game server from the database /// Remove a game server from the database
#[poise::command(slash_command)] #[poise::command(slash_command)]
pub async fn remove(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { pub async fn remove(
ctx.send(CreateReply::default().content("Yet to be implemented.")).await?; 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(()) Ok(())
} }
/// Update a game server in the database /// Update a game server in the database
#[poise::command(slash_command)] #[poise::command(slash_command)]
pub async fn update(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { pub async fn update(
ctx.send(CreateReply::default().content("Yet to be implemented.")).await?; 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(()) Ok(())
} }
@ -158,3 +253,22 @@ pub async fn list(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(()) 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::{ use crate::{
models::mpservers::MPServers, Error,
EMBED_COLOR, EMBED_COLOR,
Error models::gameservers::Gameservers,
commands::gameserver::ac_server_name
}; };
use gamedig::protocols::{ use gamedig::protocols::{
valve::{ valve::{
Engine, GatheringSettings, Response Engine,
Response,
GatheringSettings
}, },
types::TimeoutSettings, valve,
valve minecraft,
minecraft::RequestSettings,
types::TimeoutSettings
}; };
use std::{ use std::{
str::FromStr, str::FromStr,
@ -22,18 +27,21 @@ use reqwest::{
Client, Client,
header::USER_AGENT header::USER_AGENT
}; };
use tokio::{
net::lookup_host,
join
};
use poise::CreateReply; use poise::CreateReply;
use serenity::builder::CreateEmbed; use serenity::builder::CreateEmbed;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use cargo_toml::Manifest; use cargo_toml::Manifest;
use serde_json::Value; use serde_json::Value;
use tokio::join;
static PMS_BASE: Lazy<String> = Lazy::new(|| static PMS_BASE: Lazy<String> = Lazy::new(||
var("WG_PMS").expect("Expected a \"WG_PMS\" in the envvar but none was found") 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 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 addr = SocketAddr::from_str(&server_ip).unwrap();
let engine = Engine::Source(None); let engine = Engine::Source(None);
@ -62,6 +70,35 @@ fn query_server() -> Result<Response, Error> {
Ok(response?) 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> { async fn pms_serverstatus(url: &str) -> Result<Vec<Value>, Error> {
let bot_version = Manifest::from_path("Cargo.toml").unwrap().package.unwrap().version.unwrap(); 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 /// 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> { pub async fn status(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(()) Ok(())
} }
@ -86,7 +123,7 @@ pub async fn status(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
#[poise::command(slash_command)] #[poise::command(slash_command)]
pub async fn ats(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { pub async fn ats(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
let embed = CreateEmbed::new().color(EMBED_COLOR); let embed = CreateEmbed::new().color(EMBED_COLOR);
match query_server() { match query_ats_server() {
Ok(response) => { Ok(response) => {
ctx.send(CreateReply::default() ctx.send(CreateReply::default()
.embed(embed .embed(embed
@ -138,17 +175,38 @@ pub async fn wg(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// Retrieve the data from Farming Simulator 22 server /// Retrieve the server data from given Minecraft Java server
#[poise::command(slash_command, guild_only)] #[poise::command(slash_command, guild_only)]
pub async fn fs(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { pub async fn mc(
// let embed = CreateEmbed::new().color(EMBED_COLOR); ctx: poise::Context<'_, (), Error>,
let server = MPServers::get_server_ip(ctx.guild_id().unwrap().into(), "testserver").await?; #[description = "Server name"] #[autocomplete = "ac_server_name"] server_name: String
let ip = server.0; ) -> Result<(), Error> {
let md5 = server.1; 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(()) Ok(())
} }

View File

@ -8,7 +8,6 @@ pub struct Gameservers {
pub ip_address: String pub ip_address: String
} }
#[allow(dead_code)]
impl Gameservers { impl Gameservers {
pub async fn list_servers(guild_id: u64) -> Result<Vec<Self>, tokio_postgres::Error> { pub async fn list_servers(guild_id: u64) -> Result<Vec<Self>, tokio_postgres::Error> {
let client = DatabaseController::new().await?.client; let client = DatabaseController::new().await?.client;
@ -74,7 +73,8 @@ impl Gameservers {
Ok(()) 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; let client = DatabaseController::new().await?.client;
client.execute(" client.execute("
UPDATE gameservers UPDATE gameservers
@ -83,5 +83,37 @@ impl Gameservers {
", &[&new_name, &(guild_id as i64), &server_name]).await?; ", &[&new_name, &(guild_id as i64), &server_name]).await?;
Ok(()) 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; 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")))
}
}