From 8a924d44fc57e2468a6721e6b7d97d43cd824f9f Mon Sep 17 00:00:00 2001
From: tec <tec@ucc.gu.uwa.edu.au>
Date: Sat, 4 Jul 2020 23:40:33 +0800
Subject: [PATCH] Extract main serenity logic into own file

---
 src/config.test.yml     |   2 +
 src/config.ucc.yml      |   2 +
 src/main.rs             | 264 ++--------------------------------------
 src/serenity_handler.rs | 264 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 275 insertions(+), 257 deletions(-)
 create mode 100644 src/serenity_handler.rs

diff --git a/src/config.test.yml b/src/config.test.yml
index b9bf21d..1685ad0 100644
--- a/src/config.test.yml
+++ b/src/config.test.yml
@@ -2,6 +2,7 @@ server_id: 606351521117896704 # general
 main_channel: 606351521117896706 # the-corner
 welcome_channel: 606351613816209418 # general
 announcement_channel: 606351521117896706 # the-corner
+readme_channel: 606351613816209418 # general
 
 bot_id: 607078903969742848
 
@@ -10,6 +11,7 @@ vote_role: 607478818038480937 # Vote Role
 tiebreaker_role: 607509283483025409 # tie-breaker
 unregistered_member_role: 608282247350714408 # unregistered
 registered_member_role: 608282133118582815 # registered
+expired_member_role: 607479030370926613 # registered
 command_prefix: "!"
 
 for_vote: "👍"
diff --git a/src/config.ucc.yml b/src/config.ucc.yml
index 4873de8..b9941c4 100644
--- a/src/config.ucc.yml
+++ b/src/config.ucc.yml
@@ -5,12 +5,14 @@ announcement_channel: 264411219627212801 # committee
 readme_channel: 674252245008908298 # readme
 
 bot_id: 635407267881156618
+ical_url: "https://calendar.google.com/calendar/ical/rb44is9l4dftsnk6lmf1qske6g%40group.calendar.google.com/public/basic.ics"
 
 vote_pool_size: 8 # 4 exec + Fresher rep + 3 ocm
 vote_role: 269817189966544896 # @committee
 tiebreaker_role: 0 # No tiebreak apparently 635370432568098817 # @Presiding Presidenterino
 unregistered_member_role: 674641042464833548 # @unregistered
 registered_member_role: 692754285557055490 # @member
+expired_member_role: 692754285557055490 # @member
 
 command_prefix: "!"
 
diff --git a/src/main.rs b/src/main.rs
index d26cd29..1821040 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,241 +12,27 @@ extern crate guard;
 extern crate diesel;
 extern crate ldap3;
 
+extern crate reqwest;
+
 use simplelog::*;
 use std::fs::File;
 
-use chrono::prelude::Utc;
-use serenity::{
-    model::{channel, channel::Message, gateway::Ready, guild::Member},
-    prelude::*,
-    utils::MessageBuilder,
-};
+use serenity::client::Client;
 
 #[macro_use]
 mod util;
 mod config;
 mod database;
+mod ical;
 mod ldap;
 mod reaction_roles;
+mod serenity_handler;
 mod token_management;
 mod user_management;
 mod voting;
