From 4a182090ffa51a55194dd92cd5ba8ca467cca067 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Mon, 27 Apr 2020 17:13:23 -0500 Subject: [PATCH] Added better error handling and help messages --- src/database/models.rs | 15 +++++- src/main.rs | 109 ++++++++++++++++++++++++++++++----------- 2 files changed, 95 insertions(+), 29 deletions(-) diff --git a/src/database/models.rs b/src/database/models.rs index 98e2604..a560b80 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -1,7 +1,7 @@ use super::schema::events; use chrono::NaiveDateTime; -#[derive(Queryable)] +#[derive(Queryable, Clone)] pub struct Event { /// Event ID pub id: i32, @@ -19,6 +19,19 @@ pub struct Event { pub reminder_sent: i32, } +impl Into for Event { + fn into(self) -> NewEvent { + NewEvent { + event_name: self.event_name.clone(), + event_desc: self.event_desc.clone(), + event_time: self.event_time.clone(), + message_id: self.message_id.clone(), + thumbnail_link: self.message_id.clone(), + reminder_sent: self.reminder_sent, + } + } +} + #[derive(Insertable, Clone)] #[table_name = "events"] pub struct NewEvent { diff --git a/src/main.rs b/src/main.rs index 0186b74..e71b4ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ use chrono::{DateTime, Datelike, NaiveDateTime, TimeZone, Timelike, Utc}; use chrono_tz::Tz; use clap::{App, Arg}; use serenity::client::Client; -use serenity::framework::standard::macros::{command, group}; -use serenity::framework::standard::Args; +use serenity::framework::standard::macros::{command, group, help}; +use serenity::framework::standard::{help_commands, Args, CommandGroup, HelpOptions}; use serenity::framework::standard::{CommandError, CommandResult, StandardFramework}; use serenity::http::Http; use serenity::model::channel::{Message, Reaction}; @@ -31,14 +31,18 @@ mod hypebot_config; use crate::database::models::NewEvent; use database::*; use hypebot_config::HypeBotConfig; +use serenity::model::id::UserId; use serenity::model::user::User; +use std::collections::HashSet; const INTERESTED_EMOJI: &str = "\u{2705}"; const UNINTERESTED_EMOJI: &str = "\u{274C}"; /// Event commands group #[group] -#[commands(create_event, confirm_event, cancel_event)] +#[only_in(guilds)] +#[description("Commands for Creating Events")] +#[commands(create, confirm, cancel)] struct EventCommands; /// Struct for storing drafted events @@ -83,6 +87,20 @@ impl EventHandler for Handler { } } +#[help] +#[command_not_found_text = "Could not find: `{}`."] +#[strikethrough_commands_tip_in_guild("HypeBot")] +fn bot_help( + context: &mut Context, + msg: &Message, + args: Args, + help_options: &'static HelpOptions, + groups: &[&'static CommandGroup], + owners: HashSet, +) -> CommandResult { + help_commands::with_embeds(context, msg, args, help_options, groups, owners) +} + /// Thread to send reminders to users fn send_reminders(cache_and_http: &Arc, data: &Arc>) { let sleep_duration = Duration::from_secs(60); @@ -184,9 +202,10 @@ fn send_event_msg( e.title(event.event_name.clone()) .color(Colour::PURPLE) .description(format!( - "**{}**\n{}", + "**{}**\n{}\n\nReact with {} below to receive event reminders!", native_time.format("%A, %B %d @ %I:%M %P %t %Z"), - event.event_desc + event.event_desc, + INTERESTED_EMOJI )) .thumbnail(event.thumbnail_link.clone()) .footer(|f| f.text("Local Event Time")) @@ -238,7 +257,7 @@ fn send_draft_event(ctx: &Context, channel: ChannelId) -> CommandResult { channel.send_message(&ctx, |m| { m.content(format!( - "Draft message, use the `confirm_event` command to post it." + "Draft message, use the `confirm` command to post it." )) })?; send_event_msg(&ctx.http, config, channel.0, &draft_event.event, false)?; @@ -276,32 +295,28 @@ fn permission_check(ctx: &mut Context, msg: &Message, _command_name: &str) -> bo } #[command] -/// Posts the pending event in the shared context -fn confirm_event(ctx: &mut Context, msg: &Message, _args: Args) -> CommandResult { +/// Posts a previewed event +/// +/// **Note** +/// You can only post events you have created. Only one preview event can exist at a time. +fn confirm(ctx: &mut Context, msg: &Message, _args: Args) -> CommandResult { let config = get_config(&ctx.data)?; let data = ctx.data.read(); // Get draft event if let Some(draft_event) = data.get::() { - let new_event = &draft_event.event; + let mut new_event = draft_event.event.clone(); // Check to to see if message author is the owner of the pending event if draft_event.creator_id == msg.author.id.0 { // Send event message let event_msg = - send_event_msg(&ctx.http, &config, config.event_channel, new_event, true)?; + send_event_msg(&ctx.http, &config, config.event_channel, &new_event, true)?; msg.reply(&ctx, "Event posted!")?; - let new_event = NewEvent { - message_id: event_msg.id.0.to_string(), - event_time: new_event.event_time.clone(), - event_desc: new_event.event_desc.clone(), - event_name: new_event.event_name.clone(), - thumbnail_link: new_event.event_name.clone(), - reminder_sent: 0, - }; + new_event.message_id = event_msg.id.0.to_string(); - insert_event(config.db_url.clone(), &new_event).ok(); + insert_event(config.db_url.clone(), &new_event)?; } else { msg.reply(&ctx, format!("You do not have a pending event!"))?; } @@ -313,8 +328,16 @@ fn confirm_event(ctx: &mut Context, msg: &Message, _args: Args) -> CommandResult } #[command] -/// Creates an event and announce it -fn create_event(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { +/// Creates an event and previews the announcement +/// +/// `~create "event name" "04:20pm 2069-04-20" "event description" \` +/// +/// **Time format** +/// The time format is HH:MMam YYYY-MM-DD +/// +/// **Thumbnail Link** +/// The thumbnail link is optional, if one is not provided, a default image is shown +fn create(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { // Get config let config = get_config(&ctx.data)?; let guild_id = msg @@ -322,9 +345,27 @@ fn create_event(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResu .ok_or(CommandError("Unable to get guild ID".to_string()))?; // Parse args - let event_name = args.single::()?.replace("\"", ""); - let date_string = args.single::()?.replace("\"", ""); - let description = args.single::()?.replace("\"", ""); + let event_name = match args.single::() { + Ok(event_name) => event_name.replace("\"", ""), + Err(_) => { + msg.reply(&ctx, "No event name provided.".to_string())?; + return Ok(()); + } + }; + let date_string = match args.single::() { + Ok(date_string) => date_string.replace("\"", ""), + Err(_) => { + msg.reply(&ctx, "No date provided.".to_string())?; + return Ok(()); + } + }; + let description = match args.single::() { + Ok(desc) => desc.replace("\"", ""), + Err(_) => { + msg.reply(&ctx, "No description provided.".to_string())?; + return Ok(()); + } + }; let thumbnail_link = match args.single::() { Ok(link) => link.replace("<", "").replace(">", ""), Err(_) => config.default_thumbnail_link.clone(), @@ -332,7 +373,16 @@ fn create_event(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResu // Parse date let tz: Tz = config.event_timezone; - let input_date = NaiveDateTime::parse_from_str(date_string.as_str(), "%I:%M%p %Y-%m-%d")?; + let input_date = match NaiveDateTime::parse_from_str(date_string.as_str(), "%I:%M%P %Y-%m-%d") { + Ok(date) => date, + Err(_) => { + msg.reply( + &ctx, + "Invalid date format. Format is HH:MMam YYYY-MM-DD".to_string(), + )?; + return Ok(()); + } + }; let input_date = tz .ymd( @@ -373,8 +423,10 @@ fn create_event(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResu } #[command] -/// Cancels an event -fn cancel_event(ctx: &mut Context, _msg: &Message, mut args: Args) -> CommandResult { +/// Cancels an already scheduled event +/// +/// `~create_event "event name"` +fn cancel(ctx: &mut Context, _msg: &Message, mut args: Args) -> CommandResult { let config = get_config(&ctx.data)?; // Parse args @@ -447,7 +499,8 @@ fn main() -> clap::Result<()> { .ignore_webhooks(true) }) .before(permission_check) - .group(&EVENTCOMMANDS_GROUP), + .group(&EVENTCOMMANDS_GROUP) + .help(&BOT_HELP), ); // Copy config data to client data