diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs deleted file mode 100644 index 6ed5bc0..0000000 --- a/src/commands/link_email.rs +++ /dev/null @@ -1,516 +0,0 @@ -pub mod wolves { - use lettre::{ - message::{header, MultiPart, SinglePart}, - transport::smtp::{self, authentication::Credentials}, - Message, SmtpTransport, Transport, - }; - use maud::html; - use serenity::all::CommandOptionType; - use serenity::builder::CreateCommandOption; - use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; - use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; - use skynet_discord_bot::{get_now_iso, random_string, Config}; - use sqlx::{Pool, Sqlite}; - - pub mod link { - use super::*; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; - - pub async fn run(command: &CommandInteraction, 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; - - if get_server_member_discord(&db, &command.user.id).await.is_some() { - return "Already linked".to_string(); - } - - db_pending_clear_expired(&db).await; - - if get_verify_from_db(&db, &command.user.id).await.is_some() { - return "Linking already in process, please check email.".to_string(); - } - - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), - .. - }) = command.data.options.first() - { - options - } else { - return "Please provide sub options".to_string(); - }; - - let email = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(email) => email.trim(), - _ => return "Please provide a valid email".to_string(), - } - } else { - return "Please provide a valid email".to_string(); - }; - - // check if email exists - let details = match get_server_member_email(&db, email).await { - None => { - let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); - - let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); - - // see if the user actually exists - let id = match wolves.get_member(email).await { - None => { - return invalid_user; - } - Some(x) => x, - }; - - // save teh user id and email to teh db - match save_to_db_user(&db, id, email).await { - Ok(x) => x, - Err(x) => { - dbg!(x); - return "Error: unable to save user to teh database, contact Computer Society".to_string(); - } - }; - - // pull it back out (technically could do it in previous step but more explicit) - match get_server_member_email(&db, email).await { - None => { - return "Error: failed to read user from database.".to_string(); - } - Some(x) => x, - } - } - Some(x) => x, - }; - - if details.discord.is_some() { - return "Email already verified".to_string(); - } - - // generate a auth key - let auth = random_string(20); - match send_mail(&config, &details.email, &auth, &command.user.name) { - Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { - Ok(_) => {} - Err(e) => { - return format!("Unable to save to db {} {e:?}", &details.email); - } - }, - Err(e) => { - return format!("Unable to send mail to {} {e:?}", &details.email); - } - } - - format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) - } - - pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, Wolves>( - r#" - SELECT * - FROM wolves - WHERE discord = ? - "#, - ) - .bind(user.get() as i64) - .fetch_one(db) - .await - .ok() - } - - async fn get_server_member_email(db: &Pool, email: &str) -> Option { - sqlx::query_as::<_, Wolves>( - r#" - SELECT * - FROM wolves - WHERE email = ? - "#, - ) - .bind(email) - .fetch_one(db) - .await - .ok() - } - - fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { - let discord = "https://computer.discord.skynet.ie"; - let sender = format!("UL Computer Society <{}>", &config.mail_user); - - // Create the html we want to send. - let html = html! { - head { - title { "UL Wolves Discord Linker" } - style type="text/css" { - "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" - } - } - div { - h2 { "UL Wolves Discord Linker" } - - h3 { "Link your UL Wolves Account to Discord" } - // Substitute in the name of our recipient. - p { "Hi " (user) "," } - p { - "Please paste this line into Discord (and press enter) to verify your discord account:" - br; - pre { "/wolves verify code: " (auth)} - } - hr; - h3 { "Help & Support" } - p { - "If you have issues please refer to our Computer Society Discord Server:" - br; - a href=(discord) { (discord) } - br; - "UL Computer Society" - } - } - }; - - let body_text = format!( - r#" - UL Wolves Discord Linker - Link your UL Wolves Account to Discord - - Link your Account - - Hi {user}, - - Please paste this line into Discord (and press enter) to verify your Discord account: - /wolves verify code: {auth} - - ------------------------------------------------------------------------- - - Help & Support - - If you have issues please refer to our Computer Society Discord Server: - {discord} - UL Computer Society - "# - ); - - // Build the message. - let email = Message::builder() - .from(sender.parse().unwrap()) - .to(mail.parse().unwrap()) - .subject("Skynet: Link Discord to Wolves.") - .multipart( - // This is composed of two parts. - // also helps not trip spam settings (uneven number of url's - MultiPart::alternative() - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), - ) - .expect("failed to build email"); - - let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone()); - - // Open a remote connection to gmail using STARTTLS - let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build(); - - // Send the email - mailer.send(&email) - } - - pub async fn db_pending_clear_expired(pool: &Pool) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" - DELETE - FROM wolves_verify - WHERE date_expiry < ? - "#, - ) - .bind(get_now_iso(true)) - .fetch_one(pool) - .await - .ok() - } - - pub async fn get_verify_from_db(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" - SELECT * - FROM wolves_verify - WHERE discord = ? - "#, - ) - .bind(user.get() as i64) - .fetch_one(db) - .await - .ok() - } - - async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &UserId) -> Result, sqlx::Error> { - sqlx::query_as::<_, WolvesVerify>( - " - INSERT INTO wolves_verify (email, discord, auth_code, date_expiry) - VALUES (?1, ?2, ?3, ?4) - ", - ) - .bind(record.email.to_owned()) - .bind(user.get() as i64) - .bind(auth.to_owned()) - .bind(get_now_iso(false)) - .fetch_optional(db) - .await - } - - async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { - sqlx::query_as::<_, Wolves>( - " - INSERT INTO wolves (id_wolves, email) - VALUES ($1, $2) - ON CONFLICT(id_wolves) DO UPDATE SET email = $2 - ", - ) - .bind(id_wolves) - .bind(email) - .fetch_optional(db) - .await - } - } - - pub mod verify { - use super::*; - use crate::commands::link_email::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, GuildId, RoleId}; - use serenity::model::user::User; - use skynet_discord_bot::common::database::get_server_config; - use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; - use skynet_discord_bot::common::wolves::committees::Committees; - use sqlx::Error; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - 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; - - // check if user has used /link_wolves - let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { - x - } else { - return "Please use ''/wolves link'' first".to_string(); - }; - - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), - .. - }) = command.data.options.first() - { - options - } else { - return "Please provide sub options".to_string(); - }; - - let code = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(y) => y.trim(), - _ => return "Please provide a verification code".to_string(), - } - } else { - return "Please provide a verification code".to_string(); - }; - - db_pending_clear_expired(&db).await; - - if details.auth_code != code { - return "Invalid verification code".to_string(); - } - - match db_pending_clear_successful(&db, &command.user.id).await { - Ok(_) => { - return match set_discord(&db, &command.user.id, &details.email).await { - Ok(_) => { - // get teh right roles for the user - set_server_roles(&db, &command.user, ctx).await; - - // check if they are a committee member, and on that server - set_server_roles_committee(&db, &command.user, ctx).await; - - "Discord username linked to Wolves".to_string() - } - Err(e) => { - println!("{:?}", e); - "Failed to save, please try /link_wolves again".to_string() - } - }; - } - Err(e) => println!("{:?}", e), - } - - "Failed to verify".to_string() - } - - async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { - sqlx::query_as::<_, WolvesVerify>( - r#" - DELETE - FROM wolves_verify - WHERE discord = ? - "#, - ) - .bind(user.get() as i64) - .fetch_optional(pool) - .await - } - - async fn set_discord(db: &Pool, discord: &UserId, email: &str) -> Result, Error> { - sqlx::query_as::<_, Wolves>( - " - UPDATE wolves - SET discord = ? - WHERE email = ? - ", - ) - .bind(discord.get() as i64) - .bind(email) - .fetch_optional(db) - .await - } - - async fn set_server_roles(db: &Pool, discord: &User, ctx: &Context) { - if let Ok(servers) = get_servers(db, &discord.id).await { - for server in servers { - if let Ok(member) = server.server.member(&ctx.http, &discord.id).await { - if let Some(config) = get_server_config(db, &server.server).await { - let Servers { - role_past, - role_current, - .. - } = config; - - let mut roles = vec![]; - - if let Some(role) = &role_past { - if !member.roles.contains(role) { - roles.push(role.to_owned()); - } - } - - if !member.roles.contains(&role_current) { - roles.push(role_current.to_owned()); - } - - if let Err(e) = member.add_roles(&ctx, &roles).await { - println!("{:?}", e); - } - } - } - } - } - } - - async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { - sqlx::query_as::<_, Committees>( - r#" - SELECT * - FROM committees - WHERE committee LIKE ?1 - "#, - ) - .bind(format!("%{}%", wolves_id)) - .fetch_all(db) - .await - .unwrap_or_else(|e| { - dbg!(e); - vec![] - }) - } - - async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { - if let Some(x) = get_server_member_discord(db, &discord.id).await { - // if they are a member of one or more committees, and in teh committee server then give the teh general committee role - // they will get teh more specific vanity role later - if !get_committees_id(db, x.id_wolves).await.is_empty() { - let server = GuildId::new(1220150752656363520); - let committee_member = RoleId::new(1226602779968274573); - - if let Ok(member) = server.member(ctx, &discord.id).await { - member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); - } - } - } - } - - async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { - sqlx::query_as::<_, ServerMembersWolves>( - " - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE discord = ? - ", - ) - .bind(discord.get() as i64) - .fetch_all(db) - .await - } - } - - pub mod unlink { - use serenity::all::{CommandInteraction, Context, UserId}; - use skynet_discord_bot::common::database::{DataBase, Wolves}; - use sqlx::{Pool, Sqlite}; - - pub async fn run(command: &CommandInteraction, 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; - - // dosent matter if there is one or not, it will be removed regardless - delete_link(&db, &command.user.id).await; - - "Discord link removed".to_string() - } - - async fn delete_link(db: &Pool, user: &UserId) { - match sqlx::query_as::<_, Wolves>( - " - UPDATE wolves - SET discord = NULL - WHERE discord = ?1; - ", - ) - .bind(user.get() as i64) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - dbg!(e); - } - } - } - } - - pub fn register() -> CreateCommand { - CreateCommand::new("wolves") - .description("Commands related to UL Wolves") - // link - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)), - ) - // verify - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)), - ) - // unlink - .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord")) - } -} diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index d476e88..124179c 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -7,7 +7,7 @@ pub(crate) mod user { use super::*; pub(crate) mod add { use super::*; - use crate::commands::link_email::wolves::link::get_server_member_discord; + use crate::commands::wolves::link::get_server_member_discord; use serde::{Deserialize, Serialize}; use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; use serenity::model::id::UserId; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 76b43cb..9ab59ac 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,5 @@ pub mod add_server; pub mod count; -pub mod link_email; pub mod minecraft; pub mod role_adder; +pub mod wolves; diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs new file mode 100644 index 0000000..533e379 --- /dev/null +++ b/src/commands/wolves.rs @@ -0,0 +1,515 @@ + +use lettre::{ + message::{header, MultiPart, SinglePart}, + transport::smtp::{self, authentication::Credentials}, + Message, SmtpTransport, Transport, +}; +use maud::html; +use serenity::all::CommandOptionType; +use serenity::builder::CreateCommandOption; +use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; +use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; +use skynet_discord_bot::{get_now_iso, random_string, Config}; +use sqlx::{Pool, Sqlite}; + +pub mod link { + use super::*; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; + + pub async fn run(command: &CommandInteraction, 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; + + if get_server_member_discord(&db, &command.user.id).await.is_some() { + return "Already linked".to_string(); + } + + db_pending_clear_expired(&db).await; + + if get_verify_from_db(&db, &command.user.id).await.is_some() { + return "Linking already in process, please check email.".to_string(); + } + + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), + .. + }) = command.data.options.first() + { + options + } else { + return "Please provide sub options".to_string(); + }; + + let email = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(email) => email.trim(), + _ => return "Please provide a valid email".to_string(), + } + } else { + return "Please provide a valid email".to_string(); + }; + + // check if email exists + let details = match get_server_member_email(&db, email).await { + None => { + let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); + + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + + // see if the user actually exists + let id = match wolves.get_member(email).await { + None => { + return invalid_user; + } + Some(x) => x, + }; + + // save teh user id and email to teh db + match save_to_db_user(&db, id, email).await { + Ok(x) => x, + Err(x) => { + dbg!(x); + return "Error: unable to save user to teh database, contact Computer Society".to_string(); + } + }; + + // pull it back out (technically could do it in previous step but more explicit) + match get_server_member_email(&db, email).await { + None => { + return "Error: failed to read user from database.".to_string(); + } + Some(x) => x, + } + } + Some(x) => x, + }; + + if details.discord.is_some() { + return "Email already verified".to_string(); + } + + // generate a auth key + let auth = random_string(20); + match send_mail(&config, &details.email, &auth, &command.user.name) { + Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { + Ok(_) => {} + Err(e) => { + return format!("Unable to save to db {} {e:?}", &details.email); + } + }, + Err(e) => { + return format!("Unable to send mail to {} {e:?}", &details.email); + } + } + + format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) + } + + pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { + sqlx::query_as::<_, Wolves>( + r#" + SELECT * + FROM wolves + WHERE discord = ? + "#, + ) + .bind(user.get() as i64) + .fetch_one(db) + .await + .ok() + } + + async fn get_server_member_email(db: &Pool, email: &str) -> Option { + sqlx::query_as::<_, Wolves>( + r#" + SELECT * + FROM wolves + WHERE email = ? + "#, + ) + .bind(email) + .fetch_one(db) + .await + .ok() + } + + fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { + let discord = "https://computer.discord.skynet.ie"; + let sender = format!("UL Computer Society <{}>", &config.mail_user); + + // Create the html we want to send. + let html = html! { + head { + title { "UL Wolves Discord Linker" } + style type="text/css" { + "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" + } + } + div { + h2 { "UL Wolves Discord Linker" } + + h3 { "Link your UL Wolves Account to Discord" } + // Substitute in the name of our recipient. + p { "Hi " (user) "," } + p { + "Please paste this line into Discord (and press enter) to verify your discord account:" + br; + pre { "/wolves verify code: " (auth)} + } + hr; + h3 { "Help & Support" } + p { + "If you have issues please refer to our Computer Society Discord Server:" + br; + a href=(discord) { (discord) } + br; + "UL Computer Society" + } + } + }; + + let body_text = format!( + r#" + UL Wolves Discord Linker + Link your UL Wolves Account to Discord + + Link your Account + + Hi {user}, + + Please paste this line into Discord (and press enter) to verify your Discord account: + /wolves verify code: {auth} + + ------------------------------------------------------------------------- + + Help & Support + + If you have issues please refer to our Computer Society Discord Server: + {discord} + UL Computer Society + "# + ); + + // Build the message. + let email = Message::builder() + .from(sender.parse().unwrap()) + .to(mail.parse().unwrap()) + .subject("Skynet: Link Discord to Wolves.") + .multipart( + // This is composed of two parts. + // also helps not trip spam settings (uneven number of url's + MultiPart::alternative() + .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) + .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), + ) + .expect("failed to build email"); + + let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone()); + + // Open a remote connection to gmail using STARTTLS + let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build(); + + // Send the email + mailer.send(&email) + } + + pub async fn db_pending_clear_expired(pool: &Pool) -> Option { + sqlx::query_as::<_, WolvesVerify>( + r#" + DELETE + FROM wolves_verify + WHERE date_expiry < ? + "#, + ) + .bind(get_now_iso(true)) + .fetch_one(pool) + .await + .ok() + } + + pub async fn get_verify_from_db(db: &Pool, user: &UserId) -> Option { + sqlx::query_as::<_, WolvesVerify>( + r#" + SELECT * + FROM wolves_verify + WHERE discord = ? + "#, + ) + .bind(user.get() as i64) + .fetch_one(db) + .await + .ok() + } + + async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &UserId) -> Result, sqlx::Error> { + sqlx::query_as::<_, WolvesVerify>( + " + INSERT INTO wolves_verify (email, discord, auth_code, date_expiry) + VALUES (?1, ?2, ?3, ?4) + ", + ) + .bind(record.email.to_owned()) + .bind(user.get() as i64) + .bind(auth.to_owned()) + .bind(get_now_iso(false)) + .fetch_optional(db) + .await + } + + async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Wolves>( + " + INSERT INTO wolves (id_wolves, email) + VALUES ($1, $2) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2 + ", + ) + .bind(id_wolves) + .bind(email) + .fetch_optional(db) + .await + } +} + +pub mod verify { + use super::*; + use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, GuildId, RoleId}; + use serenity::model::user::User; + use skynet_discord_bot::common::database::get_server_config; + use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; + use skynet_discord_bot::common::wolves::committees::Committees; + use sqlx::Error; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + 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; + + // check if user has used /link_wolves + let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { + x + } else { + return "Please use ''/wolves link'' first".to_string(); + }; + + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), + .. + }) = command.data.options.first() + { + options + } else { + return "Please provide sub options".to_string(); + }; + + let code = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(y) => y.trim(), + _ => return "Please provide a verification code".to_string(), + } + } else { + return "Please provide a verification code".to_string(); + }; + + db_pending_clear_expired(&db).await; + + if details.auth_code != code { + return "Invalid verification code".to_string(); + } + + match db_pending_clear_successful(&db, &command.user.id).await { + Ok(_) => { + return match set_discord(&db, &command.user.id, &details.email).await { + Ok(_) => { + // get teh right roles for the user + set_server_roles(&db, &command.user, ctx).await; + + // check if they are a committee member, and on that server + set_server_roles_committee(&db, &command.user, ctx).await; + + "Discord username linked to Wolves".to_string() + } + Err(e) => { + println!("{:?}", e); + "Failed to save, please try /link_wolves again".to_string() + } + }; + } + Err(e) => println!("{:?}", e), + } + + "Failed to verify".to_string() + } + + async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { + sqlx::query_as::<_, WolvesVerify>( + r#" + DELETE + FROM wolves_verify + WHERE discord = ? + "#, + ) + .bind(user.get() as i64) + .fetch_optional(pool) + .await + } + + async fn set_discord(db: &Pool, discord: &UserId, email: &str) -> Result, Error> { + sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET discord = ? + WHERE email = ? + ", + ) + .bind(discord.get() as i64) + .bind(email) + .fetch_optional(db) + .await + } + + async fn set_server_roles(db: &Pool, discord: &User, ctx: &Context) { + if let Ok(servers) = get_servers(db, &discord.id).await { + for server in servers { + if let Ok(member) = server.server.member(&ctx.http, &discord.id).await { + if let Some(config) = get_server_config(db, &server.server).await { + let Servers { + role_past, + role_current, + .. + } = config; + + let mut roles = vec![]; + + if let Some(role) = &role_past { + if !member.roles.contains(role) { + roles.push(role.to_owned()); + } + } + + if !member.roles.contains(&role_current) { + roles.push(role_current.to_owned()); + } + + if let Err(e) = member.add_roles(&ctx, &roles).await { + println!("{:?}", e); + } + } + } + } + } + } + + async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { + sqlx::query_as::<_, Committees>( + r#" + SELECT * + FROM committees + WHERE committee LIKE ?1 + "#, + ) + .bind(format!("%{}%", wolves_id)) + .fetch_all(db) + .await + .unwrap_or_else(|e| { + dbg!(e); + vec![] + }) + } + + async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { + if let Some(x) = get_server_member_discord(db, &discord.id).await { + // if they are a member of one or more committees, and in teh committee server then give the teh general committee role + // they will get teh more specific vanity role later + if !get_committees_id(db, x.id_wolves).await.is_empty() { + let server = GuildId::new(1220150752656363520); + let committee_member = RoleId::new(1226602779968274573); + + if let Ok(member) = server.member(ctx, &discord.id).await { + member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); + } + } + } + } + + async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { + sqlx::query_as::<_, ServerMembersWolves>( + " + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE discord = ? + ", + ) + .bind(discord.get() as i64) + .fetch_all(db) + .await + } +} + +pub mod unlink { + use serenity::all::{CommandInteraction, Context, UserId}; + use skynet_discord_bot::common::database::{DataBase, Wolves}; + use sqlx::{Pool, Sqlite}; + + pub async fn run(command: &CommandInteraction, 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; + + // dosent matter if there is one or not, it will be removed regardless + delete_link(&db, &command.user.id).await; + + "Discord link removed".to_string() + } + + async fn delete_link(db: &Pool, user: &UserId) { + match sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET discord = NULL + WHERE discord = ?1; + ", + ) + .bind(user.get() as i64) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + dbg!(e); + } + } + } +} + +pub fn register() -> CreateCommand { + CreateCommand::new("wolves") + .description("Commands related to UL Wolves") + // link + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)), + ) + // verify + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)), + ) + // unlink + .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord")) +} diff --git a/src/main.rs b/src/main.rs index d35dfe5..c270b1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,7 +115,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use vec![ commands::add_server::register(), commands::role_adder::edit::register(), - commands::link_email::wolves::register(), + commands::wolves::register(), commands::minecraft::server::add::register(), commands::minecraft::server::list::register(), commands::minecraft::server::delete::register(), @@ -168,9 +168,9 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "wolves" => match command.data.options.first() { None => "Invalid Command".to_string(), Some(x) => match x.name.as_str() { - "link" => commands::link_email::wolves::link::run(&command, &ctx).await, - "verify" => commands::link_email::wolves::verify::run(&command, &ctx).await, - "unlink" => commands::link_email::wolves::unlink::run(&command, &ctx).await, + "link" => commands::wolves::link::run(&command, &ctx).await, + "verify" => commands::wolves::verify::run(&command, &ctx).await, + "unlink" => commands::wolves::unlink::run(&command, &ctx).await, // "link" => commands::count::servers::run(&command, &ctx).await, &_ => format!("not implemented :( wolves {}", x.name.as_str()), },