diff --git a/db/migrations/6_role-adder.sql b/db/migrations/6_role-adder.sql new file mode 100644 index 0000000..7206eca --- /dev/null +++ b/db/migrations/6_role-adder.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS roles_adder ( + server integer not null, + role_a integer not null, + role_b integer not null, + role_c integer not null, + PRIMARY KEY(server,role_a,role_b,role_c) +); +CREATE INDEX IF NOT EXISTS index_roles_adder_server ON roles_adder (server); +CREATE INDEX IF NOT EXISTS index_roles_adder_from ON roles_adder (role_a,role_b); +CREATE INDEX IF NOT EXISTS index_roles_adder_to ON roles_adder (role_c); +CREATE INDEX IF NOT EXISTS index_roles_adder_search ON roles_adder (server,role_a,role_b); \ No newline at end of file diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 3acfe81..a11ffd5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,3 +2,4 @@ pub mod add_server; pub mod committee; pub mod link_email; pub mod minecraft; +pub mod role_adder; diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs new file mode 100644 index 0000000..a740194 --- /dev/null +++ b/src/commands/role_adder.rs @@ -0,0 +1,247 @@ +use serenity::{ + builder::CreateApplicationCommand, + client::Context, + model::{ + application::interaction::application_command::ApplicationCommandInteraction, + prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, + }, +}; + +use skynet_discord_bot::{is_admin, DataBase, RoleAdder}; +use sqlx::{Error, Pool, Sqlite}; + +pub mod edit { + use super::*; + + 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 role_a = if let CommandDataOptionValue::Role(role) = command + .data + .options + .get(0) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") + { + role.id.to_owned() + } else { + return "Please provide a valid role for ``Role Current``".to_string(); + }; + + let role_b = if let CommandDataOptionValue::Role(role) = command + .data + .options + .get(1) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") + { + role.id.to_owned() + } else { + return "Please provide a valid role for ``Role Current``".to_string(); + }; + + let role_c = if let CommandDataOptionValue::Role(role) = command + .data + .options + .get(2) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") + { + role.id.to_owned() + } else { + return "Please provide a valid role for ``Role Current``".to_string(); + }; + + if role_a == role_b { + return "Roles A and B must be different".to_string(); + } + + if (role_c == role_a) || (role_c == role_b) { + return "Role C cannot be same as A or B".to_string(); + } + + let mut delete = false; + + if let Some(x) = command.data.options.get(3) { + let tmp = x.to_owned(); + if let Some(y) = tmp.resolved { + match y { + CommandDataOptionValue::Boolean(z) => { + delete = z; + } + _ => {} + } + } + } + + 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 = command.guild_id.unwrap_or_default(); + let server_data = RoleAdder { + server, + role_a, + role_b, + role_c, + }; + + match add_server(&db, &server_data, delete).await { + Ok(_) => {} + Err(e) => { + println!("{:?}", e); + return format!("Failure to insert into Servers {:?}", server_data); + } + } + + let mut role_a_name = String::new(); + let mut role_b_name = String::new(); + let mut role_c_name = String::new(); + + if let Ok(x) = server.roles(&ctx).await { + if let Some(y) = x.get(&role_a) { + role_a_name = y.to_owned().name; + } + if let Some(y) = x.get(&role_b) { + role_b_name = y.to_owned().name; + } + if let Some(y) = x.get(&role_b) { + role_c_name = y.to_owned().name; + } + } + + if delete { + format!("Removed {} + {} = {}", role_a_name, role_b_name, role_c_name) + } else { + format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name) + } + } + + pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("roles_adder") + .description("Combine roles together to an new one") + .create_option(|option| { + option + .name("role_a") + .description("A role you want to add to Role B") + .kind(CommandOptionType::Role) + .required(true) + }) + .create_option(|option| { + option + .name("role_b") + .description("A role you want to add to Role A") + .kind(CommandOptionType::Role) + .required(true) + }) + .create_option(|option| option.name("role_c").description("Sum of A and B").kind(CommandOptionType::Role).required(true)) + .create_option(|option| { + option + .name("delete") + .description("Delete this entry.") + .kind(CommandOptionType::Boolean) + .required(false) + }) + } + + async fn add_server(db: &Pool, server: &RoleAdder, delete: bool) -> Result, Error> { + let action; + if delete { + action = sqlx::query_as::<_, RoleAdder>( + " + DELETE FROM roles_adder + WHERE server = ?1 AND role_a = ?2 AND role_b = ?3 AND role_c = ?4 + ", + ) + .bind(*server.server.as_u64() as i64) + .bind(*server.role_a.as_u64() as i64) + .bind(*server.role_b.as_u64() as i64) + .bind(*server.role_c.as_u64() as i64) + .fetch_optional(db) + .await; + } else { + action = sqlx::query_as::<_, RoleAdder>( + " + INSERT OR REPLACE INTO roles_adder (server, role_a, role_b, role_c) + VALUES (?1, ?2, ?3, ?4) + ", + ) + .bind(*server.server.as_u64() as i64) + .bind(*server.role_a.as_u64() as i64) + .bind(*server.role_b.as_u64() as i64) + .bind(*server.role_c.as_u64() as i64) + .fetch_optional(db) + .await; + } + + action + } +} + +// TODO +pub mod list {} + +pub mod tools { + use serenity::client::Context; + use serenity::model::guild::Member; + use skynet_discord_bot::RoleAdder; + use sqlx::{Pool, Sqlite}; + + pub async fn on_role_change(db: &Pool, ctx: &Context, mut new_data: Member) { + // check if the role changed is part of the oens for this server + if let Some(role_adders) = sqlx::query_as::<_, RoleAdder>( + r#" + SELECT * + FROM roles_adder + WHERE server = ? + "#, + ) + .bind(*new_data.guild_id.as_u64() as i64) + .fetch_all(db) + .await + .ok() + { + let mut roles_add = vec![]; + let mut roles_remove = vec![]; + + for role_adder in role_adders { + // if the user has both A dnd B give them C + if new_data.roles.contains(&role_adder.role_a) && new_data.roles.contains(&role_adder.role_b) && !new_data.roles.contains(&role_adder.role_c) + { + roles_add.push(role_adder.role_c); + } + + // If the suer has C but not A or B remove C + if new_data.roles.contains(&role_adder.role_c) + && (!new_data.roles.contains(&role_adder.role_a) || !new_data.roles.contains(&role_adder.role_b)) + { + roles_remove.push(role_adder.role_c); + } + } + + if !roles_add.is_empty() { + if let Err(e) = new_data.add_roles(&ctx, &roles_add).await { + println!("{:?}", e); + } + } + + if !roles_remove.is_empty() { + if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await { + println!("{:?}", e); + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 3eda4aa..5923151 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -293,6 +293,37 @@ impl<'r> FromRow<'r, SqliteRow> for 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); diff --git a/src/main.rs b/src/main.rs index f1c70f3..a7735e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,28 @@ pub mod commands; +use crate::commands::role_adder::tools::on_role_change; +use serenity::model::guild::Member; use serenity::{ async_trait, client::{Context, EventHandler}, model::{ application::{command::Command, interaction::Interaction}, gateway::{GatewayIntents, Ready}, - guild, prelude::Activity, user::OnlineStatus, }, Client, }; -use std::sync::Arc; - use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase}; +use std::sync::Arc; use tokio::sync::RwLock; struct Handler; #[async_trait] impl EventHandler for Handler { - async fn guild_member_addition(&self, ctx: Context, mut new_member: guild::Member) { + // handles previously linked accounts joining the server + async fn guild_member_addition(&self, ctx: Context, mut new_member: Member) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() @@ -75,6 +76,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use match Command::set_global_application_commands(&ctx.http, |commands| { commands .create_application_command(|command| commands::add_server::register(command)) + .create_application_command(|command| commands::role_adder::edit::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::minecraft::server::add::register(command)) @@ -106,6 +108,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // admin commands "add" => commands::add_server::run(&command, &ctx).await, + "roles_adder" => commands::role_adder::edit::run(&command, &ctx).await, "minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await, "minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await, "minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await, @@ -120,6 +123,20 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } } + + // handles role updates + async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Member) { + // get config/db + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + // check if the role changed is part of the oens for this server + on_role_change(&db, &ctx, new_data).await; + } } #[tokio::main]