-use rand::seq::SliceRandom;
-
-use config::{CONFIG, SECRETS};
-use reaction_roles::{add_role_by_reaction, remove_role_by_reaction};
-use util::get_string_from_react;
-
-struct Handler;
-
-pub const MENTION_RESPONSES: &[&str] = &[
-    "Oh hello there",
-    "Stop bothering me. I'm busy.",
-    "You know, I'm trying to keep track of this place. I don't need any more distractions.",
-    "Don't you have better things to do?",
-    "(sigh) what now?",
-    "Yes, yes, I know I'm brilliant",
-    "What do I need to do to catch a break around here? Eh.",
-    "Mmmmhmmm. I'm still around, don't mind me.",
-    "You know, some people would consider this rude. Luckily I'm not one of those people. In fact, I'm not even a person.",
-    "Perhaps try bothering someone else for a change."
-];
-
-impl EventHandler for Handler {
-    // Set a handler for the `message` event - so that whenever a new message
-    // is received - the closure (or function) passed will be called.
-    //
-    // 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.contains(&format!("<@!{}>", CONFIG.bot_id)) // desktop mentions
-                || msg.content.contains(&format!("<@{}>", CONFIG.bot_id))
-            // mobile mentions
-            {
-                send_message!(
-                    msg.channel_id,
-                    &ctx.http,
-                    MENTION_RESPONSES
-                        .choose(&mut rand::thread_rng())
-                        .expect("We couldn't get any sass")
-                );
-            }
-            return;
-        }
-        let message_content: Vec<_> = msg.content[1..].splitn(2, ' ').collect();
-        let content = if message_content.len() > 1 {
-            message_content[1]
-        } else {
-            ""
-        };
-        match message_content[0] {
-            "say" => println!("{:#?}", msg.content),
-            "register" => user_management::Commands::register(ctx, msg.clone(), content),
-            "verify" => user_management::Commands::verify(ctx, msg.clone(), content),
-            "profile" => user_management::Commands::profile(ctx, msg.clone(), content),
-            "set" => user_management::Commands::set_info(ctx, msg.clone(), content),
-            "clear" => user_management::Commands::clear_info(ctx, msg.clone(), content),
-            "move" => voting::Commands::move_something(ctx, msg.clone(), content),
-            "motion" => voting::Commands::motion(ctx, msg.clone(), content),
-            "poll" => voting::Commands::poll(ctx, msg.clone(), content),
-            "cowsay" => voting::Commands::cowsay(ctx, msg.clone(), content),
-            "source" => {
-                let mut mesg = MessageBuilder::new();
-                mesg.push(
-                    "You want to look at my insides!? Eurgh.\nJust kidding, you can go over ",
-                );
-                mesg.push_italic("every inch");
-                mesg.push(" of me here: https://gitlab.ucc.asn.au/UCC/discord-bot 😉");
-                send_message!(msg.channel_id, &ctx.http, mesg.build());
-            }
-            "help" => {
-                // Plaintext version, keep in case IRC users kick up a fuss
-                // let mut message = MessageBuilder::new();
-                // message.push_line(format!(
-                //     "Use {}move <action> to make a circular motion",
-                //     &CONFIG.command_prefix
-                // ));
-                // message.push_line(format!(
-                //     "Use {}poll <proposal> to see what people think about something",
-                //     &CONFIG.command_prefix
-                // ));
-                // send_message!(msg.channel_id, &ctx.http, message.build());
-
-                let result = msg.channel_id.send_message(&ctx.http, |m| {
-                    m.embed(|embed| {
-                        embed.colour(serenity::utils::Colour::DARK_GREY);
-                        embed.title("Commands for the UCC Bot");
-                        embed.field("About", "This is UCC's own little in-house bot, please treat it nicely :)", false);
-                        embed.field("Commitee", "`!move <text>` to make a circular motion\n\
-                                                 `!poll <text>` to get people's opinions on something", false);
-                        embed.field("Account", "`!register <ucc username>` to link your Discord and UCC account\n\
-                                                `!profile <user>` to get the profile of a user\n\
-                                                `!set <bio|git|web|photo>` to set that property of _your_ profile\n\
-                                                `!updateroles` to update your registered roles", false);
-                        embed.field("Fun", "`!cowsay <text>` to have a cow say your words\n\
-                                            with no `<text>` it'll give you a fortune 😉", false);
-                        embed
-                    });
-                    m
-                });
-                if let Err(why) = result {
-                    error!("Error sending help embed: {:?}", why);
-                }
-            }
-            // undocumented (in !help) functins
-            "logreact" => {
-                e!("Error deleting logreact prompt: {:?}", msg.delete(&ctx));
-                send_message!(
-                    msg.channel_id,
-                    &ctx.http,
-                    "React to this to log the ID (for the next 5min)"
-                );
-            }
-            "ldap" => send_message!(
-                msg.channel_id,
-                &ctx.http,
-                format!("{:?}", ldap::ldap_search(message_content[1]))
-            ),
-            "tla" => send_message!(
-                msg.channel_id,
-                &ctx.http,
-                format!("{:?}", ldap::tla_search(message_content[1]))
-            ),
-            "updateroles" => user_management::Commands::update_registered_role(ctx, msg),
-            _ => send_message!(
-                msg.channel_id,
-                &ctx.http,
-                format!("Unrecognised command. Try {}help", &CONFIG.command_prefix)
-            ),
-        }
-    }
-
-    fn reaction_add(&self, ctx: Context, add_reaction: channel::Reaction) {
-        match add_reaction.message(&ctx.http) {
-            Ok(message) => match get_message_type(&message) {
-                MessageType::RoleReactMessage if add_reaction.user_id.0 != CONFIG.bot_id => {
-                    add_role_by_reaction(&ctx, message, add_reaction);
-                    return;
-                }
-                _ if message.author.id.0 != CONFIG.bot_id
-                    || add_reaction.user_id == CONFIG.bot_id =>
-                {
-                    return
-                }
-                MessageType::Motion => voting::reaction_add(ctx, add_reaction),
-                MessageType::LogReact => {
-                    let react_user = add_reaction.user(&ctx).unwrap();
-                    let react_as_string = get_string_from_react(&add_reaction.emoji);
-                    if Utc::now().timestamp() - message.timestamp.timestamp() > 300 {
-                        warn!(
-                            "The logreact message {} just tried to use is too old",
-                            react_user.name
-                        );
-                        return;
-                    }
-                    info!(
-                        "The react {} just added is {:?}. In full: {:?}",
-                        react_user.name, react_as_string, add_reaction.emoji
-                    );
-                    let mut msg = MessageBuilder::new();
-                    msg.push_italic(react_user.name);
-                    msg.push(format!(
-                        " wanted to know that {} is represented by ",
-                        add_reaction.emoji,
-                    ));
-                    msg.push_mono(react_as_string);
-                    send_message!(message.channel_id, &ctx.http, msg.build());
-                }
-                _ => {}
-            },
-            Err(why) => error!("Failed to get react message {:?}", why),
-        }
-    }
-
-    fn reaction_remove(&self, ctx: Context, removed_reaction: channel::Reaction) {
-        match removed_reaction.message(&ctx.http) {
-            Ok(message) => match get_message_type(&message) {
-                MessageType::RoleReactMessage if removed_reaction.user_id != CONFIG.bot_id => {
-                    remove_role_by_reaction(&ctx, message, removed_reaction);
-                    return;
-                }
-                _ if message.author.id.0 != CONFIG.bot_id
-                    || removed_reaction.user_id == CONFIG.bot_id =>
-                {
-                    return
-                }
-                MessageType::Motion => voting::reaction_remove(ctx, removed_reaction),
-                _ => {}
-            },
-            Err(why) => error!("Failed to get react message {:?}", why),
-        }
-    }
-
-    fn guild_member_addition(
-        &self,
-        ctx: Context,
-        _guild_id: serenity::model::id::GuildId,
-        the_new_member: Member,
-    ) {
-        user_management::new_member(&ctx, the_new_member);
-    }
 
