diff --git a/Cargo.toml b/Cargo.toml index 45e271a9e470a3eba801a44f5cac5fc142b9d191..1b9c56495fb82eae7590595cddff250658718e67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,5 @@ rand = "^0.7.2" lazy_static = "^1.4.0" log = "^0.4.8" simplelog = "^0.7.4" +toml = "^0.5.6" +serde = "^1.0.104" diff --git a/src/config.rs b/src/config.rs index 2c1170b4e5c3ddc1634b077bd309f9a2144abbbb..d7aad5d132ecec266496bbfe850b1d7fde027ade 120000 --- a/src/config.rs +++ b/src/config.rs @@ -1 +1,49 @@ -./config.test.rs \ No newline at end of file +use serenity; +use std::fs; +use serde::Deserialize; +use toml; + +lazy_static! { + static ref CONFIG_FILE: String = fs::read_to_string("config.toml").unwrap(); +} + +lazy_static! { + pub static ref CONFIG: UccbotConfig = toml::from_str(&CONFIG_FILE).unwrap(); +} + +pub static DISCORD_TOKEN: &str = include_str!("discord_token"); + +#[derive(Deserialize)] +pub struct UccbotConfig { + pub server_id: u64, + // #general + pub main_channel: serenity::model::id::ChannelId, + // #the-corner + pub welcome_channel: serenity::model::id::ChannelId, + // #general + pub announcement_channel: serenity::model::id::ChannelId, + pub bot_id: u64, + pub vote_pool_size: i8, + pub vote_role: u64, + pub tiebreaker_role: u64, + pub unregistered_member_role: u64, + pub registered_member_role: u64, + pub command_prefix: &'static str, + pub for_vote: &'static str, + pub against_vote: &'static str, + pub abstain_vote: &'static str, + pub approve_react: &'static str, + pub disapprove_react: &'static str, + pub unsure_react: &'static str, +} + +impl UccbotConfig { + pub fn allowed_reacts(&self) -> Vec<String> { + vec!(self.for_vote.to_string(), + self.against_vote.to_string(), + self.abstain_vote.to_string(), + self.approve_react.to_string(), + self.disapprove_react.to_string(), + self.unsure_react.to_string()) + } +} \ No newline at end of file diff --git a/src/config.test.rs b/src/config.test.rs deleted file mode 100644 index 5fc3bb7609b2223a34b248e2df3b244c263e1cd3..0000000000000000000000000000000000000000 --- a/src/config.test.rs +++ /dev/null @@ -1,39 +0,0 @@ -use serenity; - -pub static DISCORD_TOKEN: &str = include_str!("discord_token"); - -pub static SERVER_ID: u64 = 606351521117896704; -// #general -pub static MAIN_CHANNEL: serenity::model::id::ChannelId = - serenity::model::id::ChannelId(606351521117896706); -// #the-corner -pub static WELCOME_CHANNEL: serenity::model::id::ChannelId = - serenity::model::id::ChannelId(606351613816209418); -// #general -pub static ANNOUNCEMENT_CHANNEL: serenity::model::id::ChannelId = - serenity::model::id::ChannelId(606351521117896706); - -pub static BOT_ID: u64 = 607078903969742848; - -pub static VOTE_POOL_SIZE: i8 = 2; -pub static VOTE_ROLE: u64 = 607478818038480937; -pub static TIEBREAKER_ROLE: u64 = 607509283483025409; -pub static UNREGISTERED_MEMBER_ROLE: u64 = 608282247350714408; -pub static REGISTERED_MEMBER_ROLE: u64 = 608282133118582815; - -pub static COMMAND_PREFIX: &str = "!"; - -pub static FOR_VOTE: &str = "ðŸ‘"; -pub static AGAINST_VOTE: &str = "👎"; -pub static ABSTAIN_VOTE: &str = "🙊"; -pub static APPROVE_REACT: &str = "⬆"; -pub static DISAPPROVE_REACT: &str = "⬇"; -pub static UNSURE_REACT: &str = "â”"; -pub static ALLOWED_REACTS: &[&'static str] = &[ - FOR_VOTE, - AGAINST_VOTE, - ABSTAIN_VOTE, - APPROVE_REACT, - DISAPPROVE_REACT, - UNSURE_REACT, -]; diff --git a/src/config.test.toml b/src/config.test.toml new file mode 100644 index 0000000000000000000000000000000000000000..bb0b38e6f59fdd20fd1638c7690b9171d7b5df32 --- /dev/null +++ b/src/config.test.toml @@ -0,0 +1,23 @@ +server_id = 606351521117896704 +#general +main_channel = 606351521117896706 +#the-corner +welcome_channel = 606351613816209418 +#general +announcement_channel = 606351521117896706 + +bot_id = 607078903969742848 + +vote_pool_size = 2 +vote_role = 607478818038480937 +tiebreaker_role = 607509283483025409 +unregistered_member_role = 608282247350714408 +registered_member_role = 608282133118582815 +command_prefix = "!" + +for_vote = "ðŸ‘" +against_vote = "👎" +abstain_vote = "🙊" +approve_react = "⬆" +disapprove_react = "⬇" +unsure_react = "â”" diff --git a/src/config.toml b/src/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..bb0b38e6f59fdd20fd1638c7690b9171d7b5df32 --- /dev/null +++ b/src/config.toml @@ -0,0 +1,23 @@ +server_id = 606351521117896704 +#general +main_channel = 606351521117896706 +#the-corner +welcome_channel = 606351613816209418 +#general +announcement_channel = 606351521117896706 + +bot_id = 607078903969742848 + +vote_pool_size = 2 +vote_role = 607478818038480937 +tiebreaker_role = 607509283483025409 +unregistered_member_role = 608282247350714408 +registered_member_role = 608282133118582815 +command_prefix = "!" + +for_vote = "ðŸ‘" +against_vote = "👎" +abstain_vote = "🙊" +approve_react = "⬆" +disapprove_react = "⬇" +unsure_react = "â”" diff --git a/src/config.ucc.rs b/src/config.ucc.rs deleted file mode 100644 index 58aa01cc349d5631851a18104d290637f88abbcc..0000000000000000000000000000000000000000 --- a/src/config.ucc.rs +++ /dev/null @@ -1,39 +0,0 @@ -use serenity; - -pub static DISCORD_TOKEN: &str = include_str!("discord_token"); - -pub static SERVER_ID: u64 = 264401248676085760; -// #ucc -pub static MAIN_CHANNEL: serenity::model::id::ChannelId = - serenity::model::id::ChannelId(264401248676085760); -// #welcome -pub static WELCOME_CHANNEL: serenity::model::id::ChannelId = - serenity::model::id::ChannelId(606750983699300372); -// #committee -pub static ANNOUNCEMENT_CHANNEL: serenity::model::id::ChannelId = - serenity::model::id::ChannelId(264411219627212801); - -pub static BOT_ID: u64 = 635407267881156618; - -pub static VOTE_POOL_SIZE: i8 = 7; -pub static VOTE_ROLE: u64 = 269817189966544896; -pub static TIEBREAKER_ROLE: u64 = 635370432568098817; -pub static UNREGISTERED_MEMBER_ROLE: u64 = 0; // does not exist -pub static REGISTERED_MEMBER_ROLE: u64 = 0; // does not exist - -pub static COMMAND_PREFIX: &str = "!"; - -pub static FOR_VOTE: &str = "ðŸ‘"; -pub static AGAINST_VOTE: &str = "👎"; -pub static ABSTAIN_VOTE: &str = "🙊"; -pub static APPROVE_REACT: &str = "⬆"; -pub static DISAPPROVE_REACT: &str = "⬇"; -pub static UNSURE_REACT: &str = "â”"; -pub static ALLOWED_REACTS: &[&'static str] = &[ - FOR_VOTE, - AGAINST_VOTE, - ABSTAIN_VOTE, - APPROVE_REACT, - DISAPPROVE_REACT, - UNSURE_REACT, -]; diff --git a/src/config.ucc.toml b/src/config.ucc.toml new file mode 100644 index 0000000000000000000000000000000000000000..2c995db97c8425192c1a85d41715ee5bada28960 --- /dev/null +++ b/src/config.ucc.toml @@ -0,0 +1,24 @@ +server_id = 264401248676085760 +#ucc +main_channel = 264401248676085760 +#welcome +welcome_channel = 606750983699300372 +#committee +announcement_channel = 264411219627212801 + +bot_id = 635407267881156618 + +vote_pool_size = 7 +vote_role = 269817189966544896 +tiebreaker_role = 635370432568098817 +unregistered_member_role = 0 # does not exist +registered_member_role = 0 # does not exist + +command_prefix = "!" + +for_vote = "ðŸ‘" +against_vote = "👎" +abstain_vote = "🙊" +approve_react = "⬆" +disapprove_react = "⬇" +unsure_react = "â”" diff --git a/src/main.rs b/src/main.rs index 4898db678298c7a071e2f4bc9b07a5e84bbacb27..53cb2ffd779581d0cfb3dcf5e5e3886413b27697 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,8 @@ mod config; mod user_management; mod voting; +use config::CONFIG; + macro_rules! e { ($error: literal, $x:expr) => { match $x { @@ -35,7 +37,7 @@ impl EventHandler for Handler { // Event handlers are dispatched through a threadpool, and so multiple // events can be dispatched simultaneously. fn message(&self, ctx: Context, msg: Message) { - if !(msg.content.starts_with(config::COMMAND_PREFIX)) { + if !(msg.content.starts_with(CONFIG.command_prefix)) { return; } let message_content: Vec<_> = msg.content[1..].splitn(2, ' ').collect(); @@ -63,11 +65,11 @@ impl EventHandler for Handler { let mut message = MessageBuilder::new(); message.push_line(format!( "Use {}move <action> to make a circular motion", - config::COMMAND_PREFIX + &CONFIG.command_prefix )); message.push_line(format!( "Use {}poll <proposal> to see what people think about something", - config::COMMAND_PREFIX + &CONFIG.command_prefix )); e!( "Error sending message: {:?}", @@ -79,7 +81,7 @@ impl EventHandler for Handler { "Error sending message: {:?}", msg.channel_id.say( &ctx.http, - format!("Unrecognised command. Try {}help", config::COMMAND_PREFIX) + format!("Unrecognised command. Try {}help", &CONFIG.command_prefix) ) ); } @@ -89,7 +91,7 @@ impl EventHandler for Handler { fn reaction_add(&self, ctx: Context, add_reaction: channel::Reaction) { match add_reaction.message(&ctx.http) { Ok(message) => { - if message.author.id.0 != config::BOT_ID || add_reaction.user_id == config::BOT_ID { + if message.author.id.0 != CONFIG.bot_id || add_reaction.user_id == CONFIG.bot_id { return; } match message_type(&message) { @@ -106,7 +108,7 @@ impl EventHandler for Handler { fn reaction_remove(&self, ctx: Context, removed_reaction: channel::Reaction) { match removed_reaction.message(&ctx.http) { Ok(message) => { - if message.author.id.0 != config::BOT_ID || removed_reaction.user_id == config::BOT_ID { + if message.author.id.0 != CONFIG.bot_id || removed_reaction.user_id == CONFIG.bot_id { return; } match message_type(&message) { @@ -150,6 +152,8 @@ fn main() { ), ]) .unwrap(); + + // Configure the client with your Discord bot token in the environment. let token = config::DISCORD_TOKEN; diff --git a/src/user_management.rs b/src/user_management.rs index a95d50029efe80add6f8b835afcf691d3628df1e..ea1b93b8af5b11b99c2a823f9e5d02633ecedc81 100644 --- a/src/user_management.rs +++ b/src/user_management.rs @@ -5,7 +5,7 @@ use serenity::{ utils::MessageBuilder, }; -use crate::config; +use crate::config::CONFIG; macro_rules! e { ($error: literal, $x:expr) => { @@ -23,18 +23,18 @@ pub fn new_member(ctx: &Context, mut new_member: 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"); - if let Err(why) = config::WELCOME_CHANNEL.say(&ctx, message.build()) { + if let Err(why) = CONFIG.welcome_channel.say(&ctx, message.build()) { error!("Error sending message: {:?}", why); } let mut message = MessageBuilder::new(); message.push(format!("Say hi to {} in ", new_member.display_name())); - message.mention(&config::WELCOME_CHANNEL); - if let Err(why) = config::MAIN_CHANNEL.say(&ctx, message.build()) { + message.mention(&CONFIG.welcome_channel); + if let Err(why) = CONFIG.main_channel.say(&ctx, message.build()) { error!("Error sending message: {:?}", why); } - 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); }; } @@ -44,7 +44,7 @@ impl Commands { pub fn join(ctx: Context, msg: Message, _content: &str) { e!( "Unable to get user: {:?}", - serenity::model::id::GuildId(config::SERVER_ID) + serenity::model::id::GuildId(CONFIG.server_id) .member(ctx.http.clone(), msg.author.id) .map(|member| new_member(&ctx, member)) ); @@ -61,12 +61,12 @@ impl Commands { } e!( "Unable to get member: {:?}", - serenity::model::id::GuildId(config::SERVER_ID) + serenity::model::id::GuildId(CONFIG.server_id) .member(ctx.http.clone(), msg.author.id) .map(|mut member| { e!( "Unable to remove role: {:?}", - member.remove_role(&ctx.http, config::UNREGISTERED_MEMBER_ROLE) + member.remove_role(&ctx.http, CONFIG.unregistered_member_role) ); e!( "Unable to edit nickname: {:?}", @@ -90,7 +90,7 @@ impl Commands { .map(|()| { e!( "Unable to add role: {:?}", - member.add_role(&ctx.http, config::REGISTERED_MEMBER_ROLE) + member.add_role(&ctx.http, CONFIG.registered_member_role) ); }) ); diff --git a/src/voting.rs b/src/voting.rs index 40573c3661bcd9ad6b917da83f5d17a4ad61cc94..d7dd907d6baaa335bb2d4a047a205d182f07f0a2 100644 --- a/src/voting.rs +++ b/src/voting.rs @@ -6,7 +6,7 @@ use serenity::{ use std::collections::HashMap; use std::sync::Mutex; -use crate::config; +use crate::config::CONFIG; macro_rules! e { ($error: literal, $x:expr) => { @@ -96,7 +96,7 @@ fn create_motion(ctx: &Context, msg: &Message, topic: &str) { embed.colour(serenity::utils::Colour::GOLD); embed.title(format!("Motion to {}", topic)); let mut desc = MessageBuilder::new(); - desc.role(config::VOTE_ROLE); + desc.role(CONFIG.vote_role); desc.push(" take a look at this motion from "); desc.mention(&msg.author); embed.description(desc.build()); @@ -106,11 +106,11 @@ fn create_motion(ctx: &Context, msg: &Message, topic: &str) { embed }); m.reactions(vec![ - config::FOR_VOTE, - config::AGAINST_VOTE, - config::ABSTAIN_VOTE, - config::APPROVE_REACT, - config::DISAPPROVE_REACT, + CONFIG.for_vote.to_string(), + CONFIG.against_vote.to_string(), + CONFIG.abstain_vote.to_string(), + CONFIG.approve_react.to_string(), + CONFIG.disapprove_react.to_string(), ]); m }) { @@ -144,9 +144,9 @@ fn create_poll(ctx: &Context, msg: &Message, topic: &str) { embed }); m.reactions(vec![ - config::APPROVE_REACT, - config::DISAPPROVE_REACT, - config::UNSURE_REACT, + CONFIG.approve_react.to_string(), + CONFIG.disapprove_react.to_string(), + CONFIG.unsure_react.to_string(), ]); m }) { @@ -179,18 +179,18 @@ fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo { votes: { let mut m = HashMap::new(); m.insert( - config::FOR_VOTE, - msg.reaction_users(ctx, config::FOR_VOTE, None, None) + CONFIG.for_vote, + msg.reaction_users(ctx, CONFIG.for_vote, None, None) .unwrap(), ); m.insert( - config::AGAINST_VOTE, - msg.reaction_users(ctx, config::AGAINST_VOTE, None, None) + CONFIG.against_vote, + msg.reaction_users(ctx, CONFIG.against_vote, None, None) .unwrap(), ); m.insert( - config::ABSTAIN_VOTE, - msg.reaction_users(ctx, config::ABSTAIN_VOTE, None, None) + CONFIG.abstain_vote, + msg.reaction_users(ctx, CONFIG.abstain_vote, None, None) .unwrap(), ); m @@ -217,31 +217,31 @@ fn update_motion( ) { let motion_info: MotionInfo = get_cached_motion(ctx, msg); - let for_votes = motion_info.votes.get(config::FOR_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 for_votes = motion_info.votes.get(&CONFIG.for_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 has_tiebreaker = |users: &Vec<serenity::model::user::User>| { users.iter().any(|u| { - u.has_role(ctx, config::SERVER_ID, config::TIEBREAKER_ROLE) + u.has_role(ctx, CONFIG.server_id, CONFIG.tiebreaker_role) .unwrap() }) }; let for_strength = for_votes as f32 - + (if has_tiebreaker(motion_info.votes.get(config::FOR_VOTE).unwrap()) { + + (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()) { + + (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()) { + + (if has_tiebreaker(motion_info.votes.get(&CONFIG.abstain_vote).unwrap()) { 0.25 } else { 0.0 @@ -278,7 +278,7 @@ fn update_motion( message.push(" is now "); message.push_bold(status); message.push_italic(format!(" (was {})", last_status)); - if let Err(why) = config::ANNOUNCEMENT_CHANNEL.say(&ctx.http, message.build()) { + if let Err(why) = CONFIG.announcement_channel.say(&ctx.http, message.build()) { error!("Error sending message: {:?}", why); }; } @@ -306,10 +306,10 @@ fn update_motion( .expect("No previous status") .clone() .value; - if for_strength > (config::VOTE_POOL_SIZE as f32 / 2.0) { + if for_strength > (CONFIG.vote_pool_size as f32 / 2.0) { e.colour(serenity::utils::Colour::TEAL); update_status(e, "Passed", last_status_full, &topic); - } else if against_strength + abstain_strength > (config::VOTE_POOL_SIZE as f32 / 2.0) { + } else if against_strength + abstain_strength > (CONFIG.vote_pool_size as f32 / 2.0) { e.colour(serenity::utils::Colour::RED); update_status(e, "Failed", last_status_full, &topic); } else { @@ -320,7 +320,7 @@ fn update_motion( format!( "Votes ({}/{})", for_votes + against_votes + abstain_votes, - config::VOTE_POOL_SIZE + CONFIG.vote_pool_size ), format!( "For: {}\nAgainst: {}\nAbstain: {}", @@ -344,10 +344,10 @@ pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) { match add_reaction.message(&ctx.http) { Ok(mut message) => { if let Ok(user) = add_reaction.user(&ctx) { - match user.has_role(&ctx, config::SERVER_ID, config::VOTE_ROLE) { + match user.has_role(&ctx, CONFIG.server_id, CONFIG.vote_role) { Ok(true) => { // remove vote if already voted - for react in [config::FOR_VOTE, config::AGAINST_VOTE, config::ABSTAIN_VOTE] + for react in [CONFIG.for_vote, CONFIG.against_vote, CONFIG.abstain_vote] .iter() .filter(|r| r != &&add_reaction.emoji.as_data().as_str()) { @@ -362,7 +362,7 @@ pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) { } } // remove 'illegal' reacts - if !config::ALLOWED_REACTS.contains(&add_reaction.emoji.as_data().as_str()) + if !CONFIG.allowed_reacts().contains(&add_reaction.emoji.as_data()) { if let Err(why) = add_reaction.delete(&ctx) { error!("Error deleting react: {:?}", why); @@ -382,7 +382,7 @@ pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) { update_motion(&ctx, &mut message, &user, "add", add_reaction); } Ok(false) => { - if ![config::APPROVE_REACT, config::DISAPPROVE_REACT] + if ![CONFIG.approve_react, CONFIG.disapprove_react] .contains(&add_reaction.emoji.as_data().as_str()) { if let Err(why) = add_reaction.delete(&ctx) {