mod.rs
1 use std::{borrow::Cow, sync::Arc}; 2 3 use matrix_sdk::{Room, ruma::events::room::message::OriginalRoomMessageEvent}; 4 5 use crate::{ 6 Context, 7 aoc::day::AocDay, 8 config::Config, 9 matrix::utils::{RoomExt, error_message, message}, 10 }; 11 12 pub mod admin; 13 pub mod aoc; 14 mod parser; 15 mod parser_ext; 16 17 pub async fn handle( 18 event: &OriginalRoomMessageEvent, 19 room: Room, 20 context: Arc<Context>, 21 cmd: &str, 22 ) -> anyhow::Result<()> { 23 let cmd = parser::parse(cmd); 24 25 if let Err(err) = match &*cmd.command { 26 // Advent of Code 27 "join" => aoc::join::invoke(event, &room, &context).await, 28 "leaderboard" | "lb" => aoc::leaderboard::invoke(event, &room, &context, cmd).await, 29 "day" => aoc::day::invoke(event, &room, &context, cmd).await, 30 "user" => aoc::user::invoke(event, &room, &context, cmd).await, 31 "solutions" | "repos" => aoc::solutions::invoke(event, &room, &context).await, 32 "clear-cache" | "cc" => aoc::clear_cache::invoke(event, &room, &context).await, 33 34 // General 35 "ping" => ping(event, &room).await, 36 "help" => Ok(help(event, &room, &context.config).await?), 37 38 // Administration 39 "op" => admin::op(event, &room, &context.config, cmd).await, 40 41 _ => unknown_command(event, &room).await, 42 } { 43 err.send(&room, event).await?; 44 if let CommandError::Other(err) = err { 45 return Err(err); 46 } 47 } 48 49 Ok(()) 50 } 51 52 pub async fn help( 53 event: &OriginalRoomMessageEvent, 54 room: &Room, 55 config: &Config, 56 ) -> anyhow::Result<()> { 57 let prefix = &config.matrix.command_prefix; 58 59 let default_day = AocDay::current() 60 .map(|d| format!("={}", d.day)) 61 .unwrap_or_default(); 62 let default_year = AocDay::most_recent().year; 63 let default_rows = config.aoc.leaderboard_rows; 64 let content = format!( 65 r#" 66 ### AoC-Bot Commands 67 68 #### Advent of Code 69 - `{prefix}join` - Request instructions to join the private leaderboard 70 - `{prefix}leaderboard [year={default_year}] [rows={default_rows}] [offset=0]` - Show the given slice of the private leaderboard 71 - `{prefix}day [day{default_day}] [year={default_year}] [p=1|2|both] [rows={default_rows}] [offset=0]` - Show the given slice of the daily private leaderboard 72 - `{prefix}user [user] [year={default_year}]` - Show statistics of the given user 73 - `{prefix}solutions` - Show the list of solution repositories 74 - `{prefix}clear-cache` - Clear the leaderboard cache (admin only) 75 76 #### General 77 - `{prefix}ping` - Check bot health 78 - `{prefix}help` - Show this help message 79 "# 80 ); 81 82 room.reply_to(event, message(content)).await?; 83 Ok(()) 84 } 85 86 async fn unknown_command(event: &OriginalRoomMessageEvent, room: &Room) -> CommandResult<()> { 87 room.reply_to( 88 event, 89 error_message("Unknown command. Send `!help` for a list of available commands."), 90 ) 91 .await?; 92 Ok(()) 93 } 94 95 pub async fn ping(event: &OriginalRoomMessageEvent, room: &Room) -> CommandResult<()> { 96 room.reply_to(event, message("Pong!")).await?; 97 Ok(()) 98 } 99 100 #[derive(Debug)] 101 pub enum CommandError { 102 ArgRequired(&'static str), 103 ArgParse { 104 arg: &'static str, 105 allowed_values: Option<Cow<'static, str>>, 106 }, 107 PermissionDenied, 108 Other(anyhow::Error), 109 } 110 111 type CommandResult<T> = Result<T, CommandError>; 112 113 impl std::fmt::Display for CommandError { 114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 115 match self { 116 Self::ArgRequired(arg) => write!(f, "Argument '{arg}' is required")?, 117 Self::ArgParse { 118 arg, 119 allowed_values, 120 } => { 121 write!(f, "Failed to parse argument '{arg}'.")?; 122 if let Some(allowed_values) = allowed_values { 123 write!(f, " Allowed values: {allowed_values}")?; 124 } 125 } 126 Self::PermissionDenied => write!(f, "Permission denied")?, 127 Self::Other(_) => write!(f, "Internal error")?, 128 } 129 130 Ok(()) 131 } 132 } 133 134 impl CommandError { 135 async fn send(&self, room: &Room, event: &OriginalRoomMessageEvent) -> anyhow::Result<()> { 136 room.reply_to(event, error_message(self.to_string())) 137 .await?; 138 Ok(()) 139 } 140 } 141 142 impl<E> From<E> for CommandError 143 where 144 E: Into<anyhow::Error>, 145 { 146 fn from(value: E) -> Self { 147 Self::Other(value.into()) 148 } 149 }