diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 10ee284..45c4643 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -18,8 +18,8 @@ pub(crate) mod user { use serde::{Deserialize, Serialize}; use serenity::model::id::UserId; use skynet_discord_bot::common::database::Wolves; - use skynet_discord_bot::common::minecraft::Minecraft; - use skynet_discord_bot::{whitelist_update, Config}; + use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; + use skynet_discord_bot::Config; use sqlx::Error; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 000aa46..25bde46 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -1,5 +1,5 @@ use crate::common::set_roles::normal::get_server_member_bulk; -use crate::{whitelist_update, Config}; +use crate::Config; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serenity::model::id::GuildId; @@ -44,7 +44,7 @@ pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, c } } -async fn post(url: &str, bearer: &str, data: &T) { +pub async fn post(url: &str, bearer: &str, data: &T) { match surf::post(url) .header("Authorization", bearer) .header("Content-Type", "application/json") @@ -153,3 +153,22 @@ pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Ve .await .unwrap_or_default() } + +pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { + println!("Update whitelist for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + + for (name, java) in add { + let data = if *java { + BodyCommand { + command: format!("whitelist add {name}"), + } + } else { + BodyCommand { + command: format!("fwhitelist add {name}"), + } + }; + post(&format!("{url_base}/command"), &bearer, &data).await; + } +} diff --git a/src/lib.rs b/src/lib.rs index 1e11ada..5611a21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,12 @@ pub mod common; -use crate::common::set_roles::normal::get_server_member_bulk; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; use serenity::client::Context; -use serenity::model::guild; -use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; +use serenity::model::id::{ChannelId, GuildId, RoleId}; use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::prelude::TypeMapKey; -use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; -use sqlx::{Error, FromRow, Pool, Row, Sqlite}; -use std::str::FromStr; use std::{env, sync::Arc}; use tokio::sync::RwLock; @@ -122,293 +115,6 @@ pub fn get_config() -> Config { config } -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ServerMembers { - pub server: GuildId, - pub id_wolves: i64, - pub expiry: String, -} -impl<'r> FromRow<'r, SqliteRow> for ServerMembers { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - - Ok(Self { - server, - id_wolves: row.try_get("id_wolves")?, - expiry: row.try_get("expiry")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ServerMembersWolves { - pub server: GuildId, - pub id_wolves: i64, - pub expiry: String, - pub email: String, - pub discord: Option, - pub minecraft: Option, - pub minecraft_uid: Option, -} -impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; - - Ok(Self { - server, - id_wolves: row.try_get("id_wolves")?, - expiry: row.try_get("expiry")?, - email: row.try_get("email")?, - discord, - minecraft: row.try_get("minecraft")?, - minecraft_uid: row.try_get("minecraft_uid")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Wolves { - pub id_wolves: i64, - pub email: String, - pub discord: Option, - pub minecraft: Option, -} -impl<'r> FromRow<'r, SqliteRow> for Wolves { - fn from_row(row: &'r SqliteRow) -> Result { - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; - - Ok(Self { - id_wolves: row.try_get("id_wolves")?, - email: row.try_get("email")?, - discord, - minecraft: row.try_get("minecraft")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct WolvesVerify { - pub email: String, - pub discord: UserId, - pub auth_code: String, - pub date_expiry: String, -} -impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { - fn from_row(row: &'r SqliteRow) -> Result { - let user_tmp: i64 = row.try_get("discord")?; - let discord = UserId::from(user_tmp as u64); - - Ok(Self { - email: row.try_get("email")?, - discord, - auth_code: row.try_get("auth_code")?, - date_expiry: row.try_get("date_expiry")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Committee { - pub email: String, - pub discord: UserId, - pub auth_code: String, - pub committee: i64, -} -impl<'r> FromRow<'r, SqliteRow> for Committee { - fn from_row(row: &'r SqliteRow) -> Result { - let user_tmp: i64 = row.try_get("discord")?; - let discord = UserId::from(user_tmp as u64); - - Ok(Self { - email: row.try_get("email")?, - discord, - auth_code: row.try_get("auth_code")?, - committee: row.try_get("committee")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Servers { - pub server: GuildId, - pub wolves_api: String, - pub role_past: Option, - pub role_current: RoleId, - pub member_past: i64, - pub member_current: i64, - pub bot_channel_id: ChannelId, - // these can be removed in teh future with an API update - pub server_name: String, -} -impl<'r> FromRow<'r, SqliteRow> for Servers { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - let role_past = match row.try_get("role_past") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(RoleId::from(tmp as u64)) - } - } - _ => None, - }; - let role_current = match row.try_get("role_current") { - Ok(x) => { - let tmp: i64 = x; - RoleId::from(tmp as u64) - } - _ => RoleId::from(0u64), - }; - - let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?; - let bot_channel_id = ChannelId::from(bot_channel_tmp as u64); - - Ok(Self { - server, - wolves_api: row.try_get("wolves_api")?, - role_past, - role_current, - member_past: row.try_get("member_past")?, - member_current: row.try_get("member_current")?, - bot_channel_id, - server_name: row.try_get("server_name")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Minecraft { - pub discord: GuildId, - pub minecraft: String, -} -impl<'r> FromRow<'r, SqliteRow> for Minecraft { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server_discord")?; - let discord = GuildId::from(server_tmp as u64); - - Ok(Self { - discord, - minecraft: row.try_get("server_minecraft")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct RoleAdder { - pub server: GuildId, - pub role_a: RoleId, - pub role_b: RoleId, - pub role_c: RoleId, -} -impl<'r> FromRow<'r, SqliteRow> for RoleAdder { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - - Ok(Self { - server, - role_a: get_role_from_row(row, "role_a"), - role_b: get_role_from_row(row, "role_b"), - role_c: get_role_from_row(row, "role_c"), - }) - } -} - -fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { - match row.try_get(col) { - Ok(x) => { - let tmp: i64 = x; - RoleId(tmp as u64) - } - _ => RoleId::from(0u64), - } -} - -pub async fn db_init(config: &Config) -> Result, Error> { - let database = format!("{}/{}", &config.home, &config.database); - - let pool = SqlitePoolOptions::new() - .max_connections(5) - .connect_with( - SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? - .foreign_keys(true) - .create_if_missing(true), - ) - .await?; - - // migrations are amazing! - sqlx::migrate!("./db/migrations").run(&pool).await?; - - Ok(pool) -} - -pub async fn get_server_config(db: &Pool, server: &GuildId) -> Option { - sqlx::query_as::<_, Servers>( - r#" - SELECT * - FROM servers - WHERE server = ? - "#, - ) - .bind(*server.as_u64() as i64) - .fetch_one(db) - .await - .ok() -} - -pub async fn get_server_member(db: &Pool, server: &GuildId, member: &guild::Member) -> Result { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE server = ? AND discord = ? - "#, - ) - .bind(*server.as_u64() as i64) - .bind(*member.user.id.as_u64() as i64) - .fetch_one(db) - .await -} - -pub async fn get_server_config_bulk(db: &Pool) -> Vec { - sqlx::query_as::<_, Servers>( - r#" - SELECT * - FROM servers - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() -} - pub fn get_now_iso(short: bool) -> String { let now = Utc::now(); if short { @@ -422,257 +128,6 @@ pub fn random_string(len: usize) -> String { thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } -pub mod set_roles { - use super::*; - use crate::common::database::DataBase; - pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - - let db = db_lock.read().await; - - let Servers { - server, - role_past, - role_current, - .. - } = server; - - let mut roles_set = [0, 0, 0]; - let mut members = vec![]; - - for member in get_server_member_bulk(&db, server).await { - if let Some(x) = member.discord { - members.push(x); - } - } - let mut members_all = members.len(); - - if let Ok(x) = server.members(ctx, None, None).await { - for mut member in x { - // members_changed acts as an override to only deal with teh users in it - if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { - continue; - } - - if members.contains(&member.user.id) { - let mut roles = vec![]; - - if let Some(role) = &role_past { - if !member.roles.contains(role) { - roles_set[0] += 1; - roles.push(role.to_owned()); - } - } - - if !member.roles.contains(role_current) { - roles_set[1] += 1; - roles.push(role_current.to_owned()); - } - - if let Err(e) = member.add_roles(ctx, &roles).await { - println!("{:?}", e); - } - } else { - // old and never - - if let Some(role) = &role_past { - if member.roles.contains(role) { - members_all += 1; - } - } - - if member.roles.contains(role_current) { - roles_set[2] += 1; - // if theya re not a current member and have the role then remove it - if let Err(e) = member.remove_role(ctx, role_current).await { - println!("{:?}", e); - } - } - } - for role in remove_roles.iter().flatten() { - if let Err(e) = member.remove_role(ctx, role).await { - println!("{:?}", e); - } - } - } - } - - set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; - - // small bit of logging to note changes over time - println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); - } - - pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE ( - server = ? - AND discord IS NOT NULL - AND expiry > ? - ) - "#, - ) - .bind(*server.as_u64() as i64) - .bind(get_now_iso(true)) - .fetch_all(db) - .await - .unwrap_or_default() - } - - async fn set_server_numbers(db: &Pool, server: &GuildId, past: i64, current: i64) { - match sqlx::query_as::<_, Wolves>( - " - UPDATE servers - SET member_past = ?, member_current = ? - WHERE server = ? - ", - ) - .bind(past) - .bind(current) - .bind(*server.as_u64() as i64) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into {}", server.as_u64()); - println!("{:?}", e); - } - } - } -} - -pub mod get_data { - use super::*; - use crate::common::database::DataBase; - use crate::set_roles::update_server; - use std::collections::BTreeMap; - use wolves_oxidised::WolvesUser; - - pub async fn get_wolves(ctx: &Context) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database 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; - - // set up teh client - let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); - - for server_config in get_server_config_bulk(&db).await { - let Servers { - server, - wolves_api, - .. - } = &server_config; - - let existing_tmp = get_server_member(&db, server).await; - let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); - - // list of users that need to be updated for this server - let mut user_to_update = vec![]; - for user in wolves.get_members(wolves_api).await { - let id = user.member_id.parse::().unwrap_or_default(); - match existing.get(&(id as i64)) { - None => { - // user does not exist already, add everything - add_users_wolves(&db, &user).await; - add_users_server_members(&db, server, &user).await; - } - Some(old) => { - // always update wolves table, in case data has changed - add_users_wolves(&db, &user).await; - if old.expiry != user.expiry { - add_users_server_members(&db, server, &user).await; - - if let Some(discord_id) = old.discord { - user_to_update.push(discord_id); - } - } - } - } - } - - if !user_to_update.is_empty() { - update_server(ctx, &server_config, &[], &user_to_update).await; - } - } - } - - pub async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE ( - server = ? - AND discord IS NOT NULL - ) - "#, - ) - .bind(*server.as_u64() as i64) - .fetch_all(db) - .await - .unwrap_or_default() - } - - async fn add_users_wolves(db: &Pool, user: &WolvesUser) { - // expiry - match sqlx::query_as::<_, Wolves>( - " - INSERT INTO wolves (id_wolves, email) - VALUES ($1, $2) - ON CONFLICT(id_wolves) DO UPDATE SET email = $2 - ", - ) - .bind(&user.member_id) - .bind(&user.contact_email) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into Wolves {:?}", user); - println!("{:?}", e); - } - } - } - async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesUser) { - match sqlx::query_as::<_, ServerMembers>( - " - INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) - VALUES (?1, ?2, ?3) - ", - ) - .bind(*server.as_u64() as i64) - .bind(&user.member_id) - .bind(&user.expiry) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); - println!("{:?}", e); - } - } - } -} - /** For any time ye need to check if a user who calls a command has admin privlages */ @@ -710,153 +165,3 @@ 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_id: &str, db: &Pool, g_id: &GuildId, config: &Config) { - let mut usernames = vec![]; - for member in get_server_member_bulk(db, g_id).await { - if let Some(x) = member.minecraft { - usernames.push((x, true)); - } - if let Some(x) = member.minecraft_uid { - usernames.push((x, false)); - } - } - if !usernames.is_empty() { - whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await; - } -} - -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)] -pub struct ServerDetailsResSub { - pub identifier: String, - pub name: String, - pub description: String, - pub is_suspended: bool, -} -#[derive(Deserialize, Serialize, Debug)] -pub struct ServerDetailsRes { - pub attributes: ServerDetailsResSub, -} - -async fn get(url: &str, bearer: &str) -> Option { - match surf::get(url) - .header("Authorization", bearer) - .header("Content-Type", "application/json") - .header("Accept", "Application/vnd.pterodactyl.v1+json") - .recv_json() - .await - { - Ok(res) => Some(res), - Err(e) => { - dbg!(e); - - None - } - } -} - -#[derive(Deserialize, Serialize, Debug)] -struct BodyCommand { - command: String, -} - -#[derive(Deserialize, Serialize, Debug)] -struct BodyDelete { - root: String, - files: Vec, -} - -pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { - println!("Update whitelist for {}", server); - let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - - for (name, java) in add { - let data = if *java { - BodyCommand { - command: format!("whitelist add {name}"), - } - } else { - BodyCommand { - command: format!("fwhitelist add {name}"), - } - }; - post(&format!("{url_base}/command"), &bearer, &data).await; - } -} - -pub async fn whitelist_wipe(server: &str, token: &str) { - println!("Wiping whitelist for {}", server); - let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - - // delete whitelist - let deletion = BodyDelete { - root: "/".to_string(), - files: vec!["whitelist.json".to_string()], - }; - post(&format!("{url_base}/files/delete"), &bearer, &deletion).await; - - // recreate teh file, passing in the type here so the compiler knows what type of vec it is - post::>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await; - - // reload the whitelist - let data = BodyCommand { - command: "whitelist reload".to_string(), - }; - post(&format!("{url_base}/command"), &bearer, &data).await; -} - -pub async fn server_information(server: &str, token: &str) -> Option { - println!("Get server information for {}", server); - let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - get::(&format!("{url_base}/"), &bearer).await -} - -pub async fn get_minecraft_config(db: &Pool) -> Vec { - sqlx::query_as::<_, Minecraft>( - r#" - SELECT * - FROM minecraft - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() -} - -pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Vec { - sqlx::query_as::<_, Minecraft>( - r#" - SELECT * - FROM minecraft - WHERE server_discord = ?1 - "#, - ) - .bind(*g_id.as_u64() as i64) - .fetch_all(db) - .await - .unwrap_or_default() -}