diff --git a/.gitignore b/.gitignore index fedaa2b..3b963e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .env +/venv diff --git a/Cargo.lock b/Cargo.lock index 7d3755d..6482b8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1129,6 +1129,7 @@ dependencies = [ "lazy_static", "log", "num-integer", + "num-traits", "rand", "regex", "regex_command_attr", diff --git a/Cargo.toml b/Cargo.toml index 532f65f..9293c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ chrono = "0.4" chrono-tz = "0.5" lazy_static = "1.4.0" num-integer = "0.1.43" +num-traits = "0.2.12" custom_error = "1.7.1" serde = "1.0.115" serde_json = "1.0.57" diff --git a/dp.py b/dp.py new file mode 100644 index 0000000..4077bf1 --- /dev/null +++ b/dp.py @@ -0,0 +1,13 @@ +import dateparser +import sys +import pytz +from datetime import datetime + +dt = dateparser.parse(sys.argv[1], settings={ + 'TIMEZONE': sys.argv[2], + 'TO_TIMEZONE': sys.argv[3], + 'RELATIVE_BASE': datetime.now(pytz.timezone(sys.argv[2])).replace(tzinfo=None), + 'PREFER_DATES_FROM': 'future', +}) + +print(dt.timestamp() if dt is not None else -1) diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index 07f0c19..ac117f5 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -19,6 +19,8 @@ use serenity::{ framework::standard::CommandResult, }; +use tokio::process::Command; + use crate::{ models::{ ChannelData, @@ -41,7 +43,10 @@ use rand::{ RngCore, }; +use std::str::from_utf8; + use num_integer::Integer; +use num_traits::cast::ToPrimitive; use std::{ string::ToString, @@ -49,18 +54,24 @@ use std::{ time::{ SystemTime, UNIX_EPOCH, - } + }, + env, }; use regex::Regex; use serde_json::json; use sqlx::MySqlPool; +use std::convert::TryInto; lazy_static! { static ref REGEX_CHANNEL: Regex = Regex::new(r#"^\s*<#(\d+)>\s*$"#).unwrap(); static ref REGEX_CHANNEL_USER: Regex = Regex::new(r#"^\s*<(#|@)(?:!)?(\d+)>\s*$"#).unwrap(); + + static ref MIN_INTERVAL: i64 = env::var("MIN_INTERVAL").ok().map(|inner| inner.parse::().ok()).flatten().unwrap_or(600); + + static ref MAX_TIME: i64 = env::var("MAX_TIME").ok().map(|inner| inner.parse::().ok()).flatten().unwrap_or(60*60*24*365*50); } static CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; @@ -622,7 +633,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem msg.guild_id, scope_id, time_parser, - Some(interval_seconds as u32), + Some(interval_seconds), content).await } else { @@ -697,20 +708,65 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem } .replacen("{location}", &scope_id.mention(), 1) .replacen("{offset}", &time_parser.map(|tp| tp.displacement().ok()).flatten().unwrap_or(-1).to_string(), 1) - .replacen("{min_interval}", "min_interval", 1) - .replacen("{max_time}", "max_time", 1); + .replacen("{min_interval}", &MIN_INTERVAL.to_string(), 1) + .replacen("{max_time}", &MAX_TIME.to_string(), 1); let _ = msg.channel_id.say(&ctx, &str_response).await; } -async fn create_reminder( +#[command] +async fn natural(ctx: &Context, msg: &Message, args: String) -> CommandResult { + let pool = ctx.data.read().await + .get::().cloned().expect("Could not get SQLPool from data"); + + let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap(); + + let send_str = user_data.response(&pool, "natural/send").await; + let to_str = user_data.response(&pool, "natural/to").await; + let every_str = user_data.response(&pool, "natural/every").await; + + let location_ids = vec![msg.channel_id.as_u64().to_owned()]; + + let mut args_iter = args.splitn(1, &send_str); + + let (time_crop_opt, msg_crop_opt) = (args_iter.next(), args_iter.next()); + + if let (Some(time_crop), Some(msg_crop)) = (time_crop_opt, msg_crop_opt) { + let python_call = Command::new("venv/bin/python3") + .arg("dp.py") + .arg(time_crop) + .arg(user_data.timezone) + .arg(&env::var("LOCAL_TIMEZONE").unwrap_or("UTC".to_string())) + .output() + .await; + + if let Some(timestamp) = python_call.ok().map(|inner| + if inner.status.success() { + from_utf8(&*inner.stdout).unwrap().parse::().unwrap().round().to_i64() + } + else { + None + }).flatten() { + + // check other options and then create reminder :) + } + // something not right with the time parse + else { + + } + } + + Ok(()) +} + +async fn create_reminder>( ctx: impl CacheHttp, pool: &MySqlPool, user_id: u64, guild_id: Option, scope_id: &ReminderScope, - time_parser: &TimeParser, - interval: Option, + time_parser: T, + interval: Option, content: String) -> Result<(), ReminderError> { @@ -755,19 +811,19 @@ async fn create_reminder( Err(ReminderError::NotEnoughArgs) } // todo replace numbers with configurable values - else if interval.map_or(false, |inner| inner < 800) { + else if interval.map_or(false, |inner| inner < *MIN_INTERVAL) { Err(ReminderError::ShortInterval) } - else if interval.map_or(false, |inner| inner > 60*60*24*365*50) { + else if interval.map_or(false, |inner| inner > *MAX_TIME) { Err(ReminderError::LongInterval) } else { - match time_parser.timestamp() { + match time_parser.try_into() { Ok(time) => { let unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; if time > unix_time { - if time > unix_time + 60*60*24*365*50 { + if time > unix_time + *MAX_TIME { Err(ReminderError::LongTime) } else { diff --git a/src/main.rs b/src/main.rs index 1f57848..d6320a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,6 +84,9 @@ async fn main() -> Result<(), Box> { .add_command("r", &reminder_cmds::REMIND_COMMAND) .add_command("interval", &reminder_cmds::INTERVAL_COMMAND) .add_command("i", &reminder_cmds::INTERVAL_COMMAND) + .add_command("natural", &reminder_cmds::NATURAL_COMMAND) + .add_command("n", &reminder_cmds::NATURAL_COMMAND) + .add_command("", &reminder_cmds::NATURAL_COMMAND) .add_command("look", &reminder_cmds::LOOK_COMMAND) .add_command("del", &reminder_cmds::DELETE_COMMAND) diff --git a/src/time_parser.rs b/src/time_parser.rs index 38422eb..83f7cb6 100644 --- a/src/time_parser.rs +++ b/src/time_parser.rs @@ -11,6 +11,7 @@ use std::fmt::{ use chrono_tz::Tz; use chrono::TimeZone; +use std::convert::TryFrom; #[derive(Debug)] pub enum InvalidTime { @@ -40,6 +41,14 @@ pub struct TimeParser { parse_type: ParseType, } +impl TryFrom<&TimeParser> for i64 { + type Error = InvalidTime; + + fn try_from(value: &TimeParser) -> Result { + value.timestamp() + } +} + impl TimeParser { pub fn new(input: String, timezone: Tz) -> Self { let inverted = if input.starts_with("-") {