fields are now json and work. fix for intervals. moved some code together
This commit is contained in:
parent
d946ef1dca
commit
85d27c5bba
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1943,6 +1943,8 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"regex",
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -2153,6 +2155,7 @@ dependencies = [
|
|||||||
"rocket",
|
"rocket",
|
||||||
"rocket_dyn_templates",
|
"rocket_dyn_templates",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
]
|
]
|
||||||
@ -2761,6 +2764,8 @@ dependencies = [
|
|||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rsa",
|
"rsa",
|
||||||
"rustls 0.19.1",
|
"rustls 0.19.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha-1 0.9.8",
|
"sha-1 0.9.8",
|
||||||
"sha2 0.9.9",
|
"sha2 0.9.9",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
@ -2786,6 +2791,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"serde_json",
|
||||||
"sha2 0.9.9",
|
"sha2 0.9.9",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-rt",
|
"sqlx-rt",
|
||||||
|
@ -29,3 +29,5 @@ CREATE TABLE reminder_template (
|
|||||||
|
|
||||||
FOREIGN KEY (`guild_id`) REFERENCES channels (`id`) ON DELETE CASCADE
|
FOREIGN KEY (`guild_id`) REFERENCES channels (`id`) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ALTER TABLE reminders ADD COLUMN embed_fields JSON;
|
||||||
|
@ -12,7 +12,9 @@ chrono = "0.4"
|
|||||||
chrono-tz = { version = "0.5", features = ["serde"] }
|
chrono-tz = { version = "0.5", features = ["serde"] }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
num-integer = "0.1"
|
num-integer = "0.1"
|
||||||
sqlx = { version = "0.5.10", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]}
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
sqlx = { version = "0.5.10", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]}
|
||||||
|
|
||||||
[dependencies.serenity]
|
[dependencies.serenity]
|
||||||
git = "https://github.com/serenity-rs/serenity"
|
git = "https://github.com/serenity-rs/serenity"
|
||||||
|
@ -4,6 +4,7 @@ use lazy_static::lazy_static;
|
|||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use num_integer::Integer;
|
use num_integer::Integer;
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
|
use serde::Deserialize;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
builder::CreateEmbed,
|
builder::CreateEmbed,
|
||||||
http::{CacheHttp, Http, StatusCode},
|
http::{CacheHttp, Http, StatusCode},
|
||||||
@ -15,7 +16,10 @@ use serenity::{
|
|||||||
Error, Result,
|
Error, Result,
|
||||||
};
|
};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
types::chrono::{NaiveDateTime, Utc},
|
types::{
|
||||||
|
chrono::{NaiveDateTime, Utc},
|
||||||
|
Json,
|
||||||
|
},
|
||||||
Executor,
|
Executor,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -94,11 +98,6 @@ pub fn substitute(string: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Embed {
|
struct Embed {
|
||||||
inner: EmbedInner,
|
|
||||||
fields: Vec<EmbedField>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmbedInner {
|
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
image_url: Option<String>,
|
image_url: Option<String>,
|
||||||
@ -108,8 +107,10 @@ struct EmbedInner {
|
|||||||
author: String,
|
author: String,
|
||||||
author_url: Option<String>,
|
author_url: Option<String>,
|
||||||
color: u32,
|
color: u32,
|
||||||
|
fields: Json<Vec<EmbedField>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
struct EmbedField {
|
struct EmbedField {
|
||||||
title: String,
|
title: String,
|
||||||
value: String,
|
value: String,
|
||||||
@ -121,76 +122,54 @@ impl Embed {
|
|||||||
pool: impl Executor<'_, Database = Database> + Copy,
|
pool: impl Executor<'_, Database = Database> + Copy,
|
||||||
id: u32,
|
id: u32,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let mut inner = sqlx::query_as_unchecked!(
|
let mut embed = sqlx::query_as!(
|
||||||
EmbedInner,
|
Self,
|
||||||
"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
`embed_title` AS title,
|
`embed_title` AS title,
|
||||||
`embed_description` AS description,
|
`embed_description` AS description,
|
||||||
`embed_image_url` AS image_url,
|
`embed_image_url` AS image_url,
|
||||||
`embed_thumbnail_url` AS thumbnail_url,
|
`embed_thumbnail_url` AS thumbnail_url,
|
||||||
`embed_footer` AS footer,
|
`embed_footer` AS footer,
|
||||||
`embed_footer_url` AS footer_url,
|
`embed_footer_url` AS footer_url,
|
||||||
`embed_author` AS author,
|
`embed_author` AS author,
|
||||||
`embed_author_url` AS author_url,
|
`embed_author_url` AS author_url,
|
||||||
`embed_color` AS color
|
`embed_color` AS color,
|
||||||
FROM
|
IFNULL(`embed_fields`, '[]') AS "fields:_"
|
||||||
reminders
|
FROM reminders
|
||||||
WHERE
|
WHERE `id` = ?"#,
|
||||||
`id` = ?
|
|
||||||
",
|
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
inner.title = substitute(&inner.title);
|
embed.title = substitute(&embed.title);
|
||||||
inner.description = substitute(&inner.description);
|
embed.description = substitute(&embed.description);
|
||||||
inner.footer = substitute(&inner.footer);
|
embed.footer = substitute(&embed.footer);
|
||||||
|
|
||||||
let mut fields = sqlx::query_as_unchecked!(
|
embed.fields.iter_mut().for_each(|mut field| {
|
||||||
EmbedField,
|
|
||||||
"
|
|
||||||
SELECT
|
|
||||||
title,
|
|
||||||
value,
|
|
||||||
inline
|
|
||||||
FROM
|
|
||||||
embed_fields
|
|
||||||
WHERE
|
|
||||||
reminder_id = ?
|
|
||||||
",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
fields.iter_mut().for_each(|mut field| {
|
|
||||||
field.title = substitute(&field.title);
|
field.title = substitute(&field.title);
|
||||||
field.value = substitute(&field.value);
|
field.value = substitute(&field.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
let e = Embed { inner, fields };
|
if embed.has_content() {
|
||||||
|
Some(embed)
|
||||||
if e.has_content() {
|
|
||||||
Some(e)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_content(&self) -> bool {
|
pub fn has_content(&self) -> bool {
|
||||||
if self.inner.title.is_empty()
|
if self.title.is_empty()
|
||||||
&& self.inner.description.is_empty()
|
&& self.description.is_empty()
|
||||||
&& self.inner.image_url.is_none()
|
&& self.image_url.is_none()
|
||||||
&& self.inner.thumbnail_url.is_none()
|
&& self.thumbnail_url.is_none()
|
||||||
&& self.inner.footer.is_empty()
|
&& self.footer.is_empty()
|
||||||
&& self.inner.footer_url.is_none()
|
&& self.footer_url.is_none()
|
||||||
&& self.inner.author.is_empty()
|
&& self.author.is_empty()
|
||||||
&& self.inner.author_url.is_none()
|
&& self.author_url.is_none()
|
||||||
&& self.fields.is_empty()
|
&& self.fields.0.is_empty()
|
||||||
{
|
{
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
@ -203,37 +182,37 @@ impl Into<CreateEmbed> for Embed {
|
|||||||
fn into(self) -> CreateEmbed {
|
fn into(self) -> CreateEmbed {
|
||||||
let mut c = CreateEmbed::default();
|
let mut c = CreateEmbed::default();
|
||||||
|
|
||||||
c.title(&self.inner.title)
|
c.title(&self.title)
|
||||||
.description(&self.inner.description)
|
.description(&self.description)
|
||||||
.color(self.inner.color)
|
.color(self.color)
|
||||||
.author(|a| {
|
.author(|a| {
|
||||||
a.name(&self.inner.author);
|
a.name(&self.author);
|
||||||
|
|
||||||
if let Some(author_icon) = &self.inner.author_url {
|
if let Some(author_icon) = &self.author_url {
|
||||||
a.icon_url(author_icon);
|
a.icon_url(author_icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
a
|
a
|
||||||
})
|
})
|
||||||
.footer(|f| {
|
.footer(|f| {
|
||||||
f.text(&self.inner.footer);
|
f.text(&self.footer);
|
||||||
|
|
||||||
if let Some(footer_icon) = &self.inner.footer_url {
|
if let Some(footer_icon) = &self.footer_url {
|
||||||
f.icon_url(footer_icon);
|
f.icon_url(footer_icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
f
|
f
|
||||||
});
|
});
|
||||||
|
|
||||||
for field in &self.fields {
|
for field in &self.fields.0 {
|
||||||
c.field(&field.title, &field.value, field.inline);
|
c.field(&field.title, &field.value, field.inline);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(image_url) = &self.inner.image_url {
|
if let Some(image_url) = &self.image_url {
|
||||||
c.image(image_url);
|
c.image(image_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(thumbnail_url) = &self.inner.thumbnail_url {
|
if let Some(thumbnail_url) = &self.thumbnail_url {
|
||||||
c.thumbnail(thumbnail_url);
|
c.thumbnail(thumbnail_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ oauth2 = "4"
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
reqwest = "0.11"
|
reqwest = "0.11"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono"] }
|
serde_json = "1.0"
|
||||||
|
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
chrono-tz = "0.5"
|
chrono-tz = "0.5"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
@ -10,9 +10,9 @@ pub const MAX_EMBED_AUTHOR_LENGTH: usize = 256;
|
|||||||
pub const MAX_EMBED_FOOTER_LENGTH: usize = 2048;
|
pub const MAX_EMBED_FOOTER_LENGTH: usize = 2048;
|
||||||
pub const MAX_URL_LENGTH: usize = 512;
|
pub const MAX_URL_LENGTH: usize = 512;
|
||||||
pub const MAX_USERNAME_LENGTH: usize = 100;
|
pub const MAX_USERNAME_LENGTH: usize = 100;
|
||||||
|
pub const MAX_EMBED_FIELDS: usize = 25;
|
||||||
pub const MAX_EMBED_FIELD_TITLE_LENGTH: usize = 256;
|
pub const MAX_EMBED_FIELD_TITLE_LENGTH: usize = 256;
|
||||||
pub const MAX_EMBED_FIELD_VALUE_LENGTH: usize = 1024;
|
pub const MAX_EMBED_FIELD_VALUE_LENGTH: usize = 1024;
|
||||||
pub const MAX_EMBED_FIELDS: usize = 25;
|
|
||||||
|
|
||||||
pub const MINUTE: usize = 60;
|
pub const MINUTE: usize = 60;
|
||||||
pub const HOUR: usize = 60 * MINUTE;
|
pub const HOUR: usize = 60 * MINUTE;
|
||||||
|
@ -9,7 +9,11 @@ mod routes;
|
|||||||
use std::{collections::HashMap, env};
|
use std::{collections::HashMap, env};
|
||||||
|
|
||||||
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
||||||
use rocket::{fs::FileServer, tokio::sync::broadcast::Sender};
|
use rocket::{
|
||||||
|
fs::FileServer,
|
||||||
|
serde::json::{json, Json, Value as JsonValue},
|
||||||
|
tokio::sync::broadcast::Sender,
|
||||||
|
};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
@ -46,6 +50,11 @@ async fn not_found() -> Template {
|
|||||||
Template::render("errors/404", &map)
|
Template::render("errors/404", &map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[catch(422)]
|
||||||
|
async fn unprocessable_entity() -> JsonValue {
|
||||||
|
json!({"error": "Invalid request.", "errors": ["Invalid request."]})
|
||||||
|
}
|
||||||
|
|
||||||
#[catch(500)]
|
#[catch(500)]
|
||||||
async fn internal_server_error() -> Template {
|
async fn internal_server_error() -> Template {
|
||||||
let map: HashMap<String, String> = HashMap::new();
|
let map: HashMap<String, String> = HashMap::new();
|
||||||
@ -69,7 +78,16 @@ pub async fn initialize(
|
|||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
.register("/", catchers![not_authorized, forbidden, not_found, internal_server_error])
|
.register(
|
||||||
|
"/",
|
||||||
|
catchers![
|
||||||
|
not_authorized,
|
||||||
|
forbidden,
|
||||||
|
not_found,
|
||||||
|
internal_server_error,
|
||||||
|
unprocessable_entity
|
||||||
|
],
|
||||||
|
)
|
||||||
.manage(oauth2_client)
|
.manage(oauth2_client)
|
||||||
.manage(reqwest_client)
|
.manage(reqwest_client)
|
||||||
.manage(serenity_context)
|
.manage(serenity_context)
|
||||||
@ -105,10 +123,6 @@ pub async fn initialize(
|
|||||||
routes::dashboard::user::get_user_info,
|
routes::dashboard::user::get_user_info,
|
||||||
routes::dashboard::user::update_user_info,
|
routes::dashboard::user::update_user_info,
|
||||||
routes::dashboard::user::get_user_guilds,
|
routes::dashboard::user::get_user_guilds,
|
||||||
routes::dashboard::user::create_reminder,
|
|
||||||
routes::dashboard::user::get_reminders,
|
|
||||||
routes::dashboard::user::overwrite_reminder,
|
|
||||||
routes::dashboard::user::delete_reminder,
|
|
||||||
routes::dashboard::guild::get_guild_channels,
|
routes::dashboard::guild::get_guild_channels,
|
||||||
routes::dashboard::guild::get_guild_roles,
|
routes::dashboard::guild::get_guild_roles,
|
||||||
routes::dashboard::guild::create_reminder,
|
routes::dashboard::guild::create_reminder,
|
||||||
|
@ -19,12 +19,13 @@ use crate::{
|
|||||||
check_guild_subscription, check_subscription,
|
check_guild_subscription, check_subscription,
|
||||||
consts::{
|
consts::{
|
||||||
DAY, DISCORD_CDN, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH,
|
DAY, DISCORD_CDN, MAX_CONTENT_LENGTH, MAX_EMBED_AUTHOR_LENGTH,
|
||||||
MAX_EMBED_DESCRIPTION_LENGTH, MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH,
|
MAX_EMBED_DESCRIPTION_LENGTH, MAX_EMBED_FIELDS, MAX_EMBED_FIELD_TITLE_LENGTH,
|
||||||
|
MAX_EMBED_FIELD_VALUE_LENGTH, MAX_EMBED_FOOTER_LENGTH, MAX_EMBED_TITLE_LENGTH,
|
||||||
MAX_URL_LENGTH, MAX_USERNAME_LENGTH, MIN_INTERVAL,
|
MAX_URL_LENGTH, MAX_USERNAME_LENGTH, MIN_INTERVAL,
|
||||||
},
|
},
|
||||||
routes::dashboard::{
|
routes::dashboard::{
|
||||||
create_database_channel, generate_uid, name_default, DeleteReminder, JsonReminder,
|
create_database_channel, generate_uid, name_default, DeleteReminder, PatchReminder,
|
||||||
PatchReminder, Reminder,
|
Reminder,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Conte
|
|||||||
#[post("/api/guild/<id>/reminders", data = "<reminder>")]
|
#[post("/api/guild/<id>/reminders", data = "<reminder>")]
|
||||||
pub async fn create_reminder(
|
pub async fn create_reminder(
|
||||||
id: u64,
|
id: u64,
|
||||||
reminder: Json<JsonReminder>,
|
reminder: Json<Reminder>,
|
||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
serenity_context: &State<Context>,
|
serenity_context: &State<Context>,
|
||||||
pool: &State<Pool<MySql>>,
|
pool: &State<Pool<MySql>>,
|
||||||
@ -180,6 +181,13 @@ pub async fn create_reminder(
|
|||||||
check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
|
check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
|
||||||
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
|
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
|
||||||
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
|
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
|
||||||
|
check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
|
||||||
|
if let Some(fields) = &reminder.embed_fields {
|
||||||
|
for field in &fields.0 {
|
||||||
|
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
|
||||||
|
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
check_length_opt!(MAX_USERNAME_LENGTH, reminder.username);
|
check_length_opt!(MAX_USERNAME_LENGTH, reminder.username);
|
||||||
check_length_opt!(
|
check_length_opt!(
|
||||||
MAX_URL_LENGTH,
|
MAX_URL_LENGTH,
|
||||||
@ -245,6 +253,7 @@ pub async fn create_reminder(
|
|||||||
embed_image_url,
|
embed_image_url,
|
||||||
embed_thumbnail_url,
|
embed_thumbnail_url,
|
||||||
embed_title,
|
embed_title,
|
||||||
|
embed_fields,
|
||||||
enabled,
|
enabled,
|
||||||
expires,
|
expires,
|
||||||
interval_seconds,
|
interval_seconds,
|
||||||
@ -255,7 +264,7 @@ pub async fn create_reminder(
|
|||||||
tts,
|
tts,
|
||||||
username,
|
username,
|
||||||
`utc_time`
|
`utc_time`
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
new_uid,
|
new_uid,
|
||||||
attachment_data,
|
attachment_data,
|
||||||
reminder.attachment_name,
|
reminder.attachment_name,
|
||||||
@ -271,6 +280,7 @@ pub async fn create_reminder(
|
|||||||
reminder.embed_image_url,
|
reminder.embed_image_url,
|
||||||
reminder.embed_thumbnail_url,
|
reminder.embed_thumbnail_url,
|
||||||
reminder.embed_title,
|
reminder.embed_title,
|
||||||
|
reminder.embed_fields,
|
||||||
reminder.enabled,
|
reminder.enabled,
|
||||||
reminder.expires,
|
reminder.expires,
|
||||||
reminder.interval_seconds,
|
reminder.interval_seconds,
|
||||||
@ -302,6 +312,7 @@ pub async fn create_reminder(
|
|||||||
reminders.embed_image_url,
|
reminders.embed_image_url,
|
||||||
reminders.embed_thumbnail_url,
|
reminders.embed_thumbnail_url,
|
||||||
reminders.embed_title,
|
reminders.embed_title,
|
||||||
|
reminders.embed_fields,
|
||||||
reminders.enabled,
|
reminders.enabled,
|
||||||
reminders.expires,
|
reminders.expires,
|
||||||
reminders.interval_seconds,
|
reminders.interval_seconds,
|
||||||
@ -324,7 +335,7 @@ pub async fn create_reminder(
|
|||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
warn!("Failed to complete SQL query: {:?}", e);
|
warn!("Failed to complete SQL query: {:?}", e);
|
||||||
|
|
||||||
json!({"error": "Could not load reminders"})
|
json!({"error": "Could not load reminder"})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -365,6 +376,7 @@ pub async fn get_reminders(id: u64, ctx: &State<Context>, pool: &State<Pool<MySq
|
|||||||
reminders.embed_image_url,
|
reminders.embed_image_url,
|
||||||
reminders.embed_thumbnail_url,
|
reminders.embed_thumbnail_url,
|
||||||
reminders.embed_title,
|
reminders.embed_title,
|
||||||
|
reminders.embed_fields,
|
||||||
reminders.enabled,
|
reminders.enabled,
|
||||||
reminders.expires,
|
reminders.expires,
|
||||||
reminders.interval_seconds,
|
reminders.interval_seconds,
|
||||||
@ -421,6 +433,7 @@ pub async fn edit_reminder(
|
|||||||
embed_image_url,
|
embed_image_url,
|
||||||
embed_thumbnail_url,
|
embed_thumbnail_url,
|
||||||
embed_title,
|
embed_title,
|
||||||
|
embed_fields,
|
||||||
enabled,
|
enabled,
|
||||||
expires,
|
expires,
|
||||||
interval_seconds,
|
interval_seconds,
|
||||||
@ -507,6 +520,7 @@ pub async fn edit_reminder(
|
|||||||
reminders.embed_image_url,
|
reminders.embed_image_url,
|
||||||
reminders.embed_thumbnail_url,
|
reminders.embed_thumbnail_url,
|
||||||
reminders.embed_title,
|
reminders.embed_title,
|
||||||
|
reminders.embed_fields,
|
||||||
reminders.enabled,
|
reminders.enabled,
|
||||||
reminders.expires,
|
reminders.expires,
|
||||||
reminders.interval_seconds,
|
reminders.interval_seconds,
|
||||||
|
@ -6,7 +6,7 @@ use rocket::{http::CookieJar, response::Redirect};
|
|||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serenity::{http::Http, model::id::ChannelId};
|
use serenity::{http::Http, model::id::ChannelId};
|
||||||
use sqlx::Executor;
|
use sqlx::{types::Json, Executor};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{CHARACTERS, DEFAULT_AVATAR},
|
consts::{CHARACTERS, DEFAULT_AVATAR},
|
||||||
@ -30,39 +30,7 @@ fn channel_default() -> u64 {
|
|||||||
pub struct EmbedField {
|
pub struct EmbedField {
|
||||||
title: String,
|
title: String,
|
||||||
value: String,
|
value: String,
|
||||||
}
|
inline: bool,
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct JsonReminder {
|
|
||||||
attachment: Option<String>,
|
|
||||||
attachment_name: Option<String>,
|
|
||||||
avatar: Option<String>,
|
|
||||||
#[serde(with = "string")]
|
|
||||||
channel: u64,
|
|
||||||
content: String,
|
|
||||||
embed_author: String,
|
|
||||||
embed_author_url: Option<String>,
|
|
||||||
embed_color: u32,
|
|
||||||
embed_description: String,
|
|
||||||
embed_footer: String,
|
|
||||||
embed_footer_url: Option<String>,
|
|
||||||
embed_image_url: Option<String>,
|
|
||||||
embed_thumbnail_url: Option<String>,
|
|
||||||
embed_title: String,
|
|
||||||
embed_fields: Vec<EmbedField>,
|
|
||||||
enabled: bool,
|
|
||||||
expires: Option<NaiveDateTime>,
|
|
||||||
interval_seconds: Option<u32>,
|
|
||||||
interval_months: Option<u32>,
|
|
||||||
#[serde(default = "name_default")]
|
|
||||||
name: String,
|
|
||||||
pin: bool,
|
|
||||||
restartable: bool,
|
|
||||||
tts: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
uid: String,
|
|
||||||
username: Option<String>,
|
|
||||||
utc_time: NaiveDateTime,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -82,6 +50,7 @@ pub struct Reminder {
|
|||||||
embed_image_url: Option<String>,
|
embed_image_url: Option<String>,
|
||||||
embed_thumbnail_url: Option<String>,
|
embed_thumbnail_url: Option<String>,
|
||||||
embed_title: String,
|
embed_title: String,
|
||||||
|
embed_fields: Option<Json<Vec<EmbedField>>>,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
expires: Option<NaiveDateTime>,
|
expires: Option<NaiveDateTime>,
|
||||||
interval_seconds: Option<u32>,
|
interval_seconds: Option<u32>,
|
||||||
@ -130,7 +99,7 @@ pub struct PatchReminder {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
embed_title: Unset<String>,
|
embed_title: Unset<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
embed_fields: Unset<EmbedField>,
|
embed_fields: Unset<Json<Vec<EmbedField>>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
enabled: Unset<bool>,
|
enabled: Unset<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -11,14 +11,13 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
model::{
|
model::{
|
||||||
id::{GuildId, RoleId, UserId},
|
id::{GuildId, RoleId},
|
||||||
permissions::Permissions,
|
permissions::Permissions,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
use super::Reminder;
|
use crate::consts::DISCORD_API;
|
||||||
use crate::{consts::DISCORD_API, routes::dashboard::DeleteReminder};
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct UserInfo {
|
struct UserInfo {
|
||||||
@ -164,241 +163,3 @@ pub async fn get_user_guilds(cookies: &CookieJar<'_>, reqwest_client: &State<Cli
|
|||||||
json!({"error": "Not authorized"})
|
json!({"error": "Not authorized"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/api/user/reminders", data = "<reminder>")]
|
|
||||||
pub async fn create_reminder(
|
|
||||||
reminder: Json<Reminder>,
|
|
||||||
_ctx: &State<Context>,
|
|
||||||
pool: &State<Pool<MySql>>,
|
|
||||||
) -> JsonValue {
|
|
||||||
match sqlx::query!(
|
|
||||||
"INSERT INTO reminders (
|
|
||||||
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 (
|
|
||||||
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` = ?
|
|
||||||
)",
|
|
||||||
reminder.avatar,
|
|
||||||
reminder.content,
|
|
||||||
reminder.embed_author,
|
|
||||||
reminder.embed_author_url,
|
|
||||||
reminder.embed_color,
|
|
||||||
reminder.embed_description,
|
|
||||||
reminder.embed_footer,
|
|
||||||
reminder.embed_footer_url,
|
|
||||||
reminder.embed_image_url,
|
|
||||||
reminder.embed_thumbnail_url,
|
|
||||||
reminder.embed_title,
|
|
||||||
reminder.enabled,
|
|
||||||
reminder.expires,
|
|
||||||
reminder.interval_seconds,
|
|
||||||
reminder.interval_months,
|
|
||||||
reminder.name,
|
|
||||||
reminder.pin,
|
|
||||||
reminder.restartable,
|
|
||||||
reminder.tts,
|
|
||||||
reminder.username,
|
|
||||||
reminder.utc_time,
|
|
||||||
)
|
|
||||||
.execute(pool.inner())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {
|
|
||||||
json!({})
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Error in `create_reminder`: {:?}", e);
|
|
||||||
|
|
||||||
json!({"error": "Could not create reminder"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/api/user/reminders")]
|
|
||||||
pub async fn get_reminders(
|
|
||||||
pool: &State<Pool<MySql>>,
|
|
||||||
cookies: &CookieJar<'_>,
|
|
||||||
ctx: &State<Context>,
|
|
||||||
) -> JsonValue {
|
|
||||||
if let Some(user_id) =
|
|
||||||
cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten()
|
|
||||||
{
|
|
||||||
let query_res = sqlx::query!(
|
|
||||||
"SELECT channel FROM channels INNER JOIN users ON users.dm_channel = channels.id WHERE users.user = ?",
|
|
||||||
user_id
|
|
||||||
)
|
|
||||||
.fetch_one(pool.inner())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let dm_channel = if let Ok(query) = query_res {
|
|
||||||
Some(query.channel)
|
|
||||||
} else {
|
|
||||||
if let Ok(dm_channel) = UserId(user_id).create_dm_channel(&ctx.inner()).await {
|
|
||||||
Some(dm_channel.id.as_u64().to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(channel_id) = dm_channel {
|
|
||||||
let reminders = sqlx::query_as!(
|
|
||||||
Reminder,
|
|
||||||
r#"SELECT
|
|
||||||
reminders.attachment,
|
|
||||||
reminders.attachment_name,
|
|
||||||
reminders.avatar,
|
|
||||||
channels.channel,
|
|
||||||
reminders.content,
|
|
||||||
reminders.embed_author,
|
|
||||||
reminders.embed_author_url,
|
|
||||||
reminders.embed_color,
|
|
||||||
reminders.embed_description,
|
|
||||||
reminders.embed_footer,
|
|
||||||
reminders.embed_footer_url,
|
|
||||||
reminders.embed_image_url,
|
|
||||||
reminders.embed_thumbnail_url,
|
|
||||||
reminders.embed_title,
|
|
||||||
reminders.enabled as "enabled:_",
|
|
||||||
reminders.expires,
|
|
||||||
reminders.interval_seconds,
|
|
||||||
reminders.interval_months,
|
|
||||||
reminders.name,
|
|
||||||
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 = ?"#,
|
|
||||||
channel_id
|
|
||||||
)
|
|
||||||
.fetch_all(pool.inner())
|
|
||||||
.await
|
|
||||||
.unwrap_or(vec![]);
|
|
||||||
|
|
||||||
json!(reminders)
|
|
||||||
} else {
|
|
||||||
json!({"error": "User's DM channel could not be determined"})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
json!({"error": "Not authorized"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[put("/api/user/reminders", data = "<reminder>")]
|
|
||||||
pub async fn overwrite_reminder(reminder: Json<Reminder>, pool: &State<Pool<MySql>>) -> JsonValue {
|
|
||||||
match sqlx::query!(
|
|
||||||
"UPDATE reminders SET
|
|
||||||
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` = ?
|
|
||||||
WHERE uid = ?",
|
|
||||||
reminder.avatar,
|
|
||||||
reminder.content,
|
|
||||||
reminder.embed_author,
|
|
||||||
reminder.embed_author_url,
|
|
||||||
reminder.embed_color,
|
|
||||||
reminder.embed_description,
|
|
||||||
reminder.embed_footer,
|
|
||||||
reminder.embed_footer_url,
|
|
||||||
reminder.embed_image_url,
|
|
||||||
reminder.embed_thumbnail_url,
|
|
||||||
reminder.embed_title,
|
|
||||||
reminder.enabled,
|
|
||||||
reminder.expires,
|
|
||||||
reminder.interval_seconds,
|
|
||||||
reminder.interval_months,
|
|
||||||
reminder.name,
|
|
||||||
reminder.pin,
|
|
||||||
reminder.restartable,
|
|
||||||
reminder.tts,
|
|
||||||
reminder.username,
|
|
||||||
reminder.utc_time,
|
|
||||||
reminder.uid
|
|
||||||
)
|
|
||||||
.execute(pool.inner())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {
|
|
||||||
json!({})
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Error in `overwrite_reminder`: {:?}", e);
|
|
||||||
|
|
||||||
json!({"error": "Could not modify reminder"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[delete("/api/user/reminders", data = "<reminder>")]
|
|
||||||
pub async fn delete_reminder(
|
|
||||||
reminder: Json<DeleteReminder>,
|
|
||||||
pool: &State<Pool<MySql>>,
|
|
||||||
) -> JsonValue {
|
|
||||||
if sqlx::query!("DELETE FROM reminders WHERE uid = ?", reminder.uid)
|
|
||||||
.execute(pool.inner())
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
json!({})
|
|
||||||
} else {
|
|
||||||
json!({"error": "Could not delete reminder"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,8 +5,6 @@ function get_interval(element) {
|
|||||||
let minutes = element.querySelector('input[name="interval_minutes"]').value;
|
let minutes = element.querySelector('input[name="interval_minutes"]').value;
|
||||||
let seconds = element.querySelector('input[name="interval_seconds"]').value;
|
let seconds = element.querySelector('input[name="interval_seconds"]').value;
|
||||||
|
|
||||||
console.log(minutes);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
months: parseInt(months) || null,
|
months: parseInt(months) || null,
|
||||||
seconds:
|
seconds:
|
||||||
@ -53,7 +51,7 @@ function update_interval(element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let $intervalGroup = document.querySelector(".interval-group");
|
const $intervalGroup = document.querySelector(".interval-group");
|
||||||
|
|
||||||
document.querySelector(".interval-group").addEventListener(
|
document.querySelector(".interval-group").addEventListener(
|
||||||
"blur",
|
"blur",
|
||||||
@ -73,9 +71,13 @@ document.addEventListener("remindersLoaded", (event) => {
|
|||||||
for (reminder of event.detail) {
|
for (reminder of event.detail) {
|
||||||
let $intervalGroup = reminder.node.querySelector(".interval-group");
|
let $intervalGroup = reminder.node.querySelector(".interval-group");
|
||||||
|
|
||||||
$intervalGroup.addEventListener("blur", (ev) => {
|
$intervalGroup.addEventListener(
|
||||||
if (ev.target.nodeName !== "BUTTON") update_interval($intervalGroup);
|
"blur",
|
||||||
});
|
(ev) => {
|
||||||
|
if (ev.target.nodeName !== "BUTTON") update_interval($intervalGroup);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
$intervalGroup.querySelector("button.clear").addEventListener("click", () => {
|
$intervalGroup.querySelector("button.clear").addEventListener("click", () => {
|
||||||
$intervalGroup.querySelectorAll("input").forEach((el) => {
|
$intervalGroup.querySelectorAll("input").forEach((el) => {
|
||||||
|
@ -5,10 +5,10 @@ const $colorPickerModal = document.querySelector("div#pickColorModal");
|
|||||||
const $colorPickerInput = $colorPickerModal.querySelector("input");
|
const $colorPickerInput = $colorPickerModal.querySelector("input");
|
||||||
const $deleteReminderBtn = document.querySelector("#delete-reminder-confirm");
|
const $deleteReminderBtn = document.querySelector("#delete-reminder-confirm");
|
||||||
const $reminderTemplate = document.querySelector("template#guildReminder");
|
const $reminderTemplate = document.querySelector("template#guildReminder");
|
||||||
|
const $embedFieldTemplate = document.querySelector("template#embedFieldTemplate");
|
||||||
|
|
||||||
let channels;
|
let channels;
|
||||||
let roles;
|
let roles;
|
||||||
let guild_id;
|
|
||||||
|
|
||||||
function colorToInt(r, g, b) {
|
function colorToInt(r, g, b) {
|
||||||
return (r << 16) + (g << 8) + b;
|
return (r << 16) + (g << 8) + b;
|
||||||
@ -118,10 +118,10 @@ async function fetch_reminders(guild_id) {
|
|||||||
for (let reminder of data) {
|
for (let reminder of data) {
|
||||||
let newFrame = $reminderTemplate.content.cloneNode(true);
|
let newFrame = $reminderTemplate.content.cloneNode(true);
|
||||||
|
|
||||||
newFrame.querySelector(".reminderContent").dataset.uid =
|
newFrame.querySelector(".reminderContent").dataset["uid"] =
|
||||||
reminder["uid"];
|
reminder["uid"];
|
||||||
|
|
||||||
render_reminder(reminder, newFrame);
|
deserialize_reminder(reminder, newFrame);
|
||||||
|
|
||||||
$reminderBox.appendChild(newFrame);
|
$reminderBox.appendChild(newFrame);
|
||||||
|
|
||||||
@ -137,7 +137,87 @@ async function fetch_reminders(guild_id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_reminder(reminder, frame) {
|
async function serialize_reminder(node) {
|
||||||
|
let interval = get_interval(node);
|
||||||
|
|
||||||
|
let rgb_color = window.getComputedStyle(
|
||||||
|
node.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(
|
||||||
|
node.querySelector('input[name="time"]').value
|
||||||
|
).setZone("UTC");
|
||||||
|
|
||||||
|
if (utc_time.invalid) {
|
||||||
|
return { error: "Time provided invalid." };
|
||||||
|
}
|
||||||
|
|
||||||
|
let fields = [
|
||||||
|
...node.querySelectorAll("div.embed-multifield-box div.embed-field-box"),
|
||||||
|
]
|
||||||
|
.map((el) => {
|
||||||
|
return {
|
||||||
|
title: el.querySelector("textarea#embedFieldTitle").value,
|
||||||
|
value: el.querySelector("textarea#embedFieldValue").value,
|
||||||
|
inline: el.dataset["inlined"] === "1",
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(({ title, value, inline }) => title.length + value.length > 0);
|
||||||
|
|
||||||
|
let attachment = null;
|
||||||
|
let attachment_name = null;
|
||||||
|
|
||||||
|
if (node.querySelector('input[name="attachment"]').files.length > 0) {
|
||||||
|
let file = node.querySelector('input[name="attachment"]').files[0];
|
||||||
|
|
||||||
|
attachment = await new Promise((resolve) => {
|
||||||
|
let fileReader = new FileReader();
|
||||||
|
fileReader.onload = (e) => resolve(fileReader.result);
|
||||||
|
fileReader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
attachment = attachment.split(",")[1];
|
||||||
|
attachment_name = file.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reminderContent = node.closest(".reminderContent");
|
||||||
|
|
||||||
|
return {
|
||||||
|
// if we're creating a reminder, ignore this field
|
||||||
|
uid: reminderContent !== null ? reminderContent.dataset["uid"] : "",
|
||||||
|
// if we're editing a reminder, ignore this field
|
||||||
|
enabled: reminderContent !== null ? null : true,
|
||||||
|
restartable: false,
|
||||||
|
attachment: attachment,
|
||||||
|
attachment_name: attachment_name,
|
||||||
|
avatar: has_source(node.querySelector("img.discord-avatar").src),
|
||||||
|
channel: node.querySelector("select.channel-selector").value,
|
||||||
|
content: node.querySelector('textarea[name="content"]').value,
|
||||||
|
embed_author_url: has_source(node.querySelector("img.embed_author_url").src),
|
||||||
|
embed_author: node.querySelector('textarea[name="embed_author"]').value,
|
||||||
|
embed_color: color,
|
||||||
|
embed_description: node.querySelector('textarea[name="embed_description"]').value,
|
||||||
|
embed_footer: node.querySelector('textarea[name="embed_footer"]').value,
|
||||||
|
embed_footer_url: has_source(node.querySelector("img.embed_footer_url").src),
|
||||||
|
embed_image_url: has_source(node.querySelector("img.embed_image_url").src),
|
||||||
|
embed_thumbnail_url: has_source(
|
||||||
|
node.querySelector("img.embed_thumbnail_url").src
|
||||||
|
),
|
||||||
|
embed_title: node.querySelector('textarea[name="embed_title"]').value,
|
||||||
|
embed_fields: fields,
|
||||||
|
expires: null,
|
||||||
|
interval_seconds: interval.seconds,
|
||||||
|
interval_months: interval.months,
|
||||||
|
name: node.querySelector('input[name="name"]').value,
|
||||||
|
pin: node.querySelector('input[name="pin"]').checked,
|
||||||
|
tts: node.querySelector('input[name="tts"]').checked,
|
||||||
|
username: node.querySelector('input[name="username"]').value,
|
||||||
|
utc_time: utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserialize_reminder(reminder, frame) {
|
||||||
// populate channels
|
// populate channels
|
||||||
set_channels(frame.querySelector("select.channel-selector"));
|
set_channels(frame.querySelector("select.channel-selector"));
|
||||||
|
|
||||||
@ -161,10 +241,25 @@ function render_reminder(reminder, frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastChild = frame.querySelector("div.embed-multifield-box .embed-field-box");
|
||||||
|
|
||||||
|
for (let field of reminder["embed_fields"]) {
|
||||||
|
let embed_field = $embedFieldTemplate.content.cloneNode(true);
|
||||||
|
embed_field.querySelector("textarea.discord-field-title").value = field["title"];
|
||||||
|
embed_field.querySelector("textarea.discord-field-value").value = field["value"];
|
||||||
|
embed_field.querySelector(".embed-field-box").dataset["inlined"] = field["inline"]
|
||||||
|
? "1"
|
||||||
|
: "0";
|
||||||
|
|
||||||
|
frame
|
||||||
|
.querySelector("div.embed-multifield-box")
|
||||||
|
.insertBefore(embed_field, lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
if (reminder["interval_seconds"] !== null) update_interval(frame);
|
if (reminder["interval_seconds"] !== null) update_interval(frame);
|
||||||
|
|
||||||
let $enableBtn = frame.querySelector(".disable-enable");
|
let $enableBtn = frame.querySelector(".disable-enable");
|
||||||
$enableBtn.dataset.action = reminder["enabled"] ? "disable" : "enable";
|
$enableBtn.dataset["action"] = reminder["enabled"] ? "disable" : "enable";
|
||||||
|
|
||||||
let timeInput = frame.querySelector('input[name="time"]');
|
let timeInput = frame.querySelector('input[name="time"]');
|
||||||
let localTime = luxon.DateTime.fromISO(reminder["utc_time"], { zone: "UTC" }).setZone(
|
let localTime = luxon.DateTime.fromISO(reminder["utc_time"], { zone: "UTC" }).setZone(
|
||||||
@ -223,7 +318,7 @@ document.addEventListener("remindersLoaded", (event) => {
|
|||||||
|
|
||||||
const enableBtn = node.querySelector(".disable-enable");
|
const enableBtn = node.querySelector(".disable-enable");
|
||||||
enableBtn.addEventListener("click", () => {
|
enableBtn.addEventListener("click", () => {
|
||||||
let enable = enableBtn.dataset.action === "enable";
|
let enable = enableBtn.dataset["action"] === "enable";
|
||||||
|
|
||||||
fetch(`/dashboard/api/guild/${guild}/reminders`, {
|
fetch(`/dashboard/api/guild/${guild}/reminders`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
@ -237,7 +332,9 @@ document.addEventListener("remindersLoaded", (event) => {
|
|||||||
if (data.error) {
|
if (data.error) {
|
||||||
show_error(data.error);
|
show_error(data.error);
|
||||||
} else {
|
} else {
|
||||||
enableBtn.dataset.action = data["enabled"] ? "enable" : "disable";
|
enableBtn.dataset["action"] = data["enabled"]
|
||||||
|
? "enable"
|
||||||
|
: "disable";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -249,66 +346,17 @@ document.addEventListener("remindersLoaded", (event) => {
|
|||||||
|
|
||||||
const $saveBtn = node.querySelector("button.save-btn");
|
const $saveBtn = node.querySelector("button.save-btn");
|
||||||
|
|
||||||
$saveBtn.addEventListener("click", (event) => {
|
$saveBtn.addEventListener("click", async (event) => {
|
||||||
$saveBtn.querySelector("span.icon > i").classList = [
|
$saveBtn.querySelector("span.icon > i").classList = [
|
||||||
"fas fa-spinner fa-spin",
|
"fas fa-spinner fa-spin",
|
||||||
];
|
];
|
||||||
|
|
||||||
let interval = get_interval(node);
|
let reminder = await serialize_reminder(node);
|
||||||
|
if (reminder.error) {
|
||||||
|
show_error(reminder.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let rgb_color = window.getComputedStyle(
|
|
||||||
node.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(
|
|
||||||
node.querySelector('input[name="time"]').value
|
|
||||||
).setZone("UTC");
|
|
||||||
|
|
||||||
let fields = node.querySelectorAll(".embed-field-box", (el) => {
|
|
||||||
return {
|
|
||||||
title: el.querySelector('input[name="embed_field_title[]"]').value,
|
|
||||||
value: el.querySelector('input[name="embed_field_value[]"]').value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let reminder = {
|
|
||||||
uid: node.closest(".reminderContent").dataset["uid"],
|
|
||||||
avatar: has_source(node.querySelector("img.discord-avatar").src),
|
|
||||||
channel: node.querySelector("select.channel-selector").value,
|
|
||||||
content: node.querySelector('textarea[name="content"]').value,
|
|
||||||
embed_author_url: has_source(
|
|
||||||
node.querySelector("img.embed_author_url").src
|
|
||||||
),
|
|
||||||
embed_author: node.querySelector('textarea[name="embed_author"]').value,
|
|
||||||
embed_color: color,
|
|
||||||
embed_description: node.querySelector(
|
|
||||||
'textarea[name="embed_description"]'
|
|
||||||
).value,
|
|
||||||
embed_footer: node.querySelector('textarea[name="embed_footer"]').value,
|
|
||||||
embed_footer_url: has_source(
|
|
||||||
node.querySelector("img.embed_footer_url").src
|
|
||||||
),
|
|
||||||
embed_image_url: has_source(
|
|
||||||
node.querySelector("img.embed_image_url").src
|
|
||||||
),
|
|
||||||
embed_thumbnail_url: has_source(
|
|
||||||
node.querySelector("img.embed_thumbnail_url").src
|
|
||||||
),
|
|
||||||
embed_title: node.querySelector('textarea[name="embed_title"]').value,
|
|
||||||
embed_fields: fields,
|
|
||||||
expires: null,
|
|
||||||
interval_seconds: interval.seconds,
|
|
||||||
interval_months: interval.months,
|
|
||||||
name: node.querySelector('input[name="name"]').value,
|
|
||||||
pin: node.querySelector('input[name="pin"]').checked,
|
|
||||||
tts: node.querySelector('input[name="tts"]').checked,
|
|
||||||
username: node.querySelector('input[name="username"]').value,
|
|
||||||
utc_time: utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// send to server
|
|
||||||
let guild = document.querySelector(".guildList a.is-active").dataset["guild"];
|
let guild = document.querySelector(".guildList a.is-active").dataset["guild"];
|
||||||
|
|
||||||
fetch(`/dashboard/api/guild/${guild}/reminders`, {
|
fetch(`/dashboard/api/guild/${guild}/reminders`, {
|
||||||
@ -319,7 +367,9 @@ document.addEventListener("remindersLoaded", (event) => {
|
|||||||
body: JSON.stringify(reminder),
|
body: JSON.stringify(reminder),
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => console.log(data));
|
.then((data) => {
|
||||||
|
for (let error of data.errors) show_error(error);
|
||||||
|
});
|
||||||
|
|
||||||
$saveBtn.querySelector("span.icon > i").classList = ["fas fa-check"];
|
$saveBtn.querySelector("span.icon > i").classList = ["fas fa-check"];
|
||||||
|
|
||||||
@ -384,7 +434,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
document.querySelectorAll(".navbar-burger").forEach((el) => {
|
document.querySelectorAll(".navbar-burger").forEach((el) => {
|
||||||
el.addEventListener("click", () => {
|
el.addEventListener("click", () => {
|
||||||
const target = el.dataset.target;
|
const target = el.dataset["target"];
|
||||||
const $target = document.getElementById(target);
|
const $target = document.getElementById(target);
|
||||||
|
|
||||||
el.classList.toggle("is-active");
|
el.classList.toggle("is-active");
|
||||||
@ -434,8 +484,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
$anchor.addEventListener("click", async (e) => {
|
$anchor.addEventListener("click", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
guild_id = guild.id;
|
|
||||||
|
|
||||||
const event = new CustomEvent("guildSwitched", {
|
const event = new CustomEvent("guildSwitched", {
|
||||||
detail: {
|
detail: {
|
||||||
guild_name: guild.name,
|
guild_name: guild.name,
|
||||||
@ -488,35 +536,6 @@ let $createBtn = $createReminder.querySelector("button#createReminder");
|
|||||||
$createBtn.addEventListener("click", async () => {
|
$createBtn.addEventListener("click", async () => {
|
||||||
$createBtn.querySelector("span.icon > i").classList = ["fas fa-spinner fa-spin"];
|
$createBtn.querySelector("span.icon > i").classList = ["fas fa-spinner fa-spin"];
|
||||||
|
|
||||||
let interval = get_interval($createReminder);
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
if (utc_time.invalid) {
|
|
||||||
show_error("Time provided invalid.");
|
|
||||||
$createBtn.querySelector("span.icon > i").classList = ["fas fa-sparkles"];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fields = [
|
|
||||||
...$createReminder.querySelectorAll(
|
|
||||||
"div.embed-multifield-box div.embed-field-box"
|
|
||||||
),
|
|
||||||
].map((el) => {
|
|
||||||
return {
|
|
||||||
title: el.querySelector("textarea#embedFieldTitle").value,
|
|
||||||
value: el.querySelector("textarea#embedFieldValue").value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let attachment = null;
|
let attachment = null;
|
||||||
let attachment_name = null;
|
let attachment_name = null;
|
||||||
|
|
||||||
@ -532,44 +551,12 @@ $createBtn.addEventListener("click", async () => {
|
|||||||
attachment_name = file.name;
|
attachment_name = file.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reminder = {
|
let reminder = await serialize_reminder($createReminder);
|
||||||
attachment: attachment,
|
if (reminder.error) {
|
||||||
attachment_name: attachment_name,
|
show_error(reminder.error);
|
||||||
avatar: has_source($createReminder.querySelector("img.discord-avatar").src),
|
return;
|
||||||
channel: $createReminder.querySelector("select.channel-selector").value,
|
}
|
||||||
content: $createReminder.querySelector("textarea#messageContent").value,
|
|
||||||
embed_author_url: has_source(
|
|
||||||
$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: has_source(
|
|
||||||
$createReminder.querySelector("img.embed_footer_url").src
|
|
||||||
),
|
|
||||||
embed_image_url: has_source(
|
|
||||||
$createReminder.querySelector("img.embed_image_url").src
|
|
||||||
),
|
|
||||||
embed_thumbnail_url: has_source(
|
|
||||||
$createReminder.querySelector("img.embed_thumbnail_url").src
|
|
||||||
),
|
|
||||||
embed_title: $createReminder.querySelector("textarea#embedTitle").value,
|
|
||||||
embed_fields: fields,
|
|
||||||
enabled: true,
|
|
||||||
expires: null,
|
|
||||||
interval_seconds: interval.seconds,
|
|
||||||
interval_months: interval.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"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// send to server
|
|
||||||
let guild = document.querySelector(".guildList a.is-active").dataset["guild"];
|
let guild = document.querySelector(".guildList a.is-active").dataset["guild"];
|
||||||
|
|
||||||
fetch(`/dashboard/api/guild/${guild}/reminders`, {
|
fetch(`/dashboard/api/guild/${guild}/reminders`, {
|
||||||
@ -589,7 +576,7 @@ $createBtn.addEventListener("click", async () => {
|
|||||||
|
|
||||||
newFrame.querySelector(".reminderContent").dataset["uid"] = data["uid"];
|
newFrame.querySelector(".reminderContent").dataset["uid"] = data["uid"];
|
||||||
|
|
||||||
render_reminder(data, newFrame);
|
deserialize_reminder(data, newFrame);
|
||||||
|
|
||||||
$reminderBox.appendChild(newFrame);
|
$reminderBox.appendChild(newFrame);
|
||||||
|
|
||||||
@ -682,19 +669,10 @@ document.addEventListener("remindersLoaded", () => {
|
|||||||
window.getComputedStyle($discordFrame).borderLeftColor;
|
window.getComputedStyle($discordFrame).borderLeftColor;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll(".embed-field-box button.inline-btn").forEach((el) => {
|
|
||||||
el.addEventListener("click", (ev) => {
|
|
||||||
let inlined = ev.target.closest(".embed-field-box").dataset["inlined"];
|
|
||||||
ev.target.closest(".embed-field-box").dataset["inlined"] =
|
|
||||||
inlined === "1" ? "0" : "1";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function check_embed_fields() {
|
function check_embed_fields() {
|
||||||
document.querySelectorAll(".embed-field-box").forEach((element) => {
|
document.querySelectorAll(".embed-field-box").forEach((element) => {
|
||||||
const $template = document.querySelector("template#embedFieldTemplate");
|
|
||||||
const $titleInput = element.querySelector(".discord-field-title");
|
const $titleInput = element.querySelector(".discord-field-title");
|
||||||
const $valueInput = element.querySelector(".discord-field-value");
|
const $valueInput = element.querySelector(".discord-field-value");
|
||||||
|
|
||||||
@ -726,16 +704,7 @@ function check_embed_fields() {
|
|||||||
$valueInput.value !== "" &&
|
$valueInput.value !== "" &&
|
||||||
element.nextElementSibling === null
|
element.nextElementSibling === null
|
||||||
) {
|
) {
|
||||||
const $clone = $template.content.cloneNode(true);
|
const $clone = $embedFieldTemplate.content.cloneNode(true);
|
||||||
$clone
|
|
||||||
.querySelector(".embed-field-box button.inline-btn")
|
|
||||||
.addEventListener("click", (ev) => {
|
|
||||||
let inlined =
|
|
||||||
ev.target.closest(".embed-field-box").dataset["inlined"];
|
|
||||||
ev.target.closest(".embed-field-box").dataset["inlined"] =
|
|
||||||
inlined == "1" ? "0" : "1";
|
|
||||||
});
|
|
||||||
|
|
||||||
element.parentElement.append($clone);
|
element.parentElement.append($clone);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -746,16 +715,7 @@ function check_embed_fields() {
|
|||||||
$valueInput.value !== "" &&
|
$valueInput.value !== "" &&
|
||||||
element.nextElementSibling === null
|
element.nextElementSibling === null
|
||||||
) {
|
) {
|
||||||
const $clone = $template.content.cloneNode(true);
|
const $clone = $embedFieldTemplate.content.cloneNode(true);
|
||||||
$clone
|
|
||||||
.querySelector(".embed-field-box button.inline-btn")
|
|
||||||
.addEventListener("click", (ev) => {
|
|
||||||
let inlined =
|
|
||||||
ev.target.closest(".embed-field-box").dataset["inlined"];
|
|
||||||
ev.target.closest(".embed-field-box").dataset["inlined"] =
|
|
||||||
inlined == "1" ? "0" : "1";
|
|
||||||
});
|
|
||||||
|
|
||||||
element.parentElement.append($clone);
|
element.parentElement.append($clone);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -780,3 +740,11 @@ document.addEventListener("DOMNodeInserted", () => {
|
|||||||
check_embed_fields();
|
check_embed_fields();
|
||||||
resize_textareas();
|
resize_textareas();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", (ev) => {
|
||||||
|
if (ev.target.closest("button.inline-btn") !== null) {
|
||||||
|
let inlined = ev.target.closest(".embed-field-box").dataset["inlined"];
|
||||||
|
ev.target.closest(".embed-field-box").dataset["inlined"] =
|
||||||
|
inlined == "1" ? "0" : "1";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -140,6 +140,23 @@
|
|||||||
<button class="modal-close is-large close-modal" aria-label="close"></button>
|
<button class="modal-close is-large close-modal" aria-label="close"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="dataManagerModal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<label class="modal-card-title" for="urlInput">Import/Export Manager <a href="/help/iemanager"><span><i class="fa fa-question-circle"></i></span></a></label>
|
||||||
|
<button class="delete close-modal" aria-label="close"></button>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<button class="button is-success is-outlined" id="import-data">Import Data</button>
|
||||||
|
<button class="button is-success" id="export-data">Export Data</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<button class="modal-close is-large close-modal" aria-label="close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal" id="deleteReminderModal">
|
<div class="modal" id="deleteReminderModal">
|
||||||
<div class="modal-background"></div>
|
<div class="modal-background"></div>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
@ -182,12 +199,15 @@
|
|||||||
<ul class="menu-list guildList">
|
<ul class="menu-list guildList">
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<div class="aside-footer" style="position: fixed; bottom: 0;">
|
<div class="aside-footer" style="position: fixed; bottom: 0; width: 226px;">
|
||||||
<p class="menu-label">
|
<p class="menu-label">
|
||||||
Settings
|
Settings
|
||||||
</p>
|
</p>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
|
<a class="show-modal" data-modal="dataManagerModal">
|
||||||
|
<span class="icon"><i class="fas fa-exchange"></i></span> Import/Export
|
||||||
|
</a>
|
||||||
<a class="show-modal" data-modal="chooseTimezoneModal">
|
<a class="show-modal" data-modal="chooseTimezoneModal">
|
||||||
<span class="icon"><i class="fas fa-map-marked"></i></span> Timezone
|
<span class="icon"><i class="fas fa-map-marked"></i></span> Timezone
|
||||||
</a>
|
</a>
|
||||||
@ -211,16 +231,6 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<aside class="menu" style="display: flex; flex-direction: column; flex-grow: 1;">
|
<aside class="menu" style="display: flex; flex-direction: column; flex-grow: 1;">
|
||||||
<p class="menu-label">
|
|
||||||
Personal
|
|
||||||
</p>
|
|
||||||
<ul class="menu-list">
|
|
||||||
<li>
|
|
||||||
<a class="switch-pane" data-pane="personal">
|
|
||||||
<span class="icon"><i class="fas fa-map-pin"></i></span> @%username%
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p class="menu-label">
|
<p class="menu-label">
|
||||||
Servers
|
Servers
|
||||||
</p>
|
</p>
|
||||||
@ -233,6 +243,9 @@
|
|||||||
</p>
|
</p>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
|
<a class="show-modal" data-modal="dataManagerModal">
|
||||||
|
<span class="icon"><i class="fas fa-exchange"></i></span> Import/Export
|
||||||
|
</a>
|
||||||
<a class="show-modal" data-modal="chooseTimezoneModal">
|
<a class="show-modal" data-modal="chooseTimezoneModal">
|
||||||
<span class="icon"><i class="fas fa-map-marked"></i></span> Timezone
|
<span class="icon"><i class="fas fa-map-marked"></i></span> Timezone
|
||||||
</a>
|
</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user