diff --git a/Cargo.lock b/Cargo.lock index 779120b..9a15960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,7 +822,7 @@ dependencies = [ [[package]] name = "kon" -version = "0.1.11" +version = "0.1.12" dependencies = [ "cargo_toml", "gamedig", diff --git a/Cargo.toml b/Cargo.toml index dc4a773..0cd4555 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kon" -version = "0.1.11" +version = "0.1.12" rust-version = "1.74" edition = "2021" diff --git a/src/commands/gameserver.rs b/src/commands/gameserver.rs new file mode 100644 index 0000000..23ab6f5 --- /dev/null +++ b/src/commands/gameserver.rs @@ -0,0 +1,160 @@ +use crate::{ + Error, + EMBED_COLOR, + models::gameservers::Gameservers +}; + +use serenity::{ + all::Mentionable, + builder::CreateActionRow, + builder::CreateEmbed +}; +use poise::{ + CreateReply, + serenity_prelude, + serenity_prelude::ButtonStyle +}; + +/// 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> { + Ok(()) +} + +/// Add a game server to the database +#[poise::command(slash_command)] +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 = "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("confirm") + .style(ButtonStyle::Success) + .label("Yes"), + serenity_prelude::CreateButton::new("cancel") + .style(ButtonStyle::Danger) + .label("No") + ]); + + let reply = CreateReply::default() + .embed(CreateEmbed::new() + .title("Does this look correct?") + .description(format!(" + **Server name:** `{}` + **Game name:** `{}` + **Channel:** {} + **IP Address:** `{}` + ", server_name, game_name, guild_channel.mention(), ip_address)) + .color(EMBED_COLOR) + ) + .components(vec![action_row]); + + 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 == "confirm" { + let result = Gameservers::add_server( + ctx.guild_id().unwrap().into(), + server_name.as_str(), + game_name.as_str(), + guild_channel.id.into(), + ip_address.as_str() + ).await; + + let mut msg = collector.message.clone(); + + match result { + Ok(_) => { + msg.edit( + ctx, + serenity_prelude::EditMessage::new() + .content("*Confirmed, added the server to database*") + .components(Vec::new()) + ).await?; + }, + Err(y) => { + msg.edit( + ctx, + serenity_prelude::EditMessage::new() + .content(format!("*Error adding server to database: {:?}*", y)) + .components(Vec::new()) + ).await?; + } + } + } else if collector.data.custom_id == "cancel" { + let mut msg = collector.message.clone(); + + msg.edit( + ctx, + serenity_prelude::EditMessage::new() + .content("*Command cancelled*") + .components(Vec::new()) + ).await?; + } + } + + Ok(()) +} + +/// 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?; + + 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?; + + Ok(()) +} + +/// List all the available game servers for this guild +#[poise::command(slash_command)] +pub async fn list(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { + let servers = Gameservers::list_servers(ctx.guild_id().unwrap().into()).await?; + + let mut embed_fields = Vec::new(); + for server in servers { + embed_fields.push( + (server.server_name, format!("Game: `{}`\nIP: `{}`", server.game_name, server.ip_address), true) + ); + } + + ctx.send(CreateReply::default() + .embed(CreateEmbed::new() + .title("List of registered gameservers") + .fields(embed_fields) + .color(EMBED_COLOR) + ) + ).await?; + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 2cd38c3..3c4d858 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ pub mod ping; pub mod status; pub mod uptime; +pub mod gameserver; diff --git a/src/controllers/database.rs b/src/controllers/database.rs index 28631ad..5063148 100644 --- a/src/controllers/database.rs +++ b/src/controllers/database.rs @@ -31,6 +31,17 @@ impl DatabaseController { ); ").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?; + Ok(DatabaseController { client }) } } diff --git a/src/main.rs b/src/main.rs index c02bde6..77dd54f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,8 @@ async fn main() { commands: vec![ commands::ping::ping(), commands::uptime::uptime(), - commands::status::status() + commands::status::status(), + commands::gameserver::gameserver() ], pre_command: |ctx| Box::pin(async move { let get_guild_name = match ctx.guild() { diff --git a/src/models/gameservers.rs b/src/models/gameservers.rs new file mode 100644 index 0000000..a0f60a3 --- /dev/null +++ b/src/models/gameservers.rs @@ -0,0 +1,87 @@ +use crate::controllers::database::DatabaseController; + +pub struct Gameservers { + pub server_name: String, + pub game_name: String, + pub guild_owner: i64, + pub guild_channel: i64, + pub ip_address: String +} + +#[allow(dead_code)] +impl Gameservers { + pub async fn list_servers(guild_id: u64) -> Result, tokio_postgres::Error> { + let client = DatabaseController::new().await?.client; + let rows = client.query(" + SELECT * FROM gameservers + WHERE guild_owner = $1 + ", &[&(guild_id as i64)]).await?; + + let mut servers = Vec::new(); + for row in rows { + servers.push(Self { + 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") + }); + } + + Ok(servers) + } + + pub async fn add_server( + 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) + VALUES ($1, $2, $3, $4, $5) + ", &[&server_name, &game_name, &(guild_id as i64), &(guild_channel as i64), &ip_address]).await?; + + Ok(()) + } + + pub async fn remove_server(guild_id: u64, server_name: &str) -> Result<(), tokio_postgres::Error> { + let client = DatabaseController::new().await?.client; + client.execute(" + DELETE FROM gameservers + WHERE guild_owner = $1 AND server_name = $2 + ", &[&(guild_id as i64), &server_name]).await?; + + Ok(()) + } + + pub async fn update_server( + 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?; + + Ok(()) + } + + 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(()) + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 5cf24a3..f46172e 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1 +1,2 @@ pub mod mpservers; +pub mod gameservers;