diff --git a/flake.nix b/flake.nix index e6d6f02..cbec3d1 100644 --- a/flake.nix +++ b/flake.nix @@ -107,7 +107,7 @@ }; discord = mkOption rec { type = types.str; - description = "ENV file with DISCORD_TOKEN"; + description = "ENV file with DISCORD_TOKEN, DISCORD_MINECRAFT"; }; mail = mkOption rec { type = types.str; diff --git a/src/commands/add_minecraft.rs b/src/commands/add_minecraft.rs new file mode 100644 index 0000000..a987a0a --- /dev/null +++ b/src/commands/add_minecraft.rs @@ -0,0 +1,180 @@ +use serenity::{ + builder::CreateApplicationCommand, + client::Context, + model::{ + application::interaction::application_command::ApplicationCommandInteraction, + prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, + }, +}; + +use skynet_discord_bot::{get_server_config, DataBase, Servers}; +use sqlx::{Pool, Sqlite}; + +pub(crate) mod user {} + +pub(crate) mod server { + use serde::{Deserialize, Serialize}; + use serenity::model::id::GuildId; + use sqlx::Error; + // this is to managfe the server side of commands related to minecraft + use super::*; + use skynet_discord_bot::set_roles::get_server_member_bulk; + use skynet_discord_bot::{is_admin, Config}; + + pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("add_minecraft") + .description("Enable the bot for this discord") + .create_option(|option| { + option + .name("server_id") + .description("ID of the Minecraft server hosted by the Computer Society") + .kind(CommandOptionType::String) + .required(true) + }) + } + + pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + // check if user has high enough permisssions + if let Some(msg) = is_admin(command, ctx).await { + return msg; + } + let g_id = match command.guild_id { + None => return "Not in a server".to_string(), + Some(x) => x, + }; + + let server_minecraft = if let CommandDataOptionValue::String(id) = command + .data + .options + .get(0) + .expect("Expected server_id option") + .resolved + .as_ref() + .expect("Expected server_id object") + { + Some(id.to_owned()) + } else { + None + }; + + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let server_data = match get_server_config(&db, &g_id).await { + None => { + return "No existing server config, have you used ``/add``?".to_string(); + } + Some(mut x) => { + x.server_minecraft = server_minecraft.clone(); + x + } + }; + + match add_server(&db, &server_data).await { + Ok(_) => {} + Err(e) => { + println!("{:?}", e); + return format!("Failure to insert into Servers {:?}", server_data); + } + } + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + update_server(server_minecraft, &db, &g_id, &config).await; + + "Added/Updated minecraft_server info".to_string() + } + + async fn add_server(db: &Pool, server: &Servers) -> Result, Error> { + sqlx::query_as::<_, Servers>( + " + UPDATE servers + SET server_minecraft = ?2 + WHERE server = ?1; + ", + ) + .bind(*server.server.as_u64() as i64) + .bind(&server.server_minecraft) + .fetch_optional(db) + .await + } + + /** + loop through all members of server + get a list of folks with mc accounts that are members + and a list that arent members + */ + async fn update_server(server_minecraft: Option, db: &Pool, g_id: &GuildId, config: &Config) { + if let Some(server_id) = server_minecraft { + let mut usernames = vec![]; + for member in get_server_member_bulk(db, g_id).await { + if let Some(x) = member.minecraft { + usernames.push(x); + } + } + if !usernames.is_empty() { + update_whitelist(&usernames, &server_id, &config.discord_minecraft).await; + } + } + } + + pub async fn update_whitelist(add: &Vec, server: &str, token: &str) { + let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + + async fn post(url: &str, bearer: &str, data: &T) { + match surf::post(url) + .header("Authorization", bearer) + .header("Content-Type", "application/json") + .header("Accept", "Application/vnd.pterodactyl.v1+json") + .body_json(&data) + { + Ok(req) => { + req.await.ok(); + } + Err(e) => { + dbg!(e); + } + } + } + + #[derive(Deserialize, Serialize, Debug)] + struct BodyCommand { + command: String, + } + + #[derive(Deserialize, Serialize, Debug)] + struct BodyDelete { + root: String, + files: Vec, + } + + // delete whitelist + let deletion = BodyDelete { + root: "/".to_string(), + files: vec!["whitelist.json".to_string()], + }; + post(&format!("{url_base}/files/delete"), &bearer, &deletion).await; + + // reload the whitelist + let data = BodyCommand { + command: "whitelist reload".to_string(), + }; + post(&format!("{url_base}/command"), &bearer, &data).await; + + for name in add { + let data = BodyCommand { + command: format!("whitelist add {name}"), + }; + post(&format!("{url_base}/command"), &bearer, &data).await; + } + } +} diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 4929d1b..49fcae3 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -7,12 +7,12 @@ use serenity::{ }, }; use skynet_discord_bot::get_data::get_wolves; -use skynet_discord_bot::{get_server_config, set_roles::update_server, DataBase, Servers, is_admin}; +use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers}; use sqlx::{Error, Pool, Sqlite}; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions - if let Some(msg) = is_admin(command, ctx).await{ + if let Some(msg) = is_admin(command, ctx).await { return msg; } @@ -64,6 +64,8 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri role_current, member_past: 0, member_current: 0, + // this gets added later + server_minecraft: None, }; match add_server(&db, ctx, &server_data).await { @@ -109,16 +111,22 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul let role_past = server.role_past.map(|x| *x.as_u64() as i64); let role_current = server.role_current.map(|x| *x.as_u64() as i64); + let server_minecraft = match get_server_config(db, &server.server).await { + None => None, + Some(x) => x.server_minecraft, + }; + let insert = sqlx::query_as::<_, Servers>( " - INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current) - VALUES (?1, ?2, ?3, ?4) + INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, server_minecraft) + VALUES (?1, ?2, ?3, ?4, ?5) ", ) .bind(*server.server.as_u64() as i64) .bind(&server.wolves_api) .bind(role_past) .bind(role_current) + .bind(server_minecraft) .fetch_optional(db) .await; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 4be8b3d..95e0c51 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,2 +1,3 @@ +pub mod add_minecraft; pub mod add_server; pub mod link_email; diff --git a/src/lib.rs b/src/lib.rs index 45f22e7..78a6e4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,12 @@ use chrono::{Datelike, SecondsFormat, Utc}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serenity::client::Context; use serenity::model::id::UserId; +use serenity::model::prelude::application_command::ApplicationCommandInteraction; use sqlx::{ sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}, Error, FromRow, Pool, Row, Sqlite, }; use std::{env, str::FromStr, sync::Arc}; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; use tokio::sync::RwLock; pub struct Config { @@ -28,6 +28,7 @@ pub struct Config { pub auth: String, pub discord_token: String, + pub discord_minecraft: String, pub mail_smtp: String, pub mail_user: String, @@ -53,6 +54,7 @@ pub fn get_config() -> Config { ldap_api: "https://api.account.skynet.ie".to_string(), auth: "".to_string(), discord_token: "".to_string(), + discord_minecraft: "".to_string(), home: ".".to_string(), database: "database.db".to_string(), @@ -83,6 +85,9 @@ pub fn get_config() -> Config { if let Ok(x) = env::var("DISCORD_TOKEN") { config.discord_token = x.trim().to_string(); } + if let Ok(x) = env::var("DISCORD_MINECRAFT") { + config.discord_minecraft = x.trim().to_string(); + } if let Ok(x) = env::var("EMAIL_SMTP") { config.mail_smtp = x.trim().to_string(); @@ -219,6 +224,7 @@ pub struct Servers { pub role_current: Option, pub member_past: i64, pub member_current: i64, + pub server_minecraft: Option, } impl<'r> FromRow<'r, SqliteRow> for Servers { fn from_row(row: &'r SqliteRow) -> Result { @@ -247,6 +253,11 @@ impl<'r> FromRow<'r, SqliteRow> for Servers { _ => None, }; + let server_minecraft = match row.try_get("server_minecraft") { + Ok(x) => Some(x), + _ => None, + }; + Ok(Self { server, wolves_api: row.try_get("wolves_api")?, @@ -254,6 +265,7 @@ impl<'r> FromRow<'r, SqliteRow> for Servers { role_current, member_past: row.try_get("member_past")?, member_current: row.try_get("member_current")?, + server_minecraft, }) } } @@ -271,9 +283,7 @@ pub async fn db_init(config: &Config) -> Result, Error> { .await?; // migrations are amazing! - sqlx::migrate!("./db/migrations") - .run(&pool) - .await?; + sqlx::migrate!("./db/migrations").run(&pool).await.unwrap(); Ok(pool) } @@ -419,7 +429,7 @@ pub mod set_roles { println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); } - async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { + pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { sqlx::query_as::<_, ServerMembersWolves>( r#" SELECT * @@ -633,9 +643,9 @@ pub mod get_data { } /** - For any time ye need to check if a user who calls a command has admin privlages - */ -pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> Option{ + For any time ye need to check if a user who calls a command has admin privlages +*/ +pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> Option { let mut admin = false; let g_id = match command.guild_id { @@ -664,8 +674,8 @@ pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> } } if !admin { - return Some("Administrator permission required".to_string()) + Some("Administrator permission required".to_string()) } else { - return None + None } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 0aac5b9..b726541 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,6 +60,7 @@ impl EventHandler for Handler { .create_application_command(|command| commands::add_server::register(command)) .create_application_command(|command| commands::link_email::link::register(command)) .create_application_command(|command| commands::link_email::verify::register(command)) + .create_application_command(|command| commands::add_minecraft::server::register(command)) }) .await { @@ -79,6 +80,7 @@ impl EventHandler for Handler { "add" => commands::add_server::run(&command, &ctx).await, "link_wolves" => commands::link_email::link::run(&command, &ctx).await, "verify" => commands::link_email::verify::run(&command, &ctx).await, + "add_minecraft" => commands::add_minecraft::server::run(&command, &ctx).await, _ => "not implemented :(".to_string(), };