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 { 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}; 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, 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; } } }