Commit 40576b91 authored by tec's avatar tec

Merge branch 'morefix' into 'master'

move secrets to a separate config

See merge request UCC/discord-bot!9
parents d49027c1 d1b9e816
Pipeline #200 failed with stages
in 2 seconds
...@@ -22,3 +22,4 @@ diesel = { version = "1.4.3", features = ["sqlite"] } ...@@ -22,3 +22,4 @@ diesel = { version = "1.4.3", features = ["sqlite"] }
ldap3 = "0.6" ldap3 = "0.6"
url = "^2.1" url = "^2.1"
regex = "^1.3" regex = "^1.3"
toml = "0.5"
use indexmap::IndexMap; use indexmap::IndexMap;
use serde::Deserialize; use serde::Deserialize;
use serde_yaml; use serde_yaml;
use toml;
use serenity::model::id; use serenity::model::id;
use std::fs; use std::fs;
...@@ -9,6 +10,11 @@ lazy_static! { ...@@ -9,6 +10,11 @@ lazy_static! {
pub static ref CONFIG: UccbotConfig = serde_yaml::from_str(&CONFIG_FILE).unwrap(); pub static ref CONFIG: UccbotConfig = serde_yaml::from_str(&CONFIG_FILE).unwrap();
} }
lazy_static! {
static ref SECRETS_FILE: String = fs::read_to_string("secrets.toml").unwrap();
pub static ref SECRETS: UccbotSecrets = toml::from_str(&SECRETS_FILE).unwrap();
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct UccbotConfig { pub struct UccbotConfig {
pub server_id: u64, pub server_id: u64,
...@@ -32,7 +38,6 @@ pub struct UccbotConfig { ...@@ -32,7 +38,6 @@ pub struct UccbotConfig {
pub react_role_messages: Vec<ReactionMapping>, pub react_role_messages: Vec<ReactionMapping>,
#[serde(default = "ldap_bind_address")] #[serde(default = "ldap_bind_address")]
pub bind_address: String, pub bind_address: String,
pub ldap_pass: String,
} }
pub fn ldap_bind_address() -> String { pub fn ldap_bind_address() -> String {
...@@ -52,6 +57,12 @@ impl UccbotConfig { ...@@ -52,6 +57,12 @@ impl UccbotConfig {
} }
} }
#[derive(Debug, Deserialize)]
pub struct UccbotSecrets {
pub ldap_pass: String,
pub discord_token: String,
}
pub type ReactRoleMap = IndexMap<String, id::RoleId>; pub type ReactRoleMap = IndexMap<String, id::RoleId>;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
......
use ldap3::{LdapConn, LdapConnSettings, Scope, SearchEntry}; use ldap3::{LdapConn, LdapConnSettings, Scope, SearchEntry};
use crate::config::CONFIG; use crate::config::{CONFIG, SECRETS};
#[derive(Debug)] #[derive(Debug)]
pub struct LDAPUser { pub struct LDAPUser {
...@@ -14,7 +14,7 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> { ...@@ -14,7 +14,7 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> {
.expect("Unable to connect to LDAP"); .expect("Unable to connect to LDAP");
ldap.simple_bind( ldap.simple_bind(
"cn=ucc-discord-bot,cn=Users,dc=ad,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au", "cn=ucc-discord-bot,cn=Users,dc=ad,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
&CONFIG.ldap_pass, &SECRETS.ldap_pass,
) )
.expect("Unable to attempt to bind to LDAP") .expect("Unable to attempt to bind to LDAP")
.success() .success()
...@@ -29,8 +29,8 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> { ...@@ -29,8 +29,8 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> {
.expect("LDAP error") .expect("LDAP error")
.success() .success()
.expect("LDAP search error"); .expect("LDAP search error");
if rs.len() != 1 { if rs.is_empty() {
return None; return None
} }
let result = SearchEntry::construct(rs[0].clone()).attrs; let result = SearchEntry::construct(rs[0].clone()).attrs;
Some(LDAPUser { Some(LDAPUser {
...@@ -50,10 +50,7 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> { ...@@ -50,10 +50,7 @@ pub fn ldap_search(username: &str) -> Option<LDAPUser> {
} }
pub fn ldap_exists(username: &str) -> bool { pub fn ldap_exists(username: &str) -> bool {
match ldap_search(username) { ldap_search(username).is_some()
Some(_) => true,
None => false,
}
} }
#[derive(Debug)] #[derive(Debug)]
......
...@@ -13,7 +13,7 @@ extern crate diesel; ...@@ -13,7 +13,7 @@ extern crate diesel;
extern crate ldap3; extern crate ldap3;
use simplelog::*; use simplelog::*;
use std::fs::{read_to_string, File}; use std::fs::File;
use chrono::prelude::Utc; use chrono::prelude::Utc;
use serenity::{ use serenity::{
...@@ -32,7 +32,7 @@ mod token_management; ...@@ -32,7 +32,7 @@ mod token_management;
mod user_management; mod user_management;
mod voting; mod voting;
use config::CONFIG; use config::{CONFIG, SECRETS};
use reaction_roles::{add_role_by_reaction, remove_role_by_reaction}; use reaction_roles::{add_role_by_reaction, remove_role_by_reaction};
use util::get_string_from_react; use util::get_string_from_react;
...@@ -130,12 +130,12 @@ impl EventHandler for Handler { ...@@ -130,12 +130,12 @@ impl EventHandler for Handler {
Ok(message) => match get_message_type(&message) { Ok(message) => match get_message_type(&message) {
MessageType::RoleReactMessage if add_reaction.user_id.0 != CONFIG.bot_id => { MessageType::RoleReactMessage if add_reaction.user_id.0 != CONFIG.bot_id => {
add_role_by_reaction(&ctx, message, add_reaction); add_role_by_reaction(&ctx, message, add_reaction);
return; return
} }
_ if message.author.id.0 != CONFIG.bot_id _ if message.author.id.0 != CONFIG.bot_id
|| add_reaction.user_id == CONFIG.bot_id => || add_reaction.user_id == CONFIG.bot_id =>
{ {
return; return
} }
MessageType::Motion => voting::reaction_add(ctx, add_reaction), MessageType::Motion => voting::reaction_add(ctx, add_reaction),
MessageType::LogReact => { MessageType::LogReact => {
...@@ -146,7 +146,7 @@ impl EventHandler for Handler { ...@@ -146,7 +146,7 @@ impl EventHandler for Handler {
"The logreact message {} just tried to use is too old", "The logreact message {} just tried to use is too old",
react_user.name react_user.name
); );
return; return
} }
info!( info!(
"The react {} just added is {:?}. In full: {:?}", "The react {} just added is {:?}. In full: {:?}",
...@@ -172,12 +172,12 @@ impl EventHandler for Handler { ...@@ -172,12 +172,12 @@ impl EventHandler for Handler {
Ok(message) => match get_message_type(&message) { Ok(message) => match get_message_type(&message) {
MessageType::RoleReactMessage if removed_reaction.user_id != CONFIG.bot_id => { MessageType::RoleReactMessage if removed_reaction.user_id != CONFIG.bot_id => {
remove_role_by_reaction(&ctx, message, removed_reaction); remove_role_by_reaction(&ctx, message, removed_reaction);
return; return
} }
_ if message.author.id.0 != CONFIG.bot_id _ if message.author.id.0 != CONFIG.bot_id
|| removed_reaction.user_id == CONFIG.bot_id => || removed_reaction.user_id == CONFIG.bot_id =>
{ {
return; return
} }
MessageType::Motion => voting::reaction_remove(ctx, removed_reaction), MessageType::Motion => voting::reaction_remove(ctx, removed_reaction),
_ => {} _ => {}
...@@ -222,13 +222,10 @@ fn main() { ...@@ -222,13 +222,10 @@ fn main() {
]) ])
.unwrap(); .unwrap();
// Configure the client with your Discord bot token in the environment.
let token = read_to_string("discord_token").unwrap();
// Create a new instance of the Client, logging in as a bot. This will // Create a new instance of the Client, logging in as a bot. This will
// automatically prepend your bot token with "Bot ", which is a requirement // automatically prepend your bot token with "Bot ", which is a requirement
// by Discord for bot users. // by Discord for bot users.
let mut client = Client::new(&token, Handler).expect("Err creating client"); let mut client = Client::new(&SECRETS.discord_token, Handler).expect("Err creating client");
// Finally, start a single shard, and start listening to events. // Finally, start a single shard, and start listening to events.
// //
......
...@@ -97,7 +97,7 @@ pub fn sync_all_role_reactions(ctx: &Context) { ...@@ -97,7 +97,7 @@ pub fn sync_all_role_reactions(ctx: &Context) {
for react in &message.reactions { for react in &message.reactions {
let react_as_string = get_string_from_react(&react.reaction_type); let react_as_string = get_string_from_react(&react.reaction_type);
if mapping.contains_key(&react_as_string) { if mapping.contains_key(&react_as_string) {
continue; continue
} }
info!( info!(
" message #{}: Removing non-role react '{}'", " message #{}: Removing non-role react '{}'",
......
...@@ -58,6 +58,8 @@ impl std::fmt::Display for TokenError { ...@@ -58,6 +58,8 @@ impl std::fmt::Display for TokenError {
} }
} }
pub static TOKEN_LIFETIME: i64 = 300; // 5 minutes
pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, TokenError> { pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, TokenError> {
guard!(let Some(token) = text_decrypt(encrypted_token) else { guard!(let Some(token) = text_decrypt(encrypted_token) else {
return Err(TokenError::TokenInvalid) return Err(TokenError::TokenInvalid)
...@@ -73,15 +75,15 @@ pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String, ...@@ -73,15 +75,15 @@ pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String,
let token_username = token_components[2]; let token_username = token_components[2];
if token_discord_user != discord_user.id.0.to_string() { if token_discord_user != discord_user.id.0.to_string() {
warn!("... attempt failed : DiscordID mismatch"); warn!("... attempt failed : DiscordID mismatch");
return Err(TokenError::DiscordIdMismatch); return Err(TokenError::DiscordIdMismatch)
} }
let time_delta_seconds = Utc::now().timestamp() - token_timestamp.timestamp(); let time_delta_seconds = Utc::now().timestamp() - token_timestamp.timestamp();
if time_delta_seconds > 5 * 60 { if time_delta_seconds > TOKEN_LIFETIME {
warn!( warn!(
"... attempt failed : token expired ({} seconds old)", "... attempt failed : token expired ({} seconds old)",
time_delta_seconds time_delta_seconds
); );
return Err(TokenError::TokenExpired); return Err(TokenError::TokenExpired)
} }
info!( info!(
"... verification successful (token {} seconds old)", "... verification successful (token {} seconds old)",
......
...@@ -34,7 +34,7 @@ pub fn new_member(ctx: &Context, mut new_member: Member) { ...@@ -34,7 +34,7 @@ pub fn new_member(ctx: &Context, mut new_member: Member) {
if let Err(why) = new_member.add_role(&ctx.http, CONFIG.unregistered_member_role) { if let Err(why) = new_member.add_role(&ctx.http, CONFIG.unregistered_member_role) {
error!("Error adding user role: {:?}", why); error!("Error adding user role: {:?}", why);
}; }
} }
fn member_nickname(member: &database::Member) -> String { fn member_nickname(member: &database::Member) -> String {
...@@ -78,7 +78,7 @@ impl Commands { ...@@ -78,7 +78,7 @@ impl Commands {
&ctx.http, &ctx.http,
format!("Usage: {}register <username>", CONFIG.command_prefix) format!("Usage: {}register <username>", CONFIG.command_prefix)
); );
return; return
} }
if RESERVED_NAMES.contains(&account_name) || database::username_exists(account_name) { if RESERVED_NAMES.contains(&account_name) || database::username_exists(account_name) {
send_message!( send_message!(
...@@ -88,7 +88,7 @@ impl Commands { ...@@ -88,7 +88,7 @@ impl Commands {
.choose(&mut rand::thread_rng()) .choose(&mut rand::thread_rng())
.expect("We couldn't get any sass") .expect("We couldn't get any sass")
); );
return; return
} }
if !ldap_exists(account_name) { if !ldap_exists(account_name) {
send_message!( send_message!(
...@@ -99,7 +99,7 @@ impl Commands { ...@@ -99,7 +99,7 @@ impl Commands {
account_name account_name
) )
); );
return; return
} }
send_message!( send_message!(
msg.channel_id, msg.channel_id,
...@@ -200,7 +200,7 @@ impl Commands { ...@@ -200,7 +200,7 @@ impl Commands {
&ctx.http, &ctx.http,
"Sorry, I couldn't find that profile (you need to !register for a profile)" "Sorry, I couldn't find that profile (you need to !register for a profile)"
); );
return; return
} }
let member = possible_member.unwrap(); let member = possible_member.unwrap();
let result = msg.channel_id.send_message(&ctx.http, |m| { let result = msg.channel_id.send_message(&ctx.http, |m| {
...@@ -250,29 +250,15 @@ impl Commands { ...@@ -250,29 +250,15 @@ impl Commands {
} }
pub fn set_info(ctx: Context, msg: Message, info: &str) { pub fn set_info(ctx: Context, msg: Message, info: &str) {
if info.trim().is_empty() { if info.trim().is_empty() {
msg.channel_id send_message!(
.send_message(&ctx.http, |m| { msg.channel_id,
m.embed(|embed| { &ctx.http,
embed.colour(serenity::utils::Colour::LIGHT_GREY); format!(
embed.title("Usage"); "Usage: {}set <bio|git|web|photo> <value>",
embed.description( CONFIG.command_prefix
format!( )
"`{}set <field> <info>` or `{}clear <field>`", );
CONFIG.command_prefix, return
CONFIG.command_prefix,
)
);
embed.field("Biography", format!("`{}set bio <info>`\nBe friendly! Provide a little introduction to yourself.", CONFIG.command_prefix), false);
embed.field("Git", format!("`{}set git <url>`\nA link to your git forge profile. Also takes a github username for convinience", CONFIG.command_prefix), false);
embed.field("Photo", format!("`{}set photo <url>`\nPut a face to a name! Provide a profile photo.", CONFIG.command_prefix), false);
embed.field("Website", format!("`{}set web <info>`\nGot a personal website? Share it here :)", CONFIG.command_prefix), false);
embed.field("Studying", format!("`{}set study <info>`\nYou're (probably) a Uni student, what's your major?", CONFIG.command_prefix), false);
embed
});
m
})
.expect("Failed to send usage help embed");
return;
} }
let info_content: Vec<_> = info.splitn(2, ' ').collect(); let info_content: Vec<_> = info.splitn(2, ' ').collect();
let mut property = String::from(info_content[0]); let mut property = String::from(info_content[0]);
...@@ -280,43 +266,23 @@ impl Commands { ...@@ -280,43 +266,23 @@ impl Commands {
if info_content.len() == 1 if info_content.len() == 1
|| !vec!["bio", "git", "web", "photo", "study"].contains(&property.as_str()) || !vec!["bio", "git", "web", "photo", "study"].contains(&property.as_str())
{ {
msg.channel_id send_message!(
.send_message(&ctx.http, |m| { msg.channel_id,
m.embed(|embed| { &ctx.http,
embed.colour(serenity::utils::Colour::LIGHT_GREY); format!(
embed.title("Usage"); "Usage: {}set {} {}",
embed.field( CONFIG.command_prefix,
match property.as_str() { property,
"bio" => "Biography", match property.as_str() {
"git" => "Git Forge Profile", "bio" => "some information about yourself :)",
"photo" => "Profile Photo", "git" => "a url to your git{hub,lab} account",
"web" => "Personal Website", "photo" => "a url to a profile photo online",
"study" => "Area of study", "web" => "a url to your website/webpage",
_ => "???", _ => "whatever you want, because this does absolutely nothing. Try !set to see what you can do"
}, }
format!( )
"`{}set {} <info>` or `{}clear {}`\n{}", );
CONFIG.command_prefix, return
property,
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",
"study" => "Your degree title",
_ => "Whatever you want, because this does absolutely nothing.",
}
),
false,
);
embed
});
m
})
.expect("Failed to send usage embed");
return;
} }
let mut value = info_content[1].to_string(); let mut value = info_content[1].to_string();
...@@ -333,7 +299,7 @@ impl Commands { ...@@ -333,7 +299,7 @@ impl Commands {
&ctx.http, &ctx.http,
"That ain't a URL where I come from..." "That ain't a URL where I come from..."
); );
return; return
} }
} }
} }
...@@ -400,7 +366,7 @@ impl Commands { ...@@ -400,7 +366,7 @@ impl Commands {
Commands::set_info(ctx, msg, ""); Commands::set_info(ctx, msg, "");
return; return;
} }
let clear_property = match field { match field {
"bio" => database::set_member_bio(&msg.author.id.0, None), "bio" => database::set_member_bio(&msg.author.id.0, None),
"git" => database::set_member_git(&msg.author.id.0, None), "git" => database::set_member_git(&msg.author.id.0, None),
"photo" => database::set_member_photo(&msg.author.id.0, None), "photo" => database::set_member_photo(&msg.author.id.0, None),
......
...@@ -15,7 +15,7 @@ impl Commands { ...@@ -15,7 +15,7 @@ impl Commands {
let motion = content; let motion = content;
if !motion.is_empty() { if !motion.is_empty() {
create_motion(&ctx, &msg, motion); create_motion(&ctx, &msg, motion);
return; return
} }
send_message!( send_message!(
msg.channel_id, msg.channel_id,
...@@ -34,7 +34,7 @@ impl Commands { ...@@ -34,7 +34,7 @@ impl Commands {
let topic = content; let topic = content;
if !topic.is_empty() { if !topic.is_empty() {
create_poll(&ctx, &msg, topic); create_poll(&ctx, &msg, topic);
return; return
} }
send_message!( send_message!(
msg.channel_id, msg.channel_id,
...@@ -193,11 +193,24 @@ fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo { ...@@ -193,11 +193,24 @@ fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo {
fn set_cached_motion(id: serenity::model::id::MessageId, motion_info: MotionInfo) { fn set_cached_motion(id: serenity::model::id::MessageId, motion_info: MotionInfo) {
if let Some(motion) = MOTIONS_CACHE.lock().unwrap().get_mut(&id) { if let Some(motion) = MOTIONS_CACHE.lock().unwrap().get_mut(&id) {
*motion = motion_info; *motion = motion_info;
return; return
} }
warn!("{}", "Couldn't find motion in cache to set"); warn!("{}", "Couldn't find motion in cache to set");
} }
macro_rules! tiebreaker {
($ctx: expr, $vote: expr, $motion_info: expr) => {
if $motion_info.votes.get($vote).unwrap().iter().any(|u| {
u.has_role($ctx, CONFIG.server_id, CONFIG.tiebreaker_role)
.unwrap()
}) {
0.25
} else {
0.0
}
}
}
fn update_motion( fn update_motion(
ctx: &Context, ctx: &Context,
msg: &mut Message, msg: &mut Message,
...@@ -211,31 +224,9 @@ fn update_motion( ...@@ -211,31 +224,9 @@ fn update_motion(
let against_votes = motion_info.votes.get(&CONFIG.against_vote).unwrap().len() as isize - 1; let against_votes = motion_info.votes.get(&CONFIG.against_vote).unwrap().len() as isize - 1;
let abstain_votes = motion_info.votes.get(&CONFIG.abstain_vote).unwrap().len() as isize - 1; let abstain_votes = motion_info.votes.get(&CONFIG.abstain_vote).unwrap().len() as isize - 1;
let has_tiebreaker = |users: &Vec<serenity::model::user::User>| { let for_strength = for_votes as f32 + tiebreaker!(ctx, &CONFIG.for_vote, motion_info);
users.iter().any(|u| { let against_strength = against_votes as f32 + tiebreaker!(ctx, &CONFIG.against_vote, motion_info);
u.has_role(ctx, CONFIG.server_id, CONFIG.tiebreaker_role) let abstain_strength = abstain_votes as f32 + tiebreaker!(ctx, &CONFIG.abstain_vote, motion_info);
.unwrap()
})
};
let for_strength = for_votes as f32
+ (if has_tiebreaker(motion_info.votes.get(&CONFIG.for_vote).unwrap()) {
0.25
} else {
0.0
});
let against_strength = against_votes as f32
+ (if has_tiebreaker(motion_info.votes.get(&CONFIG.against_vote).unwrap()) {
0.25
} else {
0.0
});
let abstain_strength = abstain_votes as f32
+ (if has_tiebreaker(motion_info.votes.get(&CONFIG.abstain_vote).unwrap()) {
0.25
} else {
0.0
});
let old_embed = msg.embeds[0].clone(); let old_embed = msg.embeds[0].clone();
let topic = old_embed.clone().title.unwrap(); let topic = old_embed.clone().title.unwrap();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment