@@ -15,15 +15,25 @@ GNU General Public License for more details.
1515You should have received a copy of the GNU General Public License
1616along with this program. If not, see <https://www.gnu.org/licenses/>.
1717*/
18+ /// Stores all the commands for the bot.
1819mod commands;
20+ /// Responsible for queries, models and mutation requests sent to and from
21+ /// [root's](https://www.github.com/amfoss/root) graphql interace.
22+ mod graphql;
23+ /// Stores Discord IDs that are needed across the bot.
1924mod ids;
25+ /// This module is a simple cron equivalent. It spawns threads for the regular [`Task`]s that need to be completed.
2026mod scheduler;
27+ /// An interface to define a job that needs to be executed regularly, for example checking for status updates daily.
28+ mod tasks;
29+ /// Misc. helper functions that don't really have a place anywhere else.
2130mod utils;
2231
23- use crate :: ids:: {
24- AI_ROLE_ID , ARCHIVE_MESSAGE_ID , ARCHIVE_ROLE_ID , DEVOPS_ROLE_ID , MOBILE_ROLE_ID ,
25- RESEARCH_ROLE_ID , SYSTEMS_ROLE_ID , WEB_ROLE_ID ,
32+ use ids:: {
33+ AI_ROLE_ID , ARCHIVE_ROLE_ID , DEVOPS_ROLE_ID , MOBILE_ROLE_ID , RESEARCH_ROLE_ID ,
34+ ROLES_MESSAGE_ID , SYSTEMS_ROLE_ID , WEB_ROLE_ID ,
2635} ;
36+
2737use anyhow:: Context as _;
2838use poise:: { Context as PoiseContext , Framework , FrameworkOptions , PrefixFrameworkOptions } ;
2939use serenity:: {
@@ -36,50 +46,90 @@ use std::collections::HashMap;
3646pub type Error = Box < dyn std:: error:: Error + Send + Sync > ;
3747pub type Context < ' a > = PoiseContext < ' a , Data , Error > ;
3848
49+ /// Runtime allocated storage for the bot.
3950pub struct Data {
4051 pub reaction_roles : HashMap < ReactionType , RoleId > ,
4152}
4253
54+ /// This function is responsible for allocating the necessary fields
55+ /// in [`Data`], before it is passed to the bot.
56+ ///
57+ /// Currently, it only needs to store the (emoji, [`RoleId`]) pair used
58+ /// for assigning roles to users who react to a particular message.
4359pub fn initialize_data ( ) -> Data {
4460 let mut data = Data {
4561 reaction_roles : HashMap :: new ( ) ,
4662 } ;
4763
48- let roles = [
49- ( ReactionType :: Unicode ( "📁" . to_string ( ) ) , RoleId :: new ( ARCHIVE_ROLE_ID ) ) ,
50- ( ReactionType :: Unicode ( "📱" . to_string ( ) ) , RoleId :: new ( MOBILE_ROLE_ID ) ) ,
51- ( ReactionType :: Unicode ( "⚙️" . to_string ( ) ) , RoleId :: new ( SYSTEMS_ROLE_ID ) ) ,
52- ( ReactionType :: Unicode ( "🤖" . to_string ( ) ) , RoleId :: new ( AI_ROLE_ID ) ) ,
53- ( ReactionType :: Unicode ( "📜" . to_string ( ) ) , RoleId :: new ( RESEARCH_ROLE_ID ) ) ,
54- ( ReactionType :: Unicode ( "🚀" . to_string ( ) ) , RoleId :: new ( DEVOPS_ROLE_ID ) ) ,
55- ( ReactionType :: Unicode ( "🌐" . to_string ( ) ) , RoleId :: new ( WEB_ROLE_ID ) ) ,
64+ // Define the emoji-role pairs
65+ let roles = [
66+ (
67+ ReactionType :: Unicode ( "📁" . to_string ( ) ) ,
68+ RoleId :: new ( ARCHIVE_ROLE_ID ) ,
69+ ) ,
70+ (
71+ ReactionType :: Unicode ( "📱" . to_string ( ) ) ,
72+ RoleId :: new ( MOBILE_ROLE_ID ) ,
73+ ) ,
74+ (
75+ ReactionType :: Unicode ( "⚙️" . to_string ( ) ) ,
76+ RoleId :: new ( SYSTEMS_ROLE_ID ) ,
77+ ) ,
78+ (
79+ ReactionType :: Unicode ( "🤖" . to_string ( ) ) ,
80+ RoleId :: new ( AI_ROLE_ID ) ,
81+ ) ,
82+ (
83+ ReactionType :: Unicode ( "📜" . to_string ( ) ) ,
84+ RoleId :: new ( RESEARCH_ROLE_ID ) ,
85+ ) ,
86+ (
87+ ReactionType :: Unicode ( "🚀" . to_string ( ) ) ,
88+ RoleId :: new ( DEVOPS_ROLE_ID ) ,
89+ ) ,
90+ (
91+ ReactionType :: Unicode ( "🌐" . to_string ( ) ) ,
92+ RoleId :: new ( WEB_ROLE_ID ) ,
93+ ) ,
5694 ] ;
5795
96+ // Populate reaction_roles map.
5897 data. reaction_roles
5998 . extend :: < HashMap < ReactionType , RoleId > > ( roles. into ( ) ) ;
6099
61100 data
62101}
102+
103+ /// Sets up the bot using a [`poise::Framework`], which handles most of the
104+ /// configuration including the command prefix, the event handler, the available commands,
105+ /// managing [`Data`] and running the [`scheduler`].
63106#[ shuttle_runtime:: main]
64107async fn main (
65108 #[ shuttle_runtime:: Secrets ] secret_store : shuttle_runtime:: SecretStore ,
66109) -> shuttle_serenity:: ShuttleSerenity {
110+ // Uses Shuttle's environment variable storage solution SecretStore
111+ // to access the token
67112 let discord_token = secret_store
68113 . get ( "DISCORD_TOKEN" )
69114 . context ( "'DISCORD_TOKEN' was not found" ) ?;
70115
71116 let framework = Framework :: builder ( )
72117 . options ( FrameworkOptions {
118+ // Load bot commands
73119 commands : commands:: get_commands ( ) ,
120+ // Pass the event handler function
74121 event_handler : |ctx, event, framework, data| {
75122 Box :: pin ( event_handler ( ctx, event, framework, data) )
76123 } ,
124+ // General bot settings, set to default except for prefix
77125 prefix_options : PrefixFrameworkOptions {
78126 prefix : Some ( String :: from ( "$" ) ) ,
79127 ..Default :: default ( )
80128 } ,
81129 ..Default :: default ( )
82130 } )
131+ // This function that's passed to setup() is called just as
132+ // the bot is ready to start.
83133 . setup ( |ctx, _ready, framework| {
84134 Box :: pin ( async move {
85135 poise:: builtins:: register_globally ( ctx, & framework. options ( ) . commands ) . await ?;
@@ -102,21 +152,28 @@ async fn main(
102152
103153 Ok ( client. into ( ) )
104154}
105-
155+ /// Handles various events from Discord, such as reactions.
156+ ///
157+ /// Current functionality includes:
158+ /// - Adding roles to users based on reactions.
159+ /// - Removing roles from users when their reactions are removed.
160+ ///
161+ /// TODO: Refactor for better readability and modularity.
106162async fn event_handler (
107163 ctx : & SerenityContext ,
108164 event : & FullEvent ,
109165 _framework : poise:: FrameworkContext < ' _ , Data , Error > ,
110166 data : & Data ,
111167) -> Result < ( ) , Error > {
112168 match event {
169+ // Handle reactions being added.
113170 FullEvent :: ReactionAdd { add_reaction } => {
114- let message_id = MessageId :: new ( ARCHIVE_MESSAGE_ID ) ;
115- if add_reaction. message_id == message_id
116- && data. reaction_roles . contains_key ( & add_reaction. emoji )
117- {
171+ // Check if a role needs to be added i.e check if the reaction was added to [`ROLES_MESSAGE_ID`]
172+ if is_relevant_reaction ( add_reaction. message_id , & add_reaction. emoji , data) {
173+ // This check for a guild_id isn't strictly necessary, since we're already checking
174+ // if the reaction was added to the [`ROLES_MESSAGE_ID`] which *should* point to a
175+ // message in the server.
118176 if let Some ( guild_id) = add_reaction. guild_id {
119- // TODO: Use try_join to await concurrently?
120177 if let Ok ( member) = guild_id. member ( ctx, add_reaction. user_id . unwrap ( ) ) . await {
121178 if let Err ( e) = member
122179 . add_role (
@@ -127,18 +184,21 @@ async fn event_handler(
127184 )
128185 . await
129186 {
130- eprintln ! ( "Error: {:?}" , e) ;
187+ // TODO: Replace with tracing
188+ eprintln ! ( "Error adding role: {:?}" , e) ;
131189 }
132190 }
133191 }
134192 }
135193 }
136194
195+ // Handle reactions being removed.
137196 FullEvent :: ReactionRemove { removed_reaction } => {
138- let message_id = MessageId :: new ( ARCHIVE_MESSAGE_ID ) ;
139- if message_id == removed_reaction. message_id
140- && data. reaction_roles . contains_key ( & removed_reaction. emoji )
141- {
197+ // Check if a role needs to be added i.e check if the reaction was added to [`ROLES_MESSAGE_ID`]
198+ if is_relevant_reaction ( removed_reaction. message_id , & removed_reaction. emoji , data) {
199+ // This check for a guild_id isn't strictly necessary, since we're already checking
200+ // if the reaction was added to the [`ROLES_MESSAGE_ID`] which *should* point to a
201+ // message in the server.
142202 if let Some ( guild_id) = removed_reaction. guild_id {
143203 if let Ok ( member) = guild_id
144204 . member ( ctx, removed_reaction. user_id . unwrap ( ) )
@@ -150,18 +210,26 @@ async fn event_handler(
150210 * data
151211 . reaction_roles
152212 . get ( & removed_reaction. emoji )
153- . expect ( "Hard coded value verified earlier" ) ,
213+ . expect ( "Hard coded value verified earlier. " ) ,
154214 )
155215 . await
156216 {
157- eprintln ! ( "Error: {:?}" , e) ;
217+ eprintln ! ( "Error removing role : {:?}" , e) ;
158218 }
159219 }
160220 }
161221 }
162222 }
223+
224+ // Ignore all other events for now.
163225 _ => { }
164226 }
165227
166228 Ok ( ( ) )
167229}
230+
231+ /// Helper function to check if a reaction was made to [`ROLES_MESSAGE_ID`] and if
232+ /// [`Data::reaction_roles`] contains a relevant (emoji, role) pair.
233+ fn is_relevant_reaction ( message_id : MessageId , emoji : & ReactionType , data : & Data ) -> bool {
234+ message_id == MessageId :: new ( ROLES_MESSAGE_ID ) && data. reaction_roles . contains_key ( emoji)
235+ }
0 commit comments