From a9f55da04dc8961b2afc35dd543f4f280c34c3c8 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 3 Mar 2024 00:18:46 +0000 Subject: [PATCH 1/7] feat: got migrations working!!!! --- Cargo.toml | 2 +- db/migrations/1_setup.sql | 38 ++++++++++++++++++++ db/migrations/2_minecraft-server.sql | 3 ++ src/lib.rs | 54 ++-------------------------- 4 files changed, 45 insertions(+), 52 deletions(-) create mode 100644 db/migrations/1_setup.sql create mode 100644 db/migrations/2_minecraft-server.sql diff --git a/Cargo.toml b/Cargo.toml index 58b3b12..cb6d3cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ surf = "2.3.2" dotenvy = "0.15.7" # For sqlite -sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite" ] } +sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] } # create random strings rand = "0.8.5" diff --git a/db/migrations/1_setup.sql b/db/migrations/1_setup.sql new file mode 100644 index 0000000..aa4fb93 --- /dev/null +++ b/db/migrations/1_setup.sql @@ -0,0 +1,38 @@ +-- setup initial tables + +-- this handles the users "floating" account +CREATE TABLE IF NOT EXISTS wolves ( + id_wolves integer PRIMARY KEY, + email text not null, + discord integer, + minecraft text +); +CREATE INDEX IF NOT EXISTS index_discord ON wolves (discord); + +-- used to verify the users email address +CREATE TABLE IF NOT EXISTS wolves_verify ( + discord integer PRIMARY KEY, + email text not null, + auth_code text not null, + date_expiry text not null +); +CREATE INDEX IF NOT EXISTS index_date_expiry ON wolves_verify (date_expiry); + +-- information on teh server the bot is registered on +CREATE TABLE IF NOT EXISTS servers ( + server integer PRIMARY KEY, + wolves_api text not null, + role_past integer, + role_current integer, + member_past integer DEFAULT 0, + member_current integer DEFAULT 0 +); + +-- keep track of the members on the server +CREATE TABLE IF NOT EXISTS server_members ( + server integer not null, + id_wolves integer not null, + expiry text not null, + PRIMARY KEY(server,id_wolves), + FOREIGN KEY (id_wolves) REFERENCES wolves (id_wolves) +); \ No newline at end of file diff --git a/db/migrations/2_minecraft-server.sql b/db/migrations/2_minecraft-server.sql new file mode 100644 index 0000000..3aedbc3 --- /dev/null +++ b/db/migrations/2_minecraft-server.sql @@ -0,0 +1,3 @@ +-- add teh option to associate each discord server with a minecraft one managed by skynet +ALTER TABLE servers +ADD server_minecraft text; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c8ca3f9..ca57dbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -269,59 +269,11 @@ pub async fn db_init(config: &Config) -> Result, Error> { ) .await?; - sqlx::query( - "CREATE TABLE IF NOT EXISTS wolves ( - id_wolves integer PRIMARY KEY, - email text not null, - discord integer, - minecraft text - )", - ) - .execute(&pool) - .await?; - - sqlx::query("CREATE INDEX IF NOT EXISTS index_discord ON wolves (discord)").execute(&pool).await?; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS wolves_verify ( - discord integer PRIMARY KEY, - email text not null, - auth_code text not null, - date_expiry text not null - )", - ) - .execute(&pool) - .await?; - - sqlx::query("CREATE INDEX IF NOT EXISTS index_date_expiry ON wolves_verify (date_expiry)") - .execute(&pool) + // migrations are amazing! + sqlx::migrate!("./db/migrations") + .run(&pool) .await?; - sqlx::query( - "CREATE TABLE IF NOT EXISTS server_members ( - server integer not null, - id_wolves integer not null, - expiry text not null, - PRIMARY KEY(server,id_wolves), - FOREIGN KEY (id_wolves) REFERENCES wolves (id_wolves) - )", - ) - .execute(&pool) - .await?; - - sqlx::query( - "CREATE TABLE IF NOT EXISTS servers ( - server integer PRIMARY KEY, - wolves_api text not null, - role_past integer, - role_current integer, - member_past integer DEFAULT 0, - member_current integer DEFAULT 0 - )", - ) - .execute(&pool) - .await?; - Ok(pool) } From 2761098c8d81f59b4bd7da2f6003eb7def68c8b3 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 3 Mar 2024 00:53:31 +0000 Subject: [PATCH 2/7] feat: made a function to check if a user has admin perms --- src/commands/add_server.rs | 33 +++----------------------------- src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index c576b16..4929d1b 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -7,40 +7,13 @@ use serenity::{ }, }; use skynet_discord_bot::get_data::get_wolves; -use skynet_discord_bot::{get_server_config, set_roles::update_server, DataBase, Servers}; +use skynet_discord_bot::{get_server_config, set_roles::update_server, DataBase, Servers, is_admin}; use sqlx::{Error, Pool, Sqlite}; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions - let mut admin = false; - - let g_id = match command.guild_id { - None => return "Not in a server".to_string(), - Some(x) => x, - }; - - let roles_server = g_id.roles(&ctx.http).await.unwrap_or_default(); - - if let Ok(member) = g_id.member(&ctx.http, command.user.id).await { - if let Some(permissions) = member.permissions { - if permissions.administrator() { - admin = true; - } - } - - for role_id in member.roles { - if admin { - break; - } - if let Some(role) = roles_server.get(&role_id) { - if role.permissions.administrator() { - admin = true; - } - } - } - } - if !admin { - return "Administrator permission required".to_string(); + if let Some(msg) = is_admin(command, ctx).await{ + return msg; } let api_key = if let CommandDataOptionValue::String(key) = command diff --git a/src/lib.rs b/src/lib.rs index ca57dbc..45f22e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ use sqlx::{ 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 { @@ -630,3 +631,41 @@ 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{ + let mut admin = false; + + let g_id = match command.guild_id { + None => return Some("Not in a server".to_string()), + Some(x) => x, + }; + + let roles_server = g_id.roles(&ctx.http).await.unwrap_or_default(); + + if let Ok(member) = g_id.member(&ctx.http, command.user.id).await { + if let Some(permissions) = member.permissions { + if permissions.administrator() { + admin = true; + } + } + + for role_id in member.roles { + if admin { + break; + } + if let Some(role) = roles_server.get(&role_id) { + if role.permissions.administrator() { + admin = true; + } + } + } + } + if !admin { + return Some("Administrator permission required".to_string()) + } else { + return None + } +} \ No newline at end of file From d5877e99e6dec855f8a42ef5c8804d4ee82f3af7 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 3 Mar 2024 12:49:55 +0000 Subject: [PATCH 3/7] feat: server side aspects of adding minecraft whitelist added --- flake.nix | 2 +- src/commands/add_minecraft.rs | 180 ++++++++++++++++++++++++++++++++++ src/commands/add_server.rs | 16 ++- src/commands/mod.rs | 1 + src/lib.rs | 32 +++--- src/main.rs | 2 + 6 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 src/commands/add_minecraft.rs 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(), }; From 4a1b1cc7f99ec8ac433a227710db7245749eb391 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 3 Mar 2024 13:04:10 +0000 Subject: [PATCH 4/7] fix: hadnt changed the command name --- src/commands/link_email.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index e109525..700e853 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -87,7 +87,7 @@ pub(crate) mod link { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command - .name("link") + .name("link_wolves") .description("Set Wolves Email") .create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true)) } From 0d0a50c84b0159f2f43964445b1853848bf71f41 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 3 Mar 2024 13:58:10 +0000 Subject: [PATCH 5/7] feat: added teh user facing command --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index b726541..21bce57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,6 +61,7 @@ impl EventHandler for Handler { .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)) + .create_application_command(|command| commands::add_minecraft::user::register(command)) }) .await { @@ -81,6 +82,7 @@ impl EventHandler for Handler { "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, + "link_minecraft" => commands::add_minecraft::user::run(&command, &ctx).await, _ => "not implemented :(".to_string(), }; From f417b9993a171a432a94f157bb465adc7bba22b6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 3 Mar 2024 13:59:23 +0000 Subject: [PATCH 6/7] feat: added teh user facing command --- src/commands/add_minecraft.rs | 134 ++++++++++++++++++++++++++++++---- src/commands/link_email.rs | 2 +- 2 files changed, 121 insertions(+), 15 deletions(-) diff --git a/src/commands/add_minecraft.rs b/src/commands/add_minecraft.rs index a987a0a..11d09a3 100644 --- a/src/commands/add_minecraft.rs +++ b/src/commands/add_minecraft.rs @@ -10,7 +10,111 @@ use serenity::{ use skynet_discord_bot::{get_server_config, DataBase, Servers}; use sqlx::{Pool, Sqlite}; -pub(crate) mod user {} +pub(crate) mod user { + use super::*; + use crate::commands::add_minecraft::server::update_whitelist; + use crate::commands::link_email::link::get_server_member_discord; + use serenity::model::id::UserId; + use skynet_discord_bot::{Config, Wolves}; + use sqlx::Error; + + pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command.name("link_minecraft").description("Link your minecraft account").create_option(|option| { + option + .name("minecraft_id") + .description("Your Minecraft username") + .kind(CommandOptionType::String) + .required(true) + }) + } + + pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + 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 config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + // user has to have previously linked with wolves + if get_server_member_discord(&db, &command.user.id).await.is_none() { + return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string(); + } + + let username = if let CommandDataOptionValue::String(username) = command + .data + .options + .get(0) + .expect("Expected username option") + .resolved + .as_ref() + .expect("Expected username object") + { + username.trim() + } else { + return "Please provide a valid username".to_string(); + }; + + // insert the username into the database + match add_minecraft(&db, &command.user.id, username).await { + Ok(_) => {} + Err(e) => { + dbg!("{:?}", e); + return format!("Failure to minecraft username {:?}", username); + } + } + + // get a list of servers that the user is a member of + if let Ok(servers) = get_servers(&db, &command.user.id).await { + for server in servers { + if let Some(server_minecraft) = server.server_minecraft { + // activate the user on all linked servers + update_whitelist(&vec![username.to_string()], &server_minecraft, &config.discord_minecraft, false).await; + } + } + } + + "Added/Updated minecraft_user info".to_string() + } + + async fn add_minecraft(db: &Pool, user: &UserId, minecraft: &str) -> Result, Error> { + sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET minecraft = ?2 + WHERE discord = ?1; + ", + ) + .bind(*user.as_u64() as i64) + .bind(minecraft) + .fetch_optional(db) + .await + } + + async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { + sqlx::query_as::<_, Servers>( + " + SELECT servers.* + FROM servers + JOIN ( + SELECT server + FROM server_members + JOIN wolves USING (id_wolves) + WHERE discord = ?1 + ) USING (server) + WHERE server_minecraft IS NOT NULL + ", + ) + .bind(*discord.as_u64() as i64) + .fetch_all(db) + .await + } +} pub(crate) mod server { use serde::{Deserialize, Serialize}; @@ -121,12 +225,12 @@ pub(crate) mod server { } } if !usernames.is_empty() { - update_whitelist(&usernames, &server_id, &config.discord_minecraft).await; + update_whitelist(&usernames, &server_id, &config.discord_minecraft, true).await; } } } - pub async fn update_whitelist(add: &Vec, server: &str, token: &str) { + pub async fn update_whitelist(add: &Vec, server: &str, token: &str, wipe_reset: bool) { let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); @@ -157,18 +261,20 @@ pub(crate) mod server { 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; + if wipe_reset { + // 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; + // 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 { diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 700e853..bb065ab 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -92,7 +92,7 @@ pub(crate) mod link { .create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true)) } - async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { + pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { sqlx::query_as::<_, Wolves>( r#" SELECT * From 2c28f3edcce52ae96eee5406161eb02b73bfcfbc Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 3 Mar 2024 14:40:37 +0000 Subject: [PATCH 7/7] feat: added command to automatically update the mc whitelist --- src/bin/update_users.rs | 11 ++++- src/commands/add_minecraft.rs | 81 +---------------------------------- src/commands/link_email.rs | 4 +- src/lib.rs | 74 ++++++++++++++++++++++++++++++++ src/main.rs | 2 +- 5 files changed, 88 insertions(+), 84 deletions(-) diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index a5ca7e0..974fc1e 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -4,7 +4,7 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles::update_server, Config, DataBase}; +use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles, update_server, Config, DataBase}; use std::{process, sync::Arc}; use tokio::sync::RwLock; @@ -58,7 +58,14 @@ async fn bulk_check(ctx: Arc) { let db = db_lock.read().await; + 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; + for server_config in get_server_config_bulk(&db).await { - update_server(&ctx, &server_config, &[], &vec![]).await; + set_roles::update_server(&ctx, &server_config, &[], &vec![]).await; + update_server(server_config.server_minecraft, &db, &server_config.server, &config).await; } } diff --git a/src/commands/add_minecraft.rs b/src/commands/add_minecraft.rs index 11d09a3..3457190 100644 --- a/src/commands/add_minecraft.rs +++ b/src/commands/add_minecraft.rs @@ -12,10 +12,9 @@ use sqlx::{Pool, Sqlite}; pub(crate) mod user { use super::*; - use crate::commands::add_minecraft::server::update_whitelist; use crate::commands::link_email::link::get_server_member_discord; use serenity::model::id::UserId; - use skynet_discord_bot::{Config, Wolves}; + use skynet_discord_bot::{update_whitelist, Config, Wolves}; use sqlx::Error; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { @@ -117,13 +116,10 @@ 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}; + use skynet_discord_bot::{is_admin, update_server, Config}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command @@ -210,77 +206,4 @@ pub(crate) mod server { .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, true).await; - } - } - } - - pub async fn update_whitelist(add: &Vec, server: &str, token: &str, wipe_reset: bool) { - 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, - } - - if wipe_reset { - // 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/link_email.rs b/src/commands/link_email.rs index bb065ab..f4441df 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -15,7 +15,7 @@ use serenity::{ }; use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, WolvesVerify}; use sqlx::{Pool, Sqlite}; -pub(crate) mod link { +pub mod link { use super::*; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { @@ -234,7 +234,7 @@ pub(crate) mod link { } } -pub(crate) mod verify { +pub mod verify { use super::*; use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db}; use serenity::model::user::User; diff --git a/src/lib.rs b/src/lib.rs index 78a6e4f..6f0e5ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ use serenity::{ prelude::TypeMapKey, }; +use crate::set_roles::get_server_member_bulk; use chrono::{Datelike, SecondsFormat, Utc}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serenity::client::Context; @@ -679,3 +680,76 @@ pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> None } } + +/** +loop through all members of server +get a list of folks with mc accounts that are members +and a list that arent members + */ +pub 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, true).await; + } + } +} + +pub async fn update_whitelist(add: &Vec, server: &str, token: &str, wipe_reset: bool) { + 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, + } + + if wipe_reset { + // 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/main.rs b/src/main.rs index 21bce57..208f352 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -mod commands; +pub mod commands; use serenity::{ async_trait,