-    // Set a handler to be called on the `ready` event. This is called when a
-    // shard is booted, and a READY payload is sent by Discord. This payload
-    // contains data like the current user's guild Ids, current user data,
-    // private channels, and more.
-    //
-    // In this case, just print what the current user's username is.
-    fn ready(&self, ctx: Context, ready: Ready) {
-        info!("{} is connected!", ready.user.name);
-        reaction_roles::sync_all_role_reactions(&ctx);
-    }
-
-    fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) {
-        reaction_roles::sync_all_role_reactions(&ctx);
-    }
-}
+use config::SECRETS;
+use serenity_handler::Handler;
 
 fn main() {
     CombinedLogger::init(vec![
@@ -272,39 +58,3 @@ fn main() {
         error!("Client error: {:?}", why);
     }
 }
-
-#[derive(Debug, PartialEq)]
-enum MessageType {
-    Motion,
-    Role,
-    RoleReactMessage,
-    LogReact,
-    Poll,
-    Misc,
-}
-
-fn get_message_type(message: &Message) -> MessageType {
-    if CONFIG
-        .react_role_messages
-        .iter()
-        .any(|rrm| rrm.message == message.id)
-    {
-        return MessageType::RoleReactMessage;
-    }
-    if message.embeds.is_empty() {
-        // Get first word of message
-        return match message.content.splitn(2, ' ').next().unwrap() {
-            "Role" => MessageType::Role,
-            "React" => MessageType::LogReact,
-            _ => MessageType::Misc,
-        };
-    }
-    let title: String = message.embeds[0].title.clone().unwrap();
-    let words_of_title: Vec<_> = title.splitn(2, ' ').collect();
-    let first_word_of_title = words_of_title[0];
-    match first_word_of_title {
-        "Motion" => MessageType::Motion,
-        "Poll" => MessageType::Poll,
-        _ => MessageType::Misc,
-    }
-}
diff --git a/src/serenity_handler.rs b/src/serenity_handler.rs
new file mode 100644
index 0000000..fc8022f
--- /dev/null
+++ b/src/serenity_handler.rs
@@ -0,0 +1,264 @@
+use chrono::prelude::Utc;
+use serenity::{
+    model::{channel, channel::Message, gateway::Ready, guild::Member},
+    prelude::*,
+    utils::MessageBuilder,
+};
+
+use rand::seq::SliceRandom;
+
+use crate::config::CONFIG;
+use crate::ldap;
+use crate::reaction_roles::{
+    add_role_by_reaction, remove_role_by_reaction, sync_all_role_reactions,
+};
+use crate::user_management;
+use crate::util::get_string_from_react;
+use crate::voting;
+
+pub struct Handler;
+
+impl EventHandler for Handler {
+    // Set a handler for the `message` event - so that whenever a new message
+    // is received - the closure (or function) passed will be called.
+    //
+    // 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.contains(&format!("<@!{}>", CONFIG.bot_id)) // desktop mentions
+                || msg.content.contains(&format!("<@{}>", CONFIG.bot_id))
+            // mobile mentions
+            {
+                send_message!(
+                    msg.channel_id,
+                    &ctx.http,
+                    MENTION_RESPONSES
+                        .choose(&mut rand::thread_rng())
+                        .expect("We couldn't get any sass")
+                );
+            }
+            return;
+        }
+        let message_content: Vec<_> = msg.content[1..].splitn(2, ' ').collect();
+        let content = if message_content.len() > 1 {
+            message_content[1]
+        } else {
+            ""
+        };
+        match message_content[0] {
+            "say" => println!("{:#?}", msg.content),
+            "register" => user_management::Commands::register(ctx, msg.clone(), content),
+            "verify" => user_management::Commands::verify(ctx, msg.clone(), content),
+            "profile" => user_management::Commands::profile(ctx, msg.clone(), content),
+            "set" => user_management::Commands::set_info(ctx, msg.clone(), content),
+            "clear" => user_management::Commands::clear_info(ctx, msg.clone(), content),
+            "move" => voting::Commands::move_something(ctx, msg.clone(), content),
+            "motion" => voting::Commands::motion(ctx, msg.clone(), content),
+            "poll" => voting::Commands::poll(ctx, msg.clone(), content),
+            "cowsay" => voting::Commands::cowsay(ctx, msg.clone(), content),
+            "source" => {
+                let mut mesg = MessageBuilder::new();
+                mesg.push(
+                    "You want to look at my insides!? Eurgh.\nJust kidding, you can go over ",
+                );
+                mesg.push_italic("every inch");
+                mesg.push(" of me here: https://gitlab.ucc.asn.au/UCC/discord-bot 😉");
+                send_message!(msg.channel_id, &ctx.http, mesg.build());
+            }
+            "help" => {
+                // Plaintext version, keep in case IRC users kick up a fuss
+                // let mut message = MessageBuilder::new();
+                // message.push_line(format!(
+                //     "Use {}move <action> to make a circular motion",
+                //     &CONFIG.command_prefix
+                // ));
+                // message.push_line(format!(
+                //     "Use {}poll <proposal> to see what people think about something",
+                //     &CONFIG.command_prefix
+                // ));
+                // send_message!(msg.channel_id, &ctx.http, message.build());
+
+                let result = msg.channel_id.send_message(&ctx.http, |m| {
+                    m.embed(|embed| {
+                        embed.colour(serenity::utils::Colour::DARK_GREY);
+                        embed.title("Commands for the UCC Bot");
+                        embed.field("About", "This is UCC's own little in-house bot, please treat it nicely :)", false);
+                        embed.field("Commitee", "`!move <text>` to make a circular motion\n\
+                                                 `!poll <text>` to get people's opinions on something", false);
+                        embed.field("Account", "`!register <ucc username>` to link your Discord and UCC account\n\
+                                                `!profile <user>` to get the profile of a user\n\
+                                                `!set <bio|git|web|photo>` to set that property of _your_ profile\n\
+                                                `!updateroles` to update your registered roles", false);
+                        embed.field("Fun", "`!cowsay <text>` to have a cow say your words\n\
+                                            with no `<text>` it'll give you a fortune 😉", false);
+                        embed
+                    });
+                    m
+                });
+                if let Err(why) = result {
+                    error!("Error sending help embed: {:?}", why);
+                }
+            }
+            // undocumented (in !help) functins
+            "logreact" => {
+                e!("Error deleting logreact prompt: {:?}", msg.delete(&ctx));
+                send_message!(
+                    msg.channel_id,
+                    &ctx.http,
+                    "React to this to log the ID (for the next 5min)"
+                );
+            }
+            "ldap" => send_message!(
+                msg.channel_id,
+                &ctx.http,
+                format!("{:?}", ldap::ldap_search(message_content[1]))
+            ),
+            "tla" => send_message!(
+                msg.channel_id,
+                &ctx.http,
+                format!("{:?}", ldap::tla_search(message_content[1]))
+            ),
+            "updateroles" => user_management::Commands::update_registered_role(ctx, msg),
+            _ => send_message!(
+                msg.channel_id,
+                &ctx.http,
+                format!("Unrecognised command. Try {}help", &CONFIG.command_prefix)
+            ),
+        }
+    }
+
+    fn reaction_add(&self, ctx: Context, add_reaction: channel::Reaction) {
+        match add_reaction.message(&ctx.http) {
+            Ok(message) => match get_message_type(&message) {
+                MessageType::RoleReactMessage if add_reaction.user_id.0 != CONFIG.bot_id => {
+                    add_role_by_reaction(&ctx, message, add_reaction);
+                    return;
+                }
+                _ if message.author.id.0 != CONFIG.bot_id
+                    || add_reaction.user_id == CONFIG.bot_id =>
+                {
+                    return
+                }
+                MessageType::Motion => voting::reaction_add(ctx, add_reaction),
+                MessageType::LogReact => {
+                    let react_user = add_reaction.user(&ctx).unwrap();
+                    let react_as_string = get_string_from_react(&add_reaction.emoji);
+                    if Utc::now().timestamp() - message.timestamp.timestamp() > 300 {
+                        warn!(
+                            "The logreact message {} just tried to use is too old",
+                            react_user.name
+                        );
+                        return;
+                    }
+                    info!(
+                        "The react {} just added is {:?}. In full: {:?}",
+                        react_user.name, react_as_string, add_reaction.emoji
+                    );
+                    let mut msg = MessageBuilder::new();
+                    msg.push_italic(react_user.name);
+                    msg.push(format!(
+                        " wanted to know that {} is represented by ",
+                        add_reaction.emoji,
+                    ));
+                    msg.push_mono(react_as_string);
+                    send_message!(message.channel_id, &ctx.http, msg.build());
+                }
+                _ => {}
+            },
+            Err(why) => error!("Failed to get react message {:?}", why),
+        }
+    }
+
+    fn reaction_remove(&self, ctx: Context, removed_reaction: channel::Reaction) {
+        match removed_reaction.message(&ctx.http) {
+            Ok(message) => match get_message_type(&message) {
+                MessageType::RoleReactMessage if removed_reaction.user_id != CONFIG.bot_id => {
+                    remove_role_by_reaction(&ctx, message, removed_reaction);
+                    return;
+                }
+                _ if message.author.id.0 != CONFIG.bot_id
+                    || removed_reaction.user_id == CONFIG.bot_id =>
+                {
+                    return
+                }
+                MessageType::Motion => voting::reaction_remove(ctx, removed_reaction),
+                _ => {}
+            },
+            Err(why) => error!("Failed to get react message {:?}", why),
+        }
+    }
+
+    fn guild_member_addition(
+        &self,
+        ctx: Context,
+        _guild_id: serenity::model::id::GuildId,
+        the_new_member: Member,
+    ) {
+        user_management::new_member(&ctx, the_new_member);
+    }
+
+    // Set a handler to be called on the `ready` event. This is called when a
+    // shard is booted, and a READY payload is sent by Discord. This payload
+    // contains data like the current user's guild Ids, current user data,
+    // private channels, and more.
+    //
+    // In this case, just print what the current user's username is.
+    fn ready(&self, ctx: Context, ready: Ready) {
+        info!("{} is connected!", ready.user.name);
+        sync_all_role_reactions(&ctx);
+    }
+
+    fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) {
+        sync_all_role_reactions(&ctx);
+    }
+}
+
+pub const MENTION_RESPONSES: &[&str] = &[
+    "Oh hello there",
+    "Stop bothering me. I'm busy.",
+    "You know, I'm trying to keep track of this place. I don't need any more distractions.",
+    "Don't you have better things to do?",
+    "(sigh) what now?",
+    "Yes, yes, I know I'm brilliant",
+    "What do I need to do to catch a break around here? Eh.",
+    "Mmmmhmmm. I'm still around, don't mind me.",
+    "You know, some people would consider this rude. Luckily I'm not one of those people. In fact, I'm not even a person.",
+    "Perhaps try bothering someone else for a change."
+];
+
+#[derive(Debug, PartialEq)]
+enum MessageType {
+    Motion,
+    Role,
+    RoleReactMessage,
+    LogReact,
+    Poll,
+    Misc,
+}
+
+fn get_message_type(message: &Message) -> MessageType {
+    if CONFIG
+        .react_role_messages
+        .iter()
+        .any(|rrm| rrm.message == message.id)
+    {
+        return MessageType::RoleReactMessage;
+    }
+    if message.embeds.is_empty() {
+        // Get first word of message
+        return match message.content.splitn(2, ' ').next().unwrap() {
+            "Role" => MessageType::Role,
+            "React" => MessageType::LogReact,
+            _ => MessageType::Misc,
+        };
+    }
+    let title: String = message.embeds[0].title.clone().unwrap();
+    let words_of_title: Vec<_> = title.splitn(2, ' ').collect();
+    let first_word_of_title = words_of_title[0];
+    match first_word_of_title {
+        "Motion" => MessageType::Motion,
+        "Poll" => MessageType::Poll,
+        _ => MessageType::Misc,
+    }
+}
-- 
GitLab