use rand::seq::SliceRandom; use regex::Regex; use serenity::{ model::{channel::Message, guild::Member}, prelude::*, utils::MessageBuilder, }; use std::process::{Command, Stdio}; use url::Url; use crate::config::CONFIG; use crate::database; use crate::ldap::ldap_exists; use crate::token_management::*; pub fn new_member(ctx: &Context, mut new_member: Member) { let mut message = MessageBuilder::new(); message.push("Nice to see you here "); message.mention(&new_member); message.push_line("! Would you care to introduce yourself?"); message.push_line("If you're not sure where to start, perhaps you could tell us about your projects, your first computer…"); message.push_line("You should also know that we follow the Freenode Channel Guidelines: https://freenode.net/changuide, and try to avoid defamatory content."); message.push_line("Make sure to check out "); message.mention(&CONFIG.readme_channel); message.push_line(" to get yourself some roles for directed pings 😊"); send_message!(CONFIG.welcome_channel, &ctx, message.build()); let mut message = MessageBuilder::new(); message.push(format!("Say hi to {} in ", new_member.display_name())); message.mention(&CONFIG.welcome_channel); send_message!(CONFIG.main_channel, &ctx, message.build()); if let Err(why) = new_member.add_role(&ctx.http, CONFIG.unregistered_member_role) { error!("Error adding user role: {:?}", why); }; } fn member_nickname(member: &database::Member) -> String { let username = member.username.clone(); if let Some(tla) = member.tla.clone() { if username.to_uppercase() == tla { return format!("{}", username); } else { return format!("{} [{}]", username, tla); } } else { return format!("{}", username); } } pub const RANDOM_SASS: &[&str] = &[ "Please. As if I'd fall for that.", "Did you really think a stunt like that would work?", "Nothing slips past me.", "Did you even read the first line of !help?", "I never treated you this badly.", ]; pub struct Commands; impl Commands { pub fn register(ctx: Context, msg: Message, account_name: &str) { if account_name.is_empty() { send_message!( msg.channel_id, &ctx.http, format!("Usage: {}register ", CONFIG.command_prefix) ); return; } if vec![ "committee", "committee-only", "ucc", "ucc-announce", "tech", "wheel", "door", "coke", ] .contains(&account_name) || database::username_exists(account_name) { send_message!( msg.channel_id, &ctx.http, RANDOM_SASS .choose(&mut rand::thread_rng()) .expect("We couldn't get any sass") ); return; } if !ldap_exists(account_name) { send_message!( msg.channel_id, &ctx.http, format!( "I couldn't find an account with the username '{}'", account_name ) ); return; } match msg.channel_id.say( &ctx.http, format!("Ok {}, I've sent an email to you :)", account_name), ) { Ok(new_msg) => { e!("Failed to delete message: {:?}", new_msg.delete(&ctx)); } Err(why) => error!("Error sending message: {:?}", why), } e!("Error deleting register message: {:?}", msg.delete(ctx)); let message = Command::new("echo").arg(format!("

Link your Discord account

\

Hi {}, to complete the link, go to the discord server and enter\

{}verify {}
\

The UCC discord bot", account_name, CONFIG.command_prefix, generate_token(&msg.author, account_name))).stdout(Stdio::piped()).spawn().expect("Unable to spawn echo command"); match Command::new("mutt") .arg("-e") .arg("set content_type=text/html") .arg("-e") .arg("set realname=\"UCC Discord Bot\"") .arg("-s") .arg("Discord account link token") .arg(format!("{}@ucc.asn.au", account_name)) .stdin(message.stdout.unwrap()) .output() { Ok(_) => {} Err(why) => error!("Unable to send message with mutt {:?}", why), }; } pub fn verify(ctx: Context, msg: Message, token: &str) { match parse_token(&msg.author, token) { Ok(name) => { e!( "Unable to get member: {:?}", serenity::model::id::GuildId(CONFIG.server_id) .member(ctx.http.clone(), msg.author.id) .map(|mut member| { let full_member = database::add_member(&msg.author.id.0, &name); e!( "Unable to remove role: {:?}", member.remove_role(&ctx.http, CONFIG.unregistered_member_role) ); e!( "Unable to add role: {:?}", member.add_role(&ctx.http, CONFIG.registered_member_role) ); e!( "Unable to edit nickname: {:?}", member.edit(&ctx.http, |m| { m.nickname(member_nickname(&full_member)); m }) ); send_delete_message!( msg.channel_id, ctx.http.clone(), "Verification sucessful" ); }) ); } Err(reason) => send_message!( msg.channel_id, &ctx.http, format!("Verification error: {:?}", reason) ), } e!("Error deleting register message: {:?}", msg.delete(&ctx)); } pub fn profile(ctx: Context, msg: Message, name: &str) { let possible_member: Option = match if name.trim().is_empty() { database::get_member_info(&msg.author.id.0) } else { database::get_member_info_from_username(&name) } { Ok(member) => Some(member), Err(why) => { warn!("Could not find member {:?}", why); if name.len() != 3 { None } else { match database::get_member_info_from_tla(&name) { Ok(member) => Some(member), Err(_) => None, } } } }; if possible_member.is_none() { send_message!( msg.channel_id, &ctx.http, "Sorry, I couldn't find that profile (you need to !register for a profile)" ); return; } let member = possible_member.unwrap(); let result = msg.channel_id.send_message(&ctx.http, |m| { m.embed(|embed| { embed.colour(serenity::utils::Colour::LIGHTER_GREY); embed.footer(|f| { let user = &ctx .http .get_user(member.discord_id.clone() as u64) .expect("We expected this user to exist... they didn't ;("); f.text(&user.name); f.icon_url( user.static_avatar_url() .expect("Expected user to have avatar"), ); f }); if let Some(name) = member.name.clone() { embed.title(name); } if let Some(photo) = member.photo.clone() { embed.thumbnail(photo); } embed.field("Username", &member.username, true); if let Some(tla) = member.tla.clone() { embed.field("TLA", tla, true); } if let Some(bio) = member.biography.clone() { embed.field("Bio", bio, false); } if let Some(git) = member.github.clone() { embed.field("Git", git, false); } if let Some(web) = member.website.clone() { embed.field("Website", web, false); } embed }); m }); if let Err(why) = result { error!("Error sending profile embed: {:?}", why); } } pub fn set_info(ctx: Context, msg: Message, info: &str) { if info.trim().is_empty() { send_message!( msg.channel_id, &ctx.http, format!( "Usage: {}set ", CONFIG.command_prefix ) ); return; } let info_content: Vec<_> = info.splitn(2, ' ').collect(); let mut property = String::from(info_content[0]); property = property.replace("github", "git"); if info_content.len() == 1 || !vec!["bio", "git", "photo"].contains(&property.as_str()) { send_message!( msg.channel_id, &ctx.http, format!( "Usage: {}set {} {}", CONFIG.command_prefix, property, match property.as_str() { "bio" => "some information about yourself :)", "git" => "a url to your git{hub,lab} account", "photo" => "a url to a profile photo online", "web" => "a url to your website/webpage", _ => "whatever you want, because this does absolutely nothing. Try !set to see what you can do" } ) ); return; } let mut value = info_content[1].to_string(); if vec!["git", "photo", "web"].contains(&property.as_str()) { if match Url::parse(&value) { Err(_) => true, Ok(_) => false, } { let user_regex = Regex::new(r"^\w+$").unwrap(); if property == "git" && user_regex.is_match(&value) { value = format!("github.com/{}", value); } value = format!("https://{}", value); if match Url::parse(&value) { Err(_) => true, Ok(_) => false, } { send_message!( msg.channel_id, &ctx.http, "That ain't a URL where I come from..." ); return; } } } if let Ok(member) = database::get_member_info(&msg.author.id.0) { let set_property = match property.as_str() { "bio" => database::set_member_bio(&msg.author.id.0, &value), "git" => database::set_member_git(&msg.author.id.0, &value), "photo" => database::set_member_photo(&msg.author.id.0, &value), "web" => database::set_member_website(&msg.author.id.0, &value), _ => Err(diesel::result::Error::NotFound), }; match set_property { Ok(_) => { if property == "git" && member.photo == None { let git_url = Url::parse(&value).unwrap(); // we parsed this earlier and it was fine match git_url.host_str() { Some("github.com") => { if let Some(mut path_segments) = git_url.path_segments() { database::set_member_photo( &msg.author.id.0, format!( "https://github.com/{}.png", path_segments.next().expect("URL doesn't have a path") ) .as_str(), ) .expect("Attempt to set member photo failed"); } else { info!("Git path added (2), {}", git_url.path()); } } _ => info!("Git path added, {}", git_url.path()), } } } Err(why) => { error!( "Umable to set property {} to {} in DB {:?}", property, value, why ); send_message!(msg.channel_id, &ctx.http, "Failed to set property. Ooops."); } } if let Err(why) = msg.delete(&ctx) { error!("Error deleting set profile property: {:?}", why); } } else { send_message!( msg.channel_id, &ctx.http, format!( "You don't seem to have a profile. {}register to get one", CONFIG.command_prefix ) ) } } }