diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index bde0a7c..b9c66e5 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -15,142 +15,145 @@ use serenity::{ use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, WolvesVerify}; use sqlx::{Pool, Sqlite}; -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; +pub(crate) mod link { + use super::*; - 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; + 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; - if get_server_member_discord(&db, &command.user.name).await.is_some() { - return "Already linked".to_string(); - } + 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; - let option = command - .data - .options - .get(0) - .expect("Expected email option") - .resolved - .as_ref() - .expect("Expected email object"); - - let email = if let CommandDataOptionValue::String(email) = option { - email - } else { - return "Please provide a valid user".to_string(); - }; - - // check if email exists - let details = match get_server_member_email(&db, email).await { - None => return "Please check is the same as on https://ulwolves.ie/".to_string(), - Some(x) => x, - }; - - if details.verified { - return "Email already verified".to_string(); - } - - db_pending_clear_expired(&db).await; - - // send mail - if get_from_db(&db, &command.user.name).await.is_some() { - return "Please check email".to_string(); - } - - // generate a auth key - let auth = random_string(20); - match send_mail(&config, &details, &auth, &command.user.name) { - Ok(_) => match save_to_db(&db, &details, &auth, &command.user.name).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); + if get_server_member_discord(&db, &command.user.name).await.is_some() { + return "Already linked".to_string(); } + + let option = command + .data + .options + .get(0) + .expect("Expected email option") + .resolved + .as_ref() + .expect("Expected email object"); + + let email = if let CommandDataOptionValue::String(email) = option { + email + } else { + return "Please provide a valid user".to_string(); + }; + + // check if email exists + let details = match get_server_member_email(&db, email).await { + None => return "Please check is the same as on https://ulwolves.ie/".to_string(), + Some(x) => x, + }; + + if details.verified { + return "Email already verified".to_string(); + } + + db_pending_clear_expired(&db).await; + + // send mail + if get_from_db(&db, &command.user.name).await.is_some() { + return "Please check email".to_string(); + } + + // generate a auth key + let auth = random_string(20); + match send_mail(&config, &details, &auth, &command.user.name) { + Ok(_) => match save_to_db(&db, &details, &auth, &command.user.name).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 {}, user is {} {:?}", email, command.user.name, command.guild_id) } - format!("Verification email sent to {}, user is {} {:?}", email, command.user.name, command.guild_id) -} + pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("link") + .description("Set Wolves Email") + .create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true)) + } -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("link") - .description("Set Wolves Email") - .create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true)) -} - -async fn get_server_member_discord(db: &Pool, user: &str) -> Option { - sqlx::query_as::<_, Wolves>( - r#" + async fn get_server_member_discord(db: &Pool, user: &str) -> Option { + sqlx::query_as::<_, Wolves>( + r#" SELECT * FROM wolves WHERE discord = "#, - ) - .bind(user) - .fetch_one(db) - .await - .ok() -} + ) + .bind(user) + .fetch_one(db) + .await + .ok() + } -async fn get_server_member_email(db: &Pool, email: &str) -> Option { - sqlx::query_as::<_, Wolves>( - r#" + 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() -} + ) + .bind(email) + .fetch_one(db) + .await + .ok() + } -fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result { - let mail = &email.email; - let discord = "https://discord.skynet.ie"; - let sender = format!("UL Computer Society <{}>", &config.mail_user); + fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result { + let mail = &email.email; + let discord = "https://discord.skynet.ie"; + let sender = format!("UL Computer Society <{}>", &config.mail_user); - // Create the html we want to send. - let html = html! { - head { - title { "Hello from Skynet!" } - style type="text/css" { - "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" - } - } - div style="display: flex; flex-direction: column; align-items: center;" { - h2 { "Hello from Skynet!" } - // Substitute in the name of our recipient. - p { "Hi " (user) "," } - p { - "Please use " pre { "/verify " (auth)} " to verify your discord account." - } - p { - "If you have issues please refer to our Discord server:" - br; - a href=(discord) { (discord) } - } - p { - "Skynet Team" - br; - "UL Computer Society" - } - } - }; + // Create the html we want to send. + let html = html! { + head { + title { "Hello from Skynet!" } + style type="text/css" { + "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" + } + } + div style="display: flex; flex-direction: column; align-items: center;" { + h2 { "Hello from Skynet!" } + // Substitute in the name of our recipient. + p { "Hi " (user) "," } + p { + "Please use " pre { "/verify " (auth)} " to verify your discord account." + } + p { + "If you have issues please refer to our Discord server:" + br; + a href=(discord) { (discord) } + } + p { + "Skynet Team" + br; + "UL Computer Society" + } + } + }; - let body_text = format!( - r#" + let body_text = format!( + r#" Hi {user} Please use "/verify {auth}" to verify your discord account. @@ -161,70 +164,71 @@ fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result< Skynet Team UL Computer Society "# - ); + ); - // Build the message. - let email = Message::builder() - .from(sender.parse().unwrap()) - .to(mail.parse().unwrap()) - .subject("Skynet-Discord: Link 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"); + // Build the message. + let email = Message::builder() + .from(sender.parse().unwrap()) + .to(mail.parse().unwrap()) + .subject("Skynet-Discord: Link 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()); + 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).unwrap().credentials(creds).build(); + // Open a remote connection to gmail using STARTTLS + let mailer = SmtpTransport::starttls_relay(&config.mail_smtp).unwrap().credentials(creds).build(); - // Send the email - mailer.send(&email) -} + // Send the email + mailer.send(&email) + } -async fn db_pending_clear_expired(pool: &Pool) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" + 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() -} + ) + .bind(get_now_iso(true)) + .fetch_one(pool) + .await + .ok() + } -async fn get_from_db(db: &Pool, user: &str) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" + async fn get_from_db(db: &Pool, user: &str) -> Option { + sqlx::query_as::<_, WolvesVerify>( + r#" SELECT * FROM wolves_verify WHERE discord = "#, - ) - .bind(user) - .fetch_one(db) - .await - .ok() -} + ) + .bind(user) + .fetch_one(db) + .await + .ok() + } -async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &str) -> Result, sqlx::Error> { - sqlx::query_as::<_, WolvesVerify>( - " + async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &str) -> 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) - .bind(auth.to_owned()) - .bind(get_now_iso(false)) - .fetch_optional(db) - .await + ) + .bind(record.email.to_owned()) + .bind(user) + .bind(auth.to_owned()) + .bind(get_now_iso(false)) + .fetch_optional(db) + .await + } } diff --git a/src/main.rs b/src/main.rs index 24f291e..2430f0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,10 +4,7 @@ use serenity::{ async_trait, client::{Context, EventHandler}, model::{ - application::{ - command::Command, - interaction::{Interaction, InteractionResponseType}, - }, + application::{command::Command, interaction::Interaction}, gateway::{GatewayIntents, Ready}, guild, }, @@ -59,7 +56,7 @@ impl EventHandler for Handler { println!("[Main] {} is connected!", ready.user.name); Command::set_global_application_commands(&ctx.http, |commands| { - commands.create_application_command(|command| commands::link_email::register(command)) + commands.create_application_command(|command| commands::link_email::link::register(command)) }) .await .ok(); @@ -71,16 +68,11 @@ impl EventHandler for Handler { //println!("Received command interaction: {:#?}", command); let content = match command.data.name.as_str() { - "link" => commands::link_email::run(&command, &ctx).await, + "link" => commands::link_email::link::run(&command, &ctx).await, _ => "not implemented :(".to_string(), }; - if let Err(why) = command - .edit_original_interaction_response(&ctx.http, |response| { - response.content(content) - }) - .await - { + if let Err(why) = command.edit_original_interaction_response(&ctx.http, |response| response.content(content)).await { println!("Cannot respond to slash command: {}", why); } }