From b7fd89e8610799f627b5fa3494a28ed461533b80 Mon Sep 17 00:00:00 2001 From: jude Date: Tue, 8 Sep 2020 23:08:02 +0100 Subject: [PATCH] timer command. added create.sql file with changes to reflect new version --- .idea/sqldialects.xml | 7 + Cargo.lock | 1 + Cargo.toml | 1 + create.sql | 234 ++++++++++++++++++++++++++++++++++ src/commands/reminder_cmds.rs | 115 +++++++++++++++-- src/main.rs | 3 + src/models.rs | 39 ++++++ 7 files changed, 391 insertions(+), 9 deletions(-) create mode 100644 .idea/sqldialects.xml create mode 100644 create.sql diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..ea20568 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0772970..e98f3d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1121,6 +1121,7 @@ dependencies = [ "dotenv", "lazy_static", "log", + "num-integer", "regex", "regex_command_attr", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index f7871d1..2197e41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4.11" chrono = "0.4" chrono-tz = "0.5" lazy_static = "1.4.0" +num-integer = "0.1.43" [dependencies.regex_command_attr] path = "./regex_command_attr" diff --git a/create.sql b/create.sql new file mode 100644 index 0000000..7b5c84c --- /dev/null +++ b/create.sql @@ -0,0 +1,234 @@ +SET FOREIGN_KEY_CHECKS=0; + +USE reminders; + +CREATE TABLE reminders.guilds ( + id INT UNSIGNED UNIQUE NOT NULL AUTO_INCREMENT, + guild BIGINT UNSIGNED UNIQUE NOT NULL, + + name VARCHAR(100), + + prefix VARCHAR(5) DEFAULT '$' NOT NULL, + timezone VARCHAR(32) DEFAULT 'UTC' NOT NULL, + + default_channel_id INT UNSIGNED, + default_username VARCHAR(32) DEFAULT 'Reminder' NOT NULL, + default_avatar VARCHAR(512) DEFAULT 'https://raw.githubusercontent.com/reminder-bot/logos/master/Remind_Me_Bot_Logo_PPic.jpg' NOT NULL, + + PRIMARY KEY (id), + FOREIGN KEY (default_channel_id) REFERENCES reminders.channels(id) ON DELETE SET NULL +); + +CREATE TABLE reminders.channels ( + id INT UNSIGNED UNIQUE NOT NULL AUTO_INCREMENT, + channel BIGINT UNSIGNED UNIQUE NOT NULL, + + name VARCHAR(100), + + nudge SMALLINT NOT NULL DEFAULT 0, + blacklisted BOOL NOT NULL DEFAULT FALSE, + + webhook_id BIGINT UNSIGNED UNIQUE, + webhook_token TEXT, + + paused BOOL NOT NULL DEFAULT 0, + paused_until TIMESTAMP, + + guild_id INT UNSIGNED, + + PRIMARY KEY (id), + FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE +); + +CREATE TABLE reminders.users ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + user BIGINT UNSIGNED UNIQUE NOT NULL, + + name VARCHAR(37) NOT NULL, + + dm_channel INT UNSIGNED UNIQUE NOT NULL, + + language VARCHAR(2) DEFAULT 'EN' NOT NULL, + timezone VARCHAR(32), # nullable s.t it can default to server timezone + allowed_dm BOOLEAN DEFAULT 1 NOT NULL, + + patreon BOOL NOT NULL DEFAULT 0, + + PRIMARY KEY (id), + FOREIGN KEY (dm_channel) REFERENCES reminders.channels(id) ON DELETE RESTRICT +); + +CREATE TABLE reminders.roles ( + id INT UNSIGNED UNIQUE NOT NULL AUTO_INCREMENT, + role BIGINT UNSIGNED UNIQUE NOT NULL, + + name VARCHAR(100), + + guild_id INT UNSIGNED NOT NULL, + + PRIMARY KEY (id), + FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE +); + +CREATE TABLE reminders.embeds ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + + title VARCHAR(256) NOT NULL DEFAULT '', + description VARCHAR(2048) NOT NULL DEFAULT '', + + image_url VARCHAR(512), + thumbnail_url VARCHAR(512), + + footer VARCHAR(2048) NOT NULL DEFAULT '', + footer_icon VARCHAR(512), + + color MEDIUMINT UNSIGNED NOT NULL DEFAULT 0x0, + + PRIMARY KEY (id) +); + +CREATE TABLE reminders.messages ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + + content VARCHAR(2048) NOT NULL DEFAULT '', + tts BOOL NOT NULL DEFAULT 0, + embed_id INT UNSIGNED, + + attachment MEDIUMBLOB, + attachment_name VARCHAR(260), + + PRIMARY KEY (id), + FOREIGN KEY (embed_id) REFERENCES reminders.embeds(id) ON DELETE SET NULL +); + +CREATE TABLE reminders.reminders ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + uid VARCHAR(64) UNIQUE NOT NULL, + + name VARCHAR(24) NOT NULL DEFAULT 'Reminder', + + message_id INT UNSIGNED NOT NULL, + channel_id INT UNSIGNED NOT NULL, + + `time` INT UNSIGNED DEFAULT 0 NOT NULL, + `interval` INT UNSIGNED DEFAULT NULL, + + enabled BOOLEAN DEFAULT 1 NOT NULL, + + avatar VARCHAR(512) DEFAULT 'https://raw.githubusercontent.com/reminder-bot/logos/master/Remind_Me_Bot_Logo_PPic.jpg' NOT NULL, + username VARCHAR(32) DEFAULT 'Reminder' NOT NULL, + + method ENUM('remind', 'natural', 'dashboard', 'todo'), + set_at TIMESTAMP DEFAULT NOW(), + set_by INT UNSIGNED, + + PRIMARY KEY (id), + FOREIGN KEY (message_id) REFERENCES reminders.messages(id) ON DELETE RESTRICT, + FOREIGN KEY (channel_id) REFERENCES reminders.channels(id) ON DELETE CASCADE, + FOREIGN KEY (set_by) REFERENCES reminders.users(id) ON DELETE SET NULL +); + +CREATE TRIGGER message_cleanup AFTER DELETE ON reminders.reminders +FOR EACH ROW + DELETE FROM reminders.messages WHERE id = OLD.message_id; + +CREATE TRIGGER embed_cleanup AFTER DELETE ON reminders.messages +FOR EACH ROW + DELETE FROM reminders.embeds WHERE id = OLD.embed_id; + +CREATE TABLE reminders.todos ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + user_id INT UNSIGNED, + guild_id INT UNSIGNED, + channel_id INT UNSIGNED, + value VARCHAR(2000) NOT NULL, + + PRIMARY KEY (id), + FOREIGN KEY (user_id) REFERENCES reminders.users(id) ON DELETE SET NULL, + FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE, + FOREIGN KEY (channel_id) REFERENCES reminders.channels(id) ON DELETE SET NULL +); + +CREATE TABLE reminders.command_restrictions ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + + role_id INT UNSIGNED NOT NULL, + command ENUM('todos', 'natural', 'remind', 'interval', 'timer', 'del', 'look', 'alias') NOT NULL, + + PRIMARY KEY (id), + FOREIGN KEY (role_id) REFERENCES reminders.roles(id) ON DELETE CASCADE, + UNIQUE KEY (`role_id`, `command`) +); + +CREATE TABLE reminders.timers ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + start_time TIMESTAMP NOT NULL DEFAULT NOW(), + name VARCHAR(32) NOT NULL, + owner BIGINT UNSIGNED NOT NULL, + + PRIMARY KEY (id) +); + +CREATE TABLE reminders.events ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + `time` TIMESTAMP NOT NULL DEFAULT NOW(), + + event_name ENUM('edit', 'enable', 'disable', 'delete') NOT NULL, + bulk_count INT UNSIGNED, + + guild_id INT UNSIGNED NOT NULL, + user_id INT UNSIGNED, + reminder_id INT UNSIGNED, + + PRIMARY KEY (id), + FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES reminders.users(id) ON DELETE SET NULL, + FOREIGN KEY (reminder_id) REFERENCES reminders.reminders(id) ON DELETE SET NULL +); + +CREATE TABLE reminders.command_aliases ( + id INT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + + guild_id INT UNSIGNED NOT NULL, + name VARCHAR(12) NOT NULL, + + command VARCHAR(2048) NOT NULL, + + PRIMARY KEY (id), + FOREIGN KEY (guild_id) REFERENCES reminders.guilds(id) ON DELETE CASCADE, + UNIQUE KEY (`guild_id`, `name`) +); + +CREATE TABLE reminders.guild_users ( + guild INT UNSIGNED NOT NULL, + user INT UNSIGNED NOT NULL, + + can_access BOOL NOT NULL DEFAULT 0, + + FOREIGN KEY (guild) REFERENCES reminders.guilds(id) ON DELETE CASCADE, + FOREIGN KEY (user) REFERENCES reminders.users(id) ON DELETE CASCADE, + UNIQUE KEY (guild, user) +); + +CREATE EVENT reminders.event_cleanup +ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY +ON COMPLETION PRESERVE +DO DELETE FROM reminders.events WHERE `time` < DATE_SUB(NOW(), INTERVAL 5 DAY); + +CREATE TABLE reminders.languages ( + id SMALLINT UNSIGNED AUTO_INCREMENT UNIQUE NOT NULL, + name VARCHAR(20) NOT NULL, + code VARCHAR(2) NOT NULL, + + PRIMARY KEY (id) +); + +CREATE TABLE reminders.strings ( + id INT NOT NULL AUTO_INCREMENT, + name TEXT, + language TEXT, + value TEXT, + + PRIMARY KEY (id) +); +-- MUST RUN to_database.py TO FORM STRING STORES diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index 45cbe8e..a1a1804 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -16,6 +16,7 @@ use crate::{ GuildData, UserData, Reminder, + Timer, }, SQLPool, time_parser::TimeParser, @@ -23,9 +24,17 @@ use crate::{ use chrono::NaiveDateTime; -use regex::Regex; +use num_integer::Integer; -use std::default::Default; +use std::{ + default::Default, + time::{ + SystemTime, + UNIX_EPOCH, + } +}; + +use regex::Regex; lazy_static! { static ref REGEX_CHANNEL: Regex = Regex::new(r#"^\s*<#(\d+)>\s*$"#).unwrap(); @@ -240,7 +249,7 @@ async fn look(ctx: &Context, msg: &Message, args: String) -> CommandResult { let flags = LookFlags::from_string(&args); - let enabled = if flags.show_disabled { None } else { Some(false) }; + let enabled = if flags.show_disabled { "0,1" } else { "1" }; let reminders = if let Some(guild_id) = msg.guild_id.map(|f| f.as_u64().to_owned()) { let channel_id = flags.channel_id.unwrap_or(msg.channel_id.as_u64().to_owned()); @@ -258,7 +267,7 @@ ON WHERE channels.guild_id = (SELECT id FROM guilds WHERE guild = ?) AND channels.channel = ? AND - reminders.enabled != ? + FIND_IN_SET(reminders.enabled, ?) LIMIT ? ", guild_id, channel_id, enabled, flags.limit) @@ -274,7 +283,7 @@ FROM reminders WHERE reminders.channel_id = (SELECT id FROM channels WHERE channel = ?) AND - reminders.enabled != ? + FIND_IN_SET(reminders.enabled, ?) LIMIT ? ", msg.channel_id.as_u64(), enabled, flags.limit) @@ -301,7 +310,7 @@ LIMIT #[command] #[permission_level(Managed)] -async fn delete(ctx: &Context, msg: &Message, args: String) -> CommandResult { +async fn delete(ctx: &Context, msg: &Message, _args: String) -> CommandResult { let pool = ctx.data.read().await .get::().cloned().expect("Could not get SQLPool from data"); @@ -371,7 +380,7 @@ WHERE .ok() .map( |val| - reminder_ids.get(val) + reminder_ids.get(val - 1) ) .flatten() ) @@ -381,10 +390,11 @@ WHERE if parts.len() == valid_parts.len() { sqlx::query!( " -DELETE FROM reminders WHERE id IN (?) +DELETE FROM reminders WHERE FIND_IN_SET(id, ?) ", valid_parts.join(",")) .execute(&pool) - .await; + .await + .unwrap(); // TODO add deletion events to event list @@ -394,3 +404,90 @@ DELETE FROM reminders WHERE id IN (?) Ok(()) } + +#[command] +#[permission_level(Managed)] +async fn timer(ctx: &Context, msg: &Message, args: String) -> CommandResult { + fn time_difference(start_time: NaiveDateTime) -> String { + let unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; + let now = NaiveDateTime::from_timestamp(unix_time, 0); + + let delta = (now - start_time).num_seconds(); + + let (minutes, seconds) = delta.div_rem(&60); + let (hours, minutes) = minutes.div_rem(&60); + + format!("{:02}:{:02}:{:02}", hours, minutes, seconds) + } + + let pool = ctx.data.read().await + .get::().cloned().expect("Could not get SQLPool from data"); + + let user_data = UserData::from_id(&msg.author, &ctx, &pool).await.unwrap(); + + let mut args_iter = args.splitn(2, " "); + + let owner = msg.guild_id.map(|g| g.as_u64().to_owned()).unwrap_or(msg.author.id.as_u64().to_owned()); + + match args_iter.next() { + Some("list") => { + let timers = Timer::from_owner(owner, &pool).await; + + let _ = msg.channel_id.send_message(&ctx, |m| m + .embed(|e| { + e.fields(timers.iter().map(|timer| (&timer.name, time_difference(timer.start_time), false))) + }) + ).await; + }, + + Some("start") => { + let count = Timer::count_from_owner(owner, &pool).await; + + if count >= 25 { + let _ = msg.channel_id.say(&ctx, user_data.response(&pool, "timer/limit").await).await; + } + else { + let name = args_iter.next().map(|s| s.to_string()).unwrap_or(format!("New timer #{}", count + 1)); + + Timer::create(&name, owner, &pool).await; + + let _ = msg.channel_id.say(&ctx, user_data.response(&pool, "timer/success").await).await; + } + }, + + Some("delete") => { + if let Some(name) = args_iter.next() { + let exists = sqlx::query!( + " +SELECT 1 as _r FROM timers WHERE owner = ? AND name = ? + ", owner, name) + .fetch_one(&pool) + .await; + + if exists.is_ok() { + sqlx::query!( + " +DELETE FROM timers WHERE owner = ? AND name = ? + ", owner, name) + .execute(&pool) + .await + .unwrap(); + + let _ = msg.channel_id.say(&ctx, user_data.response(&pool, "timer/deleted").await).await; + } + else { + let _ = msg.channel_id.say(&ctx, user_data.response(&pool, "timer/not_found").await).await; + } + } + else { + let _ = msg.channel_id.say(&ctx, user_data.response(&pool, "timer/help").await).await; + } + }, + + _ => { + let _ = msg.channel_id.say(&ctx, user_data.response(&pool, "timer/help").await).await; + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 6080de5..0418dbb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,7 +74,10 @@ async fn main() -> Result<(), Box> { .add_command("dashboard", &info_cmds::DASHBOARD_COMMAND) .add_command("clock", &info_cmds::CLOCK_COMMAND) + .add_command("timer", &reminder_cmds::TIMER_COMMAND) + .add_command("look", &reminder_cmds::LOOK_COMMAND) + .add_command("del", &reminder_cmds::DELETE_COMMAND) .add_command("todo", &todo_cmds::TODO_PARSE_COMMAND) diff --git a/src/models.rs b/src/models.rs index b976a13..455a650 100644 --- a/src/models.rs +++ b/src/models.rs @@ -212,3 +212,42 @@ pub struct Reminder { pub time: u32, pub channel_id: u32, } + +pub struct Timer { + pub name: String, + pub start_time: NaiveDateTime, + pub owner: u64, +} + +impl Timer { + pub async fn from_owner(owner: u64, pool: &MySqlPool) -> Vec { + sqlx::query_as_unchecked!(Timer, + " +SELECT name, start_time, owner FROM timers WHERE owner = ? + ", owner) + .fetch_all(pool) + .await + .unwrap() + } + + pub async fn count_from_owner(owner: u64, pool: &MySqlPool) -> u32 { + sqlx::query!( + " +SELECT COUNT(1) as count FROM timers WHERE owner = ? + ", owner) + .fetch_one(pool) + .await + .unwrap() + .count as u32 + } + + pub async fn create(name: &str, owner: u64, pool: &MySqlPool) { + sqlx::query!( + " +INSERT INTO timers (name, owner) VALUES (?, ?) + ", name, owner) + .execute(pool) + .await + .unwrap(); + } +}