create reminders :)
This commit is contained in:
@ -16,3 +16,4 @@ sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql",
|
||||
chrono = "0.4"
|
||||
chrono-tz = "0.5"
|
||||
lazy_static = "1.4.0"
|
||||
rand = "0.7"
|
||||
|
@ -18,6 +18,8 @@ pub const MINUTE: usize = 60;
|
||||
pub const HOUR: usize = 60 * MINUTE;
|
||||
pub const DAY: usize = 24 * HOUR;
|
||||
|
||||
pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
|
||||
use std::{collections::HashSet, env, iter::FromIterator};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
@ -24,7 +24,7 @@ macro_rules! check_length_opt {
|
||||
|
||||
macro_rules! check_url {
|
||||
($field:expr) => {
|
||||
if $field.starts_with("http://") || $field.starts_with("https://") {
|
||||
if !($field.starts_with("http://") || $field.starts_with("https://")) {
|
||||
return json!({ "error": "URL invalid" });
|
||||
}
|
||||
};
|
||||
|
@ -18,7 +18,9 @@ use crate::{
|
||||
MAX_EMBED_DESCRIPTION_LENGTH, MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH,
|
||||
MAX_URL_LENGTH, MAX_USERNAME_LENGTH, MIN_INTERVAL,
|
||||
},
|
||||
routes::dashboard::{create_database_channel, DeleteReminder, Reminder},
|
||||
routes::dashboard::{
|
||||
create_database_channel, generate_uid, name_default, DeleteReminder, Reminder,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Serialize)]
|
||||
@ -193,11 +195,13 @@ pub async fn create_reminder(
|
||||
if reminder.utc_time < Utc::now().naive_utc() {
|
||||
return json!({"error": "Time must be in the future"});
|
||||
}
|
||||
if reminder.interval_months.unwrap_or(0) * 30 * DAY as u32
|
||||
+ reminder.interval_seconds.unwrap_or(0)
|
||||
< *MIN_INTERVAL
|
||||
{
|
||||
return json!({"error": "Interval too short"});
|
||||
if reminder.interval_seconds.is_some() || reminder.interval_months.is_some() {
|
||||
if reminder.interval_months.unwrap_or(0) * 30 * DAY as u32
|
||||
+ reminder.interval_seconds.unwrap_or(0)
|
||||
< *MIN_INTERVAL
|
||||
{
|
||||
return json!({"error": "Interval too short"});
|
||||
}
|
||||
}
|
||||
|
||||
// check patreon if necessary
|
||||
@ -209,9 +213,12 @@ pub async fn create_reminder(
|
||||
}
|
||||
}
|
||||
|
||||
let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() };
|
||||
|
||||
// write to db
|
||||
match sqlx::query!(
|
||||
"INSERT INTO reminders (
|
||||
uid,
|
||||
channel_id,
|
||||
avatar,
|
||||
content,
|
||||
@ -234,30 +241,8 @@ pub async fn create_reminder(
|
||||
tts,
|
||||
username,
|
||||
`utc_time`
|
||||
) VALUES (
|
||||
channel_id = ?,
|
||||
avatar = ?,
|
||||
content = ?,
|
||||
embed_author = ?,
|
||||
embed_author_url = ?,
|
||||
embed_color = ?,
|
||||
embed_description = ?,
|
||||
embed_footer = ?,
|
||||
embed_footer_url = ?,
|
||||
embed_image_url = ?,
|
||||
embed_thumbnail_url = ?,
|
||||
embed_title = ?,
|
||||
enabled = ?,
|
||||
expires = ?,
|
||||
interval_seconds = ?,
|
||||
interval_months = ?,
|
||||
name = ?,
|
||||
pin = ?,
|
||||
restartable = ?,
|
||||
tts = ?,
|
||||
username = ?,
|
||||
`utc_time` = ?
|
||||
)",
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
generate_uid(),
|
||||
channel,
|
||||
reminder.avatar,
|
||||
reminder.content,
|
||||
@ -274,7 +259,7 @@ pub async fn create_reminder(
|
||||
reminder.expires,
|
||||
reminder.interval_seconds,
|
||||
reminder.interval_months,
|
||||
reminder.name,
|
||||
name,
|
||||
reminder.pin,
|
||||
reminder.restartable,
|
||||
reminder.tts,
|
||||
|
@ -1,17 +1,17 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::naive::NaiveDateTime;
|
||||
use rand::{rngs::OsRng, seq::IteratorRandom};
|
||||
use rocket::{http::CookieJar, response::Redirect};
|
||||
use rocket_dyn_templates::Template;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::{
|
||||
client::Context,
|
||||
http::{CacheHttp, Http},
|
||||
model::id::ChannelId,
|
||||
};
|
||||
use sqlx::{Executor, Pool};
|
||||
use serenity::{http::Http, model::id::ChannelId};
|
||||
use sqlx::Executor;
|
||||
|
||||
use crate::{consts::DEFAULT_AVATAR, Database, Error};
|
||||
use crate::{
|
||||
consts::{CHARACTERS, DEFAULT_AVATAR},
|
||||
Database, Error,
|
||||
};
|
||||
|
||||
pub mod guild;
|
||||
pub mod user;
|
||||
@ -37,21 +37,30 @@ pub struct Reminder {
|
||||
embed_image_url: Option<String>,
|
||||
embed_thumbnail_url: Option<String>,
|
||||
embed_title: String,
|
||||
enabled: i8,
|
||||
enabled: bool,
|
||||
expires: Option<NaiveDateTime>,
|
||||
interval_seconds: Option<u32>,
|
||||
interval_months: Option<u32>,
|
||||
#[serde(default = "name_default")]
|
||||
name: String,
|
||||
pin: i8,
|
||||
restartable: i8,
|
||||
tts: i8,
|
||||
pin: bool,
|
||||
restartable: bool,
|
||||
tts: bool,
|
||||
#[serde(default)]
|
||||
uid: String,
|
||||
username: Option<String>,
|
||||
utc_time: NaiveDateTime,
|
||||
}
|
||||
|
||||
pub fn generate_uid() -> String {
|
||||
let mut generator: OsRng = Default::default();
|
||||
|
||||
(0..64)
|
||||
.map(|_| CHARACTERS.chars().choose(&mut generator).unwrap().to_owned().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
// https://github.com/serde-rs/json/issues/329#issuecomment-305608405
|
||||
mod string {
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
@ -282,7 +282,7 @@ pub async fn get_reminders(
|
||||
if let Some(channel_id) = dm_channel {
|
||||
let reminders = sqlx::query_as!(
|
||||
Reminder,
|
||||
"SELECT
|
||||
r#"SELECT
|
||||
reminders.attachment,
|
||||
reminders.attachment_name,
|
||||
reminders.avatar,
|
||||
@ -297,18 +297,18 @@ pub async fn get_reminders(
|
||||
reminders.embed_image_url,
|
||||
reminders.embed_thumbnail_url,
|
||||
reminders.embed_title,
|
||||
reminders.enabled,
|
||||
reminders.enabled as "enabled:_",
|
||||
reminders.expires,
|
||||
reminders.interval_seconds,
|
||||
reminders.interval_months,
|
||||
reminders.name,
|
||||
reminders.pin,
|
||||
reminders.restartable,
|
||||
reminders.tts,
|
||||
reminders.pin as "pin:_",
|
||||
reminders.restartable as "restartable:_",
|
||||
reminders.tts as "tts:_",
|
||||
reminders.uid,
|
||||
reminders.username,
|
||||
reminders.utc_time
|
||||
FROM reminders INNER JOIN channels ON channels.id = reminders.channel_id WHERE channels.channel = ?",
|
||||
FROM reminders INNER JOIN channels ON channels.id = reminders.channel_id WHERE channels.channel = ?"#,
|
||||
channel_id
|
||||
)
|
||||
.fetch_all(pool.inner())
|
||||
|
@ -114,7 +114,7 @@
|
||||
<br>
|
||||
Your browser timezone is: <strong><span class="browser-timezone">%browsertimezone%</span></strong> (<span class="browser-time">HH:mm</span>)
|
||||
<br>
|
||||
Your bot timezone is: <strong><span class="bot-timezone">%bottimezone</span></strong> (<span class="bot-time">HH:mm</span>)
|
||||
Your bot timezone is: <strong><span class="bot-timezone">%bottimezone%</span></strong> (<span class="bot-time">HH:mm</span>)
|
||||
</p>
|
||||
<br>
|
||||
<div class="has-text-centered">
|
||||
@ -414,6 +414,7 @@
|
||||
// populate channels
|
||||
newFrame.querySelector('select.channel-selector');
|
||||
|
||||
// populate majority of items
|
||||
for (let prop in reminder) {
|
||||
if (reminder.hasOwnProperty(prop) && reminder[prop] !== null) {
|
||||
let $input = newFrame.querySelector(`*[name="${prop}"]`);
|
||||
@ -427,6 +428,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
let timeInput = newFrame.querySelector('input[name="time"]');
|
||||
let localTime = luxon.DateTime.fromISO(reminder["utc_time"]).setZone(timezone);
|
||||
timeInput.value = localTime.toFormat("yyyy-LL-dd'T'HH:mm:ss");
|
||||
|
||||
$reminderBox.appendChild(newFrame);
|
||||
}
|
||||
}
|
||||
@ -668,6 +673,62 @@
|
||||
});
|
||||
});
|
||||
|
||||
let $createReminder = document.querySelector('#reminderCreator');
|
||||
|
||||
$createReminder.querySelector('button#createReminder').addEventListener('click', () => {
|
||||
// create reminder object
|
||||
let seconds = parseInt($createReminder.querySelector('input[name="interval_seconds"]').value) || null;
|
||||
let months = parseInt($createReminder.querySelector('input[name="interval_months"]').value) || null;
|
||||
|
||||
let rgb_color = window.getComputedStyle($createReminder.querySelector('div.discord-embed')).borderLeftColor;
|
||||
let rgb = rgb_color.match(/\d+/g);
|
||||
let color = colorToInt(parseInt(rgb[0]), parseInt(rgb[1]), parseInt(rgb[2]));
|
||||
|
||||
let utc_time = luxon.DateTime.fromISO($createReminder.querySelector('input[name="time"]').value).setZone('UTC');
|
||||
|
||||
let reminder = {
|
||||
avatar: $createReminder.querySelector('img.discord-avatar').src,
|
||||
channel: $createReminder.querySelector('select.channel-selector').value,
|
||||
content: $createReminder.querySelector('textarea#messageContent').value,
|
||||
embed_author_url: $createReminder.querySelector('img.embed_author_url').src,
|
||||
embed_author: $createReminder.querySelector('textarea#embedAuthor').value,
|
||||
embed_color: color,
|
||||
embed_description: $createReminder.querySelector('textarea#embedDescription').value,
|
||||
embed_footer: $createReminder.querySelector('textarea#embedFooter').value,
|
||||
embed_footer_url: $createReminder.querySelector('img.embed_footer_url').src,
|
||||
embed_image_url: $createReminder.querySelector('img.embed_image_url').src,
|
||||
embed_thumbnail_url: $createReminder.querySelector('img.embed_thumbnail_url').src,
|
||||
embed_title: $createReminder.querySelector('textarea#embedTitle').value,
|
||||
enabled: true,
|
||||
expires: null,
|
||||
interval_seconds: seconds,
|
||||
interval_months: months,
|
||||
name: $createReminder.querySelector('input[name="name"]').value,
|
||||
pin: $createReminder.querySelector('input[name="pin"]').checked,
|
||||
restartable: false,
|
||||
tts: $createReminder.querySelector('input[name="tts"]').checked,
|
||||
username: $createReminder.querySelector('input#reminderUsername').value,
|
||||
utc_time: utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss")
|
||||
}
|
||||
|
||||
console.log(reminder);
|
||||
|
||||
// send to server
|
||||
let guild = document.querySelector('.guildList a.is-active').dataset['guild'];
|
||||
|
||||
fetch(`/dashboard/api/guild/${guild}/reminders`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(reminder)
|
||||
}).then(response => response.json()).then(data => console.log(data))
|
||||
|
||||
// process response
|
||||
|
||||
// reset inputs
|
||||
});
|
||||
|
||||
document.querySelectorAll('textarea.autoresize').forEach((element) => {
|
||||
element.addEventListener('input', () => {
|
||||
element.style.height = "";
|
||||
@ -696,6 +757,10 @@
|
||||
});
|
||||
});
|
||||
|
||||
function colorToInt(r, g, b) {
|
||||
return (r << 16) + (g << 8) + (b);
|
||||
}
|
||||
|
||||
document.querySelectorAll('.customizable').forEach((element) => {
|
||||
element.querySelector('a').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
@ -28,7 +28,7 @@
|
||||
<div class="a">
|
||||
<p class="image is-24x24 customizable">
|
||||
<a>
|
||||
<img class="is-rounded" src="">
|
||||
<img class="is-rounded embed_author_url" src="">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@ -77,7 +77,7 @@
|
||||
<div class="b">
|
||||
<p class="image thumbnail customizable">
|
||||
<a>
|
||||
<img class="" src="" alt="Square thumbnail embedded image">
|
||||
<img class="embed_thumbnail_url" src="" alt="Square thumbnail embedded image">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
@ -85,21 +85,21 @@
|
||||
|
||||
<p class="image is-400x300 customizable">
|
||||
<a>
|
||||
<img class="" src="" alt="Large embedded image">
|
||||
<img class="embed_image_url" src="" alt="Large embedded image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="embed-footer-box">
|
||||
<p class="image is-20x20 customizable">
|
||||
<a>
|
||||
<img class="is-rounded " src="" alt="Footer profile-like image">
|
||||
<img class="is-rounded embed_footer_url" src="" alt="Footer profile-like image">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<label class="is-sr-only" for="embedFooter">Embed Footer text</label>
|
||||
<textarea class="discord-embed-footer message-input autoresize "
|
||||
placeholder="Embed Footer..."
|
||||
maxlength="2048" id="embedFooter" name="embed_author" rows="1"></textarea>
|
||||
maxlength="2048" id="embedFooter" name="embed_footer" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -128,7 +128,7 @@
|
||||
<div class="field">
|
||||
<label class="label">Time</label>
|
||||
<div class="control">
|
||||
<input class="input" type="datetime-local" name="time">
|
||||
<input class="input" type="datetime-local" step="1" name="time">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -140,13 +140,13 @@
|
||||
<input class="input" type="number" name="interval_months" placeholder="Months">
|
||||
</div>
|
||||
<div class="column">
|
||||
<input class="input" type="number" name="interval_seconds" placeholder="Days">
|
||||
<input class="input" type="number" name="interval_days" placeholder="Days">
|
||||
</div>
|
||||
<div class="column">
|
||||
<input class="input" type="number" name="interval_seconds" placeholder="Hours">
|
||||
<input class="input" type="number" name="interval_hours" placeholder="Hours">
|
||||
</div>
|
||||
<div class="column">
|
||||
<input class="input" type="number" name="interval_seconds" placeholder="Minutes">
|
||||
<input class="input" type="number" name="interval_minutes" placeholder="Minutes">
|
||||
</div>
|
||||
<div class="column">
|
||||
<input class="input" type="number" name="interval_seconds" placeholder="Seconds">
|
||||
|
@ -6,6 +6,7 @@
|
||||
<div id="reminderCreator">
|
||||
{% set creating = true %}
|
||||
{% include "reminder_dashboard/guild_reminder" %}
|
||||
{% set creating = false %}
|
||||
</div>
|
||||
|
||||
<div class="buttons has-addons" style="margin-top: 1rem;">
|
||||
|
@ -45,10 +45,20 @@
|
||||
<h2 class="title">JellyWX's Home</h2>
|
||||
<ul class="is-size-5 pl-6">
|
||||
<li>Do not discuss politics, harass other users, or use language intended to upset other users</li>
|
||||
<li>Do not send malicious links</li>
|
||||
<li>Do not share personal information about yourself or any other user. This includes but is not
|
||||
limited to real names<sup>1</sup>, addresses, phone numbers, country of origin<sup>2</sup>, religion, email address,
|
||||
IP address.</li>
|
||||
<li>Do not send malicious links or attachments</li>
|
||||
<li>Do not advertise</li>
|
||||
<li>Do not send unwarranted direct messages</li>
|
||||
</ul>
|
||||
<p class="small">
|
||||
<sup>1</sup> Some users may use their real name on their account. In this case, do not assert that
|
||||
this is a user's real name, or use it to try and identify a user.
|
||||
<br>
|
||||
<sup>2</sup> Country of current residence may be discussed, as this is relevant to timezone and
|
||||
DST selection.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
Reference in New Issue
Block a user