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();
+ }
+}