1 Commits

Author SHA1 Message Date
53aa5ebc55 Configure things 2023-08-16 16:48:36 +01:00
31 changed files with 1634 additions and 1630 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@
/venv /venv
.cargo .cargo
/.idea /.idea
node_modules/

1389
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,18 @@
[package] [package]
name = "reminder-rs" name = "reminder-rs"
version = "1.6.40" version = "1.6.36"
authors = ["Jude Southworth <judesouthworth@pm.me>"] authors = ["Jude Southworth <judesouthworth@pm.me>"]
edition = "2021" edition = "2021"
license = "AGPL-3.0 only" license = "AGPL-3.0 only"
description = "Reminder Bot for Discord, now in Rust" description = "Reminder Bot for Discord, now in Rust"
[dependencies] [dependencies]
poise = "0.5" poise = "0.5.5"
dotenv = "0.15" dotenv = "0.15"
tokio = { version = "1", features = ["process", "full"] } tokio = { version = "1", features = ["process", "full"] }
reqwest = "0.11" reqwest = "0.11"
lazy-regex = "3.0" lazy-regex = "2.3.0"
regex = "1.9" regex = "1.6"
log = "0.4" log = "0.4"
env_logger = "0.10" env_logger = "0.10"
chrono = "0.4" chrono = "0.4"
@ -25,7 +25,7 @@ serde_repr = "0.1"
rmp-serde = "1.1" rmp-serde = "1.1"
rand = "0.8" rand = "0.8"
levenshtein = "1.0" levenshtein = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]} sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "migrate"]}
base64 = "0.21.0" base64 = "0.21.0"
[dependencies.postman] [dependencies.postman]
@ -42,7 +42,7 @@ assets = [
["target/release/reminder-rs", "usr/bin/reminder-rs", "755"], ["target/release/reminder-rs", "usr/bin/reminder-rs", "755"],
["conf/default.env", "etc/reminder-rs/config.env", "600"], ["conf/default.env", "etc/reminder-rs/config.env", "600"],
["conf/Rocket.toml", "etc/reminder-rs/Rocket.toml", "600"], ["conf/Rocket.toml", "etc/reminder-rs/Rocket.toml", "600"],
["web/static/**/*", "lib/reminder-rs/static", "644"], ["$OUT_DIR/web/static/**/*", "lib/reminder-rs/static", "644"],
["web/templates/**/*", "lib/reminder-rs/templates", "644"], ["web/templates/**/*", "lib/reminder-rs/templates", "644"],
["healthcheck", "lib/reminder-rs/healthcheck", "755"], ["healthcheck", "lib/reminder-rs/healthcheck", "755"],
["cron.d/reminder_health", "etc/cron.d/reminder_health", "644"], ["cron.d/reminder_health", "etc/cron.d/reminder_health", "644"],

View File

@ -1,3 +1,99 @@
#[cfg(not(debug_assertions))]
use std::{
env, fs,
fs::{create_dir_all, DirEntry, File},
io,
io::Write,
path::Path,
process::Command,
};
#[cfg(not(debug_assertions))]
fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> io::Result<()> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dirs(&path, cb)?;
} else {
cb(&entry);
}
}
}
Ok(())
}
#[cfg(not(debug_assertions))]
fn process_static(file: &DirEntry) {
let out_dir = env::var("OUT_DIR").unwrap();
let path = file.path();
let in_path = path.to_str().unwrap();
let art_path = format!("{}/{}", out_dir, in_path);
let art_dir = format!("{}/{}", out_dir, path.parent().unwrap().to_str().unwrap());
match path.extension().map(|o| o.to_str()).flatten() {
Some("ts") => {}
Some("js") => {
create_dir_all(art_dir).unwrap();
if art_path.ends_with(".min.js") {
Command::new("cp").arg(in_path).arg(art_path).spawn().expect("Could not copy");
} else {
let minified = Command::new("npx")
.arg("minify")
.arg(in_path)
.output()
.expect("Could not minify");
let mut fh = File::create(art_path).expect("Couldn't create file");
fh.write(&minified.stdout).unwrap();
}
}
Some("css") => {
create_dir_all(art_dir).unwrap();
if art_path.ends_with(".min.css") {
Command::new("cp").arg(in_path).arg(art_path).spawn().expect("Could not copy");
} else {
let minified = Command::new("npx")
.arg("minify")
.arg(in_path)
.output()
.expect("Could not minify");
let mut fh = File::create(art_path).expect("Couldn't create file");
fh.write(&minified.stdout).unwrap();
}
}
_ => {
create_dir_all(art_dir).unwrap();
Command::new("cp").arg(in_path).arg(art_path).spawn().expect("Could not copy");
}
}
}
// fn compile_tsc(file: &DirEntry) {
// if path.extension() == Some("ts") {
// let out_dir = env::var("OUT_DIR").unwrap();
// let path = file.path();
//
// Command::new("npx")
// .arg("tsc")
// .arg(in_path)
// .arg(art_path)
// .spawn()
// .expect("Could not compile");
// }
// }
fn main() { fn main() {
println!("cargo:rerun-if-changed=migrations"); println!("cargo:rerun-if-changed=migrations");
#[cfg(not(debug_assertions))]
visit_dirs("web/static".as_ref(), &process_static).unwrap();
// visit_dirs("web/static".as_ref(), &compile_tsc).unwrap();
} }

View File

@ -1,19 +0,0 @@
-- Drop existing constraint
ALTER TABLE `reminders` DROP CONSTRAINT `reminders_ibfk_1`;
ALTER TABLE `reminders` MODIFY COLUMN `channel_id` INT UNSIGNED;
ALTER TABLE `reminders` ADD COLUMN `guild_id` INT UNSIGNED;
ALTER TABLE `reminders`
ADD CONSTRAINT `guild_id_fk`
FOREIGN KEY (`guild_id`)
REFERENCES `guilds`(`id`)
ON DELETE CASCADE;
ALTER TABLE `reminders`
ADD CONSTRAINT `channel_id_fk`
FOREIGN KEY (`channel_id`)
REFERENCES `channels`(`id`)
ON DELETE SET NULL;
UPDATE `reminders` SET `guild_id` = (SELECT guilds.`id` FROM `channels` INNER JOIN `guilds` ON channels.guild_id = guilds.id WHERE reminders.channel_id = channels.id);

View File

@ -1,4 +0,0 @@
ALTER TABLE reminders ADD COLUMN `status_change_time` DATETIME;
-- This is a best-guess as to the status change time.
UPDATE reminders SET `status_change_time` = `utc_time` WHERE `status` != 'pending';

485
package-lock.json generated Normal file
View File

@ -0,0 +1,485 @@
{
"name": "reminder-rs",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"minify": "^10.3.0",
"prettier": "^3.0.1",
"tsc": "^2.0.4"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@putout/minify": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/@putout/minify/-/minify-1.49.0.tgz",
"integrity": "sha512-T/eS9rJC0tgq/s8uLpB0cpbsUaY7KSML3UbvPri2qjVCcEK/qwi8+lNWdp8VSyOWiC25Ntrt/DewOu6dXRX1ng==",
"dev": true,
"engines": {
"node": ">=16"
}
},
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/camel-case": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
"integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
"dev": true,
"dependencies": {
"pascal-case": "^3.1.2",
"tslib": "^2.0.3"
}
},
"node_modules/clean-css": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
"integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==",
"dev": true,
"dependencies": {
"source-map": "~0.6.0"
},
"engines": {
"node": ">= 10.0"
}
},
"node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/css-b64-images": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz",
"integrity": "sha512-TgQBEdP07adhrDfXvI5o6bHGukKBNMzp2Ngckc/6d09zpjD2gc1Hl3Ca1CKgb8FXjHi88+Phv2Uegs2kTL4zjg==",
"dev": true,
"bin": {
"css-b64-images": "bin/css-b64-images"
},
"engines": {
"node": "*"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/dot-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
"dev": true,
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/find-up": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
"integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
"dev": true,
"dependencies": {
"locate-path": "^7.1.0",
"path-exists": "^5.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/html-minifier-terser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
"integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
"dev": true,
"dependencies": {
"camel-case": "^4.1.2",
"clean-css": "~5.3.2",
"commander": "^10.0.0",
"entities": "^4.4.0",
"param-case": "^3.0.4",
"relateurl": "^0.2.7",
"terser": "^5.15.1"
},
"bin": {
"html-minifier-terser": "cli.js"
},
"engines": {
"node": "^14.13.1 || >=16.0.0"
}
},
"node_modules/jju": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
"integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
"dev": true
},
"node_modules/locate-path": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
"integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
"dev": true,
"dependencies": {
"p-locate": "^6.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lower-case": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
"dev": true,
"dependencies": {
"tslib": "^2.0.3"
}
},
"node_modules/minify": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/minify/-/minify-10.3.0.tgz",
"integrity": "sha512-eRkx2J1ykkGBVi1gI2sksmovWFzts+GYi2u3Jd/S5eNIkzj0pidciICsWRWdTKTLZVFUP7b6IvoAzasvQkMicg==",
"dev": true,
"dependencies": {
"@putout/minify": "^1.0.4",
"clean-css": "^5.0.1",
"css-b64-images": "~0.2.5",
"debug": "^4.1.0",
"find-up": "^6.1.0",
"html-minifier-terser": "^7.1.0",
"readjson": "^2.2.2",
"simport": "^1.2.0",
"try-catch": "^3.0.0",
"try-to-catch": "^3.0.0"
},
"bin": {
"minify": "bin/minify.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
"dev": true,
"dependencies": {
"lower-case": "^2.0.2",
"tslib": "^2.0.3"
}
},
"node_modules/p-limit": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
"dev": true,
"dependencies": {
"yocto-queue": "^1.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
"integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
"dev": true,
"dependencies": {
"p-limit": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/param-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
"integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
"dev": true,
"dependencies": {
"dot-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/pascal-case": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
"dev": true,
"dependencies": {
"no-case": "^3.0.4",
"tslib": "^2.0.3"
}
},
"node_modules/path-exists": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
"integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/prettier": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz",
"integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/readjson": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/readjson/-/readjson-2.2.2.tgz",
"integrity": "sha512-PdeC9tsmLWBiL8vMhJvocq+OezQ3HhsH2HrN7YkhfYcTjQSa/iraB15A7Qvt7Xpr0Yd2rDNt6GbFwVQDg3HcAw==",
"dev": true,
"dependencies": {
"jju": "^1.4.0",
"try-catch": "^3.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/relateurl": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
"dev": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/simport": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/simport/-/simport-1.2.0.tgz",
"integrity": "sha512-85Bm7pKsqiiQ8rmYCaPDdlXZjJvuW6/k/FY8MTtLFMgU7f8S00CgTHfRtWB6KwSb6ek4p9YyG2enG1+yJbl+CA==",
"dev": true,
"dependencies": {
"readjson": "^2.2.0",
"try-to-catch": "^3.0.0"
},
"engines": {
"node": ">=12.2"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/terser": {
"version": "5.19.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz",
"integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/try-catch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/try-catch/-/try-catch-3.0.1.tgz",
"integrity": "sha512-91yfXw1rr/P6oLpHSyHDOHm0vloVvUoo9FVdw8YwY05QjJQG9OT0LUxe2VRAzmHG+0CUOmI3nhxDUMLxDN/NEQ==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/try-to-catch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-3.0.1.tgz",
"integrity": "sha512-hOY83V84Hx/1sCzDSaJA+Xz2IIQOHRvjxzt+F0OjbQGPZ6yLPLArMA0gw/484MlfUkQbCpKYMLX3VDCAjWKfzQ==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/tsc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/tsc/-/tsc-2.0.4.tgz",
"integrity": "sha512-fzoSieZI5KKJVBYGvwbVZs/J5za84f2lSTLPYf6AGiIf43tZ3GNrI1QzTLcjtyDDP4aLxd46RTZq1nQxe7+k5Q==",
"dev": true,
"bin": {
"tsc": "bin/tsc"
}
},
"node_modules/tslib": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
"integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==",
"dev": true
},
"node_modules/yocto-queue": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
"dev": true,
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}

7
package.json Normal file
View File

@ -0,0 +1,7 @@
{
"devDependencies": {
"minify": "^10.3.0",
"prettier": "^3.0.1",
"tsc": "^2.0.4"
}
}

View File

@ -5,12 +5,12 @@ edition = "2021"
[dependencies] [dependencies]
tokio = { version = "1", features = ["process", "full"] } tokio = { version = "1", features = ["process", "full"] }
regex = "1.9" regex = "1.4"
log = "0.4" log = "0.4"
chrono = "0.4" chrono = "0.4"
chrono-tz = { version = "0.8", 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"
serde = "1.0" serde = "1.0"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]} sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono", "json"]}
serenity = { version = "0.11", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } serenity = { version = "0.11.1", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }

View File

@ -237,11 +237,11 @@ impl Into<CreateEmbed> for Embed {
pub struct Reminder { pub struct Reminder {
id: u32, id: u32,
channel_id: Option<u64>, channel_id: u64,
webhook_id: Option<u64>, webhook_id: Option<u64>,
webhook_token: Option<String>, webhook_token: Option<String>,
channel_paused: Option<bool>, channel_paused: bool,
channel_paused_until: Option<NaiveDateTime>, channel_paused_until: Option<NaiveDateTime>,
enabled: bool, enabled: bool,
@ -297,7 +297,7 @@ SELECT
reminders.`username` AS username reminders.`username` AS username
FROM FROM
reminders reminders
LEFT JOIN INNER JOIN
channels channels
ON ON
reminders.channel_id = channels.id reminders.channel_id = channels.id
@ -343,10 +343,7 @@ WHERE
async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) { async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) {
let _ = sqlx::query!( let _ = sqlx::query!(
" "UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ?",
UPDATE channels SET webhook_id = NULL, webhook_token = NULL
WHERE channel = ?
",
self.channel_id self.channel_id
) )
.execute(pool) .execute(pool)
@ -418,9 +415,7 @@ WHERE
self.set_sent(pool).await; self.set_sent(pool).await;
} else { } else {
sqlx::query!( sqlx::query!(
" "UPDATE reminders SET `utc_time` = ? WHERE `id` = ?",
UPDATE reminders SET `utc_time` = ? WHERE `id` = ?
",
updated_reminder_time.with_timezone(&Utc), updated_reminder_time.with_timezone(&Utc),
self.id self.id
) )
@ -453,10 +448,7 @@ WHERE
if *LOG_TO_DATABASE { if *LOG_TO_DATABASE {
sqlx::query!( sqlx::query!(
" "INSERT INTO stat (type, reminder_id, message) VALUES ('reminder_failed', ?, ?)",
INSERT INTO stat (type, reminder_id, message)
VALUES ('reminder_failed', ?, ?)
",
self.id, self.id,
message, message,
) )
@ -469,10 +461,7 @@ WHERE
async fn log_success(&self, pool: impl Executor<'_, Database = Database> + Copy) { async fn log_success(&self, pool: impl Executor<'_, Database = Database> + Copy) {
if *LOG_TO_DATABASE { if *LOG_TO_DATABASE {
sqlx::query!( sqlx::query!(
" "INSERT INTO stat (type, reminder_id) VALUES ('reminder_sent', ?)",
INSERT INTO stat (type, reminder_id)
VALUES ('reminder_sent', ?)
",
self.id, self.id,
) )
.execute(pool) .execute(pool)
@ -482,14 +471,7 @@ WHERE
} }
async fn set_sent(&self, pool: impl Executor<'_, Database = Database> + Copy) { async fn set_sent(&self, pool: impl Executor<'_, Database = Database> + Copy) {
sqlx::query!( sqlx::query!("UPDATE reminders SET `status` = 'sent' WHERE `id` = ?", self.id)
"
UPDATE reminders
SET `status` = 'sent', `status_change_time` = NOW()
WHERE `id` = ?
",
self.id
)
.execute(pool) .execute(pool)
.await .await
.expect(&format!("Could not delete Reminder {}", self.id)); .expect(&format!("Could not delete Reminder {}", self.id));
@ -501,11 +483,7 @@ WHERE
message: &'static str, message: &'static str,
) { ) {
sqlx::query!( sqlx::query!(
" "UPDATE reminders SET `status` = 'failed', `status_message` = ? WHERE `id` = ?",
UPDATE reminders
SET `status` = 'failed', `status_message` = ?, `status_change_time` = NOW()
WHERE `id` = ?
",
message, message,
self.id self.id
) )
@ -515,9 +493,7 @@ WHERE
} }
async fn pin_message<M: Into<u64>>(&self, message_id: M, http: impl AsRef<Http>) { async fn pin_message<M: Into<u64>>(&self, message_id: M, http: impl AsRef<Http>) {
if let Some(channel_id) = self.channel_id { let _ = http.as_ref().pin_message(self.channel_id, message_id.into(), None).await;
let _ = http.as_ref().pin_message(channel_id, message_id.into(), None).await;
}
} }
pub async fn send( pub async fn send(
@ -527,11 +503,10 @@ WHERE
) { ) {
async fn send_to_channel( async fn send_to_channel(
cache_http: impl CacheHttp, cache_http: impl CacheHttp,
channel_id: u64,
reminder: &Reminder, reminder: &Reminder,
embed: Option<CreateEmbed>, embed: Option<CreateEmbed>,
) -> Result<()> { ) -> Result<()> {
let channel = ChannelId(channel_id).to_channel(&cache_http).await; let channel = ChannelId(reminder.channel_id).to_channel(&cache_http).await;
match channel { match channel {
Ok(Channel::Guild(channel)) => { Ok(Channel::Guild(channel)) => {
@ -563,7 +538,6 @@ WHERE
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
Ok(Channel::Private(channel)) => { Ok(Channel::Private(channel)) => {
match channel match channel
.send_message(&cache_http.http(), |m| { .send_message(&cache_http.http(), |m| {
@ -593,9 +567,7 @@ WHERE
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
Err(e) => Err(e), Err(e) => Err(e),
_ => Err(Error::Other("Channel not of valid type")), _ => Err(Error::Other("Channel not of valid type")),
} }
} }
@ -650,20 +622,14 @@ WHERE
} }
} }
match self.channel_id {
Some(channel_id) => {
if self.enabled if self.enabled
&& !(self.channel_paused.unwrap_or(false) && !(self.channel_paused
&& self && self
.channel_paused_until .channel_paused_until
.map_or(true, |inner| inner >= Utc::now().naive_local())) .map_or(true, |inner| inner >= Utc::now().naive_local()))
{ {
let _ = sqlx::query!( let _ = sqlx::query!(
" "UPDATE `channels` SET paused = 0, paused_until = NULL WHERE `channel` = ?",
UPDATE `channels`
SET paused = 0, paused_until = NULL
WHERE `channel` = ?
",
self.channel_id self.channel_id
) )
.execute(pool) .execute(pool)
@ -674,10 +640,8 @@ WHERE
let result = if let (Some(webhook_id), Some(webhook_token)) = let result = if let (Some(webhook_id), Some(webhook_token)) =
(self.webhook_id, &self.webhook_token) (self.webhook_id, &self.webhook_token)
{ {
let webhook_res = cache_http let webhook_res =
.http() cache_http.http().get_webhook_with_token(webhook_id, webhook_token).await;
.get_webhook_with_token(webhook_id, webhook_token)
.await;
if let Ok(webhook) = webhook_res { if let Ok(webhook) = webhook_res {
send_to_webhook(cache_http, &self, webhook, embed).await send_to_webhook(cache_http, &self, webhook, embed).await
@ -685,10 +649,10 @@ WHERE
warn!("Webhook vanished for reminder {}: {:?}", self.id, webhook_res); warn!("Webhook vanished for reminder {}: {:?}", self.id, webhook_res);
self.reset_webhook(pool).await; self.reset_webhook(pool).await;
send_to_channel(cache_http, channel_id, &self, embed).await send_to_channel(cache_http, &self, embed).await
} }
} else { } else {
send_to_channel(cache_http, channel_id, &self, embed).await send_to_channel(cache_http, &self, embed).await
}; };
if let Err(e) = result { if let Err(e) = result {
@ -715,10 +679,7 @@ WHERE
None::<&'static str>, None::<&'static str>,
) )
.await; .await;
self.set_failed( self.set_failed(pool, "Could not be sent as guild does not exist")
pool,
"Could not be sent as guild does not exist",
)
.await; .await;
} }
50001 => { 50001 => {
@ -728,11 +689,7 @@ WHERE
None::<&'static str>, None::<&'static str>,
) )
.await; .await;
self.set_failed( self.set_failed(pool, "Could not be sent as missing access").await;
pool,
"Could not be sent as missing access",
)
.await;
} }
50007 => { 50007 => {
self.log_error( self.log_error(
@ -741,10 +698,7 @@ WHERE
None::<&'static str>, None::<&'static str>,
) )
.await; .await;
self.set_failed( self.set_failed(pool, "Could not be sent as user has DMs disabled")
pool,
"Could not be sent as user has DMs disabled",
)
.await; .await;
} }
50013 => { 50013 => {
@ -788,13 +742,4 @@ WHERE
self.refresh(pool).await; self.refresh(pool).await;
} }
} }
None => {
info!("Reminder {} is orphaned", self.id);
self.log_error(pool, "Orphaned", Option::<u8>::None).await;
self.set_failed(pool, "Could not be sent as channel was deleted").await;
}
}
}
} }

View File

@ -27,7 +27,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
"SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", "SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
guild_id.0 guild_id.0
) )
.fetch_all(&mut *transaction) .fetch_all(&mut transaction)
.await?; .await?;
let mut added_aliases = 0; let mut added_aliases = 0;
@ -42,7 +42,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
cmd_macro.description, cmd_macro.description,
cmd_macro.commands cmd_macro.commands
) )
.execute(&mut *transaction) .execute(&mut transaction)
.await?; .await?;
added_aliases += 1; added_aliases += 1;

View File

@ -166,21 +166,15 @@ impl ComponentDataModel {
.await; .await;
} }
ComponentDataModel::DelSelector(selector) => { ComponentDataModel::DelSelector(selector) => {
for id in &component.data.values { let selected_id = component.data.values.join(",");
match id.parse::<u32>() {
Ok(id) => {
if let Some(reminder) = Reminder::from_id(&data.database, id).await {
reminder.delete(&data.database).await.unwrap();
} else {
warn!("Attempt to delete non-existent reminder");
}
}
Err(e) => { sqlx::query!(
warn!("Error casting ID to integer: {:?}.", e); "UPDATE reminders SET `status` = 'pending' WHERE FIND_IN_SET(id, ?)",
} selected_id
} )
} .execute(&data.database)
.await
.unwrap();
let reminders = Reminder::from_guild( let reminders = Reminder::from_guild(
&ctx, &ctx,

View File

@ -10,7 +10,6 @@ pub struct ChannelData {
pub webhook_id: Option<u64>, pub webhook_id: Option<u64>,
pub webhook_token: Option<String>, pub webhook_token: Option<String>,
pub paused: bool, pub paused: bool,
pub db_guild_id: Option<u32>,
pub paused_until: Option<NaiveDateTime>, pub paused_until: Option<NaiveDateTime>,
} }
@ -23,11 +22,7 @@ impl ChannelData {
if let Ok(c) = sqlx::query_as_unchecked!( if let Ok(c) = sqlx::query_as_unchecked!(
Self, Self,
" "SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?",
SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until,
guild_id AS db_guild_id
FROM channels WHERE channel = ?
",
channel_id channel_id
) )
.fetch_one(pool) .fetch_one(pool)
@ -35,18 +30,12 @@ impl ChannelData {
{ {
Ok(c) Ok(c)
} else { } else {
let props = let props = channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name));
channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name));
let (guild_id, channel_name) = let (guild_id, channel_name) = if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
sqlx::query!( sqlx::query!(
" "INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))",
INSERT IGNORE INTO channels
(channel, name, guild_id)
VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))
",
channel_id, channel_id,
channel_name, channel_name,
guild_id guild_id
@ -57,10 +46,7 @@ impl ChannelData {
Ok(sqlx::query_as_unchecked!( Ok(sqlx::query_as_unchecked!(
Self, Self,
" "
SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?
paused_until, guild_id AS db_guild_id
FROM channels
WHERE channel = ?
", ",
channel_id channel_id
) )
@ -72,10 +58,8 @@ impl ChannelData {
pub async fn commit_changes(&self, pool: &MySqlPool) { pub async fn commit_changes(&self, pool: &MySqlPool) {
sqlx::query!( sqlx::query!(
" "
UPDATE channels UPDATE channels SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, paused = ?, paused_until \
SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, = ? WHERE id = ?
paused = ?, paused_until = ?
WHERE id = ?
", ",
self.name, self.name,
self.nudge, self.nudge,

View File

@ -51,7 +51,6 @@ pub struct ReminderBuilder {
pool: MySqlPool, pool: MySqlPool,
uid: String, uid: String,
channel: u32, channel: u32,
guild: Option<u32>,
thread_id: Option<u64>, thread_id: Option<u64>,
utc_time: NaiveDateTime, utc_time: NaiveDateTime,
timezone: String, timezone: String,
@ -87,7 +86,6 @@ impl ReminderBuilder {
INSERT INTO reminders ( INSERT INTO reminders (
`uid`, `uid`,
`channel_id`, `channel_id`,
`guild_id`,
`utc_time`, `utc_time`,
`timezone`, `timezone`,
`interval_seconds`, `interval_seconds`,
@ -112,13 +110,11 @@ INSERT INTO reminders (
?, ?,
?, ?,
?, ?,
?,
? ?
) )
", ",
self.uid, self.uid,
self.channel, self.channel,
self.guild,
utc_time, utc_time,
self.timezone, self.timezone,
self.interval_seconds, self.interval_seconds,
@ -251,10 +247,10 @@ impl<'a> MultiReminderBuilder<'a> {
{ {
Err(ReminderError::UserBlockedDm) Err(ReminderError::UserBlockedDm)
} else { } else {
Ok((user_data.dm_channel, None)) Ok(user_data.dm_channel)
} }
} else { } else {
Ok((user_data.dm_channel, None)) Ok(user_data.dm_channel)
} }
} else { } else {
Err(ReminderError::InvalidTag) Err(ReminderError::InvalidTag)
@ -301,13 +297,13 @@ impl<'a> MultiReminderBuilder<'a> {
.commit_changes(&self.ctx.data().database) .commit_changes(&self.ctx.data().database)
.await; .await;
Ok((channel_data.id, channel_data.db_guild_id)) Ok(channel_data.id)
} }
Err(e) => Err(ReminderError::DiscordError(e.to_string())), Err(e) => Err(ReminderError::DiscordError(e.to_string())),
} }
} else { } else {
Ok((channel_data.id, channel_data.db_guild_id)) Ok(channel_data.id)
} }
} }
} else { } else {
@ -321,8 +317,7 @@ impl<'a> MultiReminderBuilder<'a> {
let builder = ReminderBuilder { let builder = ReminderBuilder {
pool: self.ctx.data().database.clone(), pool: self.ctx.data().database.clone(),
uid: generate_uid(), uid: generate_uid(),
channel: c.0, channel: c,
guild: c.1,
thread_id, thread_id,
utc_time: self.utc_time, utc_time: self.utc_time,
timezone: self.timezone.to_string(), timezone: self.timezone.to_string(),

View File

@ -304,10 +304,7 @@ WHERE
&self, &self,
db: impl Executor<'_, Database = Database>, db: impl Executor<'_, Database = Database>,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
sqlx::query!( sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", self.uid)
"UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?",
self.uid
)
.execute(db) .execute(db)
.await .await
.map(|_| ()) .map(|_| ())

View File

@ -7,14 +7,14 @@ edition = "2018"
[dependencies] [dependencies]
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tls", "secrets", "json"] } rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tls", "secrets", "json"] }
rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tera"] } rocket_dyn_templates = { git = "https://github.com/SergioBenitez/Rocket", branch = "master", features = ["tera"] }
serenity = { version = "0.11", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] } serenity = { version = "0.11.1", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
oauth2 = "4" 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.7", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] } sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "macros", "mysql", "chrono", "json"] }
chrono = "0.4" chrono = "0.4"
chrono-tz = "0.8" chrono-tz = "0.5"
lazy_static = "1.4.0" lazy_static = "1.4.0"
rand = "0.7" rand = "0.7"
base64 = "0.13" base64 = "0.13"

View File

@ -72,14 +72,10 @@ pub async fn initialize(
db_pool: Pool<Database>, db_pool: Pool<Database>,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
info!("Checking environment variables..."); info!("Checking environment variables...");
if env::var("OFFLINE").map_or(true, |v| v != "1") {
env::var("OAUTH2_CLIENT_ID").expect("`OAUTH2_CLIENT_ID' not supplied"); env::var("OAUTH2_CLIENT_ID").expect("`OAUTH2_CLIENT_ID' not supplied");
env::var("OAUTH2_CLIENT_SECRET").expect("`OAUTH2_CLIENT_SECRET' not supplied"); env::var("OAUTH2_CLIENT_SECRET").expect("`OAUTH2_CLIENT_SECRET' not supplied");
env::var("OAUTH2_DISCORD_CALLBACK").expect("`OAUTH2_DISCORD_CALLBACK' not supplied"); env::var("OAUTH2_DISCORD_CALLBACK").expect("`OAUTH2_DISCORD_CALLBACK' not supplied");
env::var("PATREON_GUILD_ID").expect("`PATREON_GUILD_ID' not supplied"); env::var("PATREON_GUILD_ID").expect("`PATREON_GUILD_ID' not supplied");
}
info!("Done!"); info!("Done!");
let oauth2_client = BasicClient::new( let oauth2_client = BasicClient::new(
@ -150,8 +146,7 @@ pub async fn initialize(
.mount( .mount(
"/dashboard", "/dashboard",
routes![ routes![
routes::dashboard::dashboard_1, routes::dashboard::dashboard,
routes::dashboard::dashboard_2,
routes::dashboard::dashboard_home, routes::dashboard::dashboard_home,
routes::dashboard::user::get_user_info, routes::dashboard::user::get_user_info,
routes::dashboard::user::update_user_info, routes::dashboard::user::update_user_info,
@ -190,8 +185,6 @@ pub async fn initialize(
} }
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool { pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
offline!(true);
if let Some(subscription_guild) = *CNC_GUILD { if let Some(subscription_guild) = *CNC_GUILD {
let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await; let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await;
@ -213,8 +206,6 @@ pub async fn check_guild_subscription(
cache_http: impl CacheHttp, cache_http: impl CacheHttp,
guild_id: impl Into<GuildId>, guild_id: impl Into<GuildId>,
) -> bool { ) -> bool {
offline!(true);
if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) { if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) {
let owner = guild.owner_id; let owner = guild.owner_id;

View File

@ -1,11 +1,3 @@
macro_rules! offline {
($field:expr) => {
if std::env::var("OFFLINE").map_or(false, |v| v == "1") {
return $field;
}
};
}
macro_rules! check_length { macro_rules! check_length {
($max:ident, $field:expr) => { ($max:ident, $field:expr) => {
if $field.len() > $max { if $field.len() > $max {
@ -60,7 +52,6 @@ macro_rules! check_authorization {
let user_id = $cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten(); let user_id = $cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten();
if std::env::var("OFFLINE").map_or(true, |v| v != "1") {
match user_id { match user_id {
Some(user_id) => { Some(user_id) => {
match GuildId($guild).to_guild_cached($ctx) { match GuildId($guild).to_guild_cached($ctx) {
@ -101,7 +92,6 @@ macro_rules! check_authorization {
} }
} }
} }
}
} }
macro_rules! update_field { macro_rules! update_field {

View File

@ -12,7 +12,8 @@ use sqlx::{MySql, Pool};
use crate::routes::{ use crate::routes::{
dashboard::{ dashboard::{
create_reminder, ImportBody, ReminderCreate, ReminderCsv, ReminderTemplateCsv, TodoCsv, create_reminder, generate_uid, ImportBody, Reminder, ReminderCsv, ReminderTemplateCsv,
TodoCsv,
}, },
JsonResult, JsonResult,
}; };
@ -140,7 +141,7 @@ pub async fn import_reminders(
match channel_id.parse::<u64>() { match channel_id.parse::<u64>() {
Ok(channel_id) => { Ok(channel_id) => {
let reminder = ReminderCreate { let reminder = Reminder {
attachment: record.attachment, attachment: record.attachment,
attachment_name: record.attachment_name, attachment_name: record.attachment_name,
avatar: record.avatar, avatar: record.avatar,
@ -167,6 +168,7 @@ pub async fn import_reminders(
name: record.name, name: record.name,
restartable: record.restartable, restartable: record.restartable,
tts: record.tts, tts: record.tts,
uid: generate_uid(),
username: record.username, username: record.username,
utc_time: record.utc_time, utc_time: record.utc_time,
}; };

View File

@ -26,7 +26,7 @@ use crate::{
routes::{ routes::{
dashboard::{ dashboard::{
create_database_channel, create_reminder, template_name_default, DeleteReminder, create_database_channel, create_reminder, template_name_default, DeleteReminder,
DeleteReminderTemplate, PatchReminder, Reminder, ReminderCreate, ReminderTemplate, DeleteReminderTemplate, PatchReminder, Reminder, ReminderTemplate,
}, },
JsonResult, JsonResult,
}, },
@ -46,7 +46,6 @@ pub async fn get_guild_patreon(
cookies: &CookieJar<'_>, cookies: &CookieJar<'_>,
ctx: &State<Context>, ctx: &State<Context>,
) -> JsonResult { ) -> JsonResult {
offline!(Ok(json!({ "patreon": true })));
check_authorization!(cookies, ctx.inner(), id); check_authorization!(cookies, ctx.inner(), id);
match GuildId(id).to_guild_cached(ctx.inner()) { match GuildId(id).to_guild_cached(ctx.inner()) {
@ -74,12 +73,6 @@ pub async fn get_guild_channels(
cookies: &CookieJar<'_>, cookies: &CookieJar<'_>,
ctx: &State<Context>, ctx: &State<Context>,
) -> JsonResult { ) -> JsonResult {
offline!(Ok(json!(vec![ChannelInfo {
name: "general".to_string(),
id: "1".to_string(),
webhook_avatar: None,
webhook_name: None,
}])));
check_authorization!(cookies, ctx.inner(), id); check_authorization!(cookies, ctx.inner(), id);
match GuildId(id).to_guild_cached(ctx.inner()) { match GuildId(id).to_guild_cached(ctx.inner()) {
@ -118,7 +111,6 @@ struct RoleInfo {
#[get("/api/guild/<id>/roles")] #[get("/api/guild/<id>/roles")]
pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Context>) -> JsonResult { pub async fn get_guild_roles(id: u64, cookies: &CookieJar<'_>, ctx: &State<Context>) -> JsonResult {
offline!(Ok(json!(vec![RoleInfo { name: "@everyone".to_string(), id: "1".to_string() }])));
check_authorization!(cookies, ctx.inner(), id); check_authorization!(cookies, ctx.inner(), id);
let roles_res = ctx.cache.guild_roles(id); let roles_res = ctx.cache.guild_roles(id);
@ -298,7 +290,7 @@ pub async fn delete_reminder_template(
#[post("/api/guild/<id>/reminders", data = "<reminder>")] #[post("/api/guild/<id>/reminders", data = "<reminder>")]
pub async fn create_guild_reminder( pub async fn create_guild_reminder(
id: u64, id: u64,
reminder: Json<ReminderCreate>, reminder: Json<Reminder>,
cookies: &CookieJar<'_>, cookies: &CookieJar<'_>,
serenity_context: &State<Context>, serenity_context: &State<Context>,
pool: &State<Pool<MySql>>, pool: &State<Pool<MySql>>,
@ -318,22 +310,30 @@ pub async fn create_guild_reminder(
.await .await
} }
#[get("/api/guild/<id>/reminders?<status>")] #[get("/api/guild/<id>/reminders")]
pub async fn get_reminders( pub async fn get_reminders(
id: u64, id: u64,
cookies: &CookieJar<'_>, cookies: &CookieJar<'_>,
ctx: &State<Context>,
serenity_context: &State<Context>, serenity_context: &State<Context>,
pool: &State<Pool<MySql>>, pool: &State<Pool<MySql>>,
status: Option<String>,
) -> JsonResult { ) -> JsonResult {
check_authorization!(cookies, serenity_context.inner(), id); check_authorization!(cookies, serenity_context.inner(), id);
let status = status.unwrap_or("pending".to_string()); let channels_res = GuildId(id).channels(&ctx.inner()).await;
match channels_res {
Ok(channels) => {
let channels = channels
.keys()
.into_iter()
.map(|k| k.as_u64().to_string())
.collect::<Vec<String>>()
.join(",");
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(
Reminder, Reminder,
" "SELECT
SELECT
reminders.attachment, reminders.attachment,
reminders.attachment_name, reminders.attachment_name,
reminders.avatar, reminders.avatar,
@ -359,15 +359,11 @@ pub async fn get_reminders(
reminders.tts, reminders.tts,
reminders.uid, reminders.uid,
reminders.username, reminders.username,
reminders.utc_time, reminders.utc_time
reminders.status,
reminders.status_change_time,
reminders.status_message
FROM reminders FROM reminders
LEFT JOIN channels ON channels.id = reminders.channel_id LEFT JOIN channels ON channels.id = reminders.channel_id
WHERE FIND_IN_SET(`status`, ?) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)", WHERE `status` = 'pending' AND FIND_IN_SET(channels.channel, ?)",
status, channels
id
) )
.fetch_all(pool.inner()) .fetch_all(pool.inner())
.await .await
@ -377,6 +373,13 @@ pub async fn get_reminders(
json_err!("Could not load reminders") json_err!("Could not load reminders")
}) })
}
Err(e) => {
warn!("Could not fetch channels from {}: {:?}", id, e);
Ok(json!([]))
}
}
} }
#[patch("/api/guild/<id>/reminders", data = "<reminder>")] #[patch("/api/guild/<id>/reminders", data = "<reminder>")]
@ -550,8 +553,7 @@ pub async fn edit_reminder(
match sqlx::query_as_unchecked!( match sqlx::query_as_unchecked!(
Reminder, Reminder,
" "SELECT reminders.attachment,
SELECT reminders.attachment,
reminders.attachment_name, reminders.attachment_name,
reminders.avatar, reminders.avatar,
channels.channel, channels.channel,
@ -576,10 +578,7 @@ pub async fn edit_reminder(
reminders.tts, reminders.tts,
reminders.uid, reminders.uid,
reminders.username, reminders.username,
reminders.utc_time, reminders.utc_time
reminders.status,
reminders.status_change_time,
reminders.status_message
FROM reminders FROM reminders
LEFT JOIN channels ON channels.id = reminders.channel_id LEFT JOIN channels ON channels.id = reminders.channel_id
WHERE uid = ?", WHERE uid = ?",
@ -603,10 +602,7 @@ pub async fn delete_reminder(
reminder: Json<DeleteReminder>, reminder: Json<DeleteReminder>,
pool: &State<Pool<MySql>>, pool: &State<Pool<MySql>>,
) -> JsonResult { ) -> JsonResult {
match sqlx::query!( match sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", reminder.uid)
"UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?",
reminder.uid
)
.execute(pool.inner()) .execute(pool.inner())
.await .await
{ {

View File

@ -118,8 +118,8 @@ pub struct EmbedField {
inline: bool, inline: bool,
} }
#[derive(Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ReminderCreate { pub struct Reminder {
#[serde(with = "base64s")] #[serde(with = "base64s")]
attachment: Option<Vec<u8>>, attachment: Option<Vec<u8>>,
attachment_name: Option<String>, attachment_name: Option<String>,
@ -146,45 +146,10 @@ pub struct ReminderCreate {
name: String, name: String,
restartable: bool, restartable: bool,
tts: bool, tts: bool,
username: Option<String>,
utc_time: NaiveDateTime,
}
#[derive(Serialize, Deserialize)]
pub struct Reminder {
#[serde(with = "base64s")]
attachment: Option<Vec<u8>>,
attachment_name: Option<String>,
avatar: Option<String>,
#[serde(with = "string_opt")]
channel: Option<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: Option<Json<Vec<EmbedField>>>,
enabled: bool,
expires: Option<NaiveDateTime>,
interval_seconds: Option<u32>,
interval_days: Option<u32>,
interval_months: Option<u32>,
#[serde(default = "name_default")]
name: String,
restartable: bool,
tts: bool,
#[serde(default)] #[serde(default)]
uid: String, uid: String,
username: Option<String>, username: Option<String>,
utc_time: NaiveDateTime, utc_time: NaiveDateTime,
status: String,
status_message: Option<String>,
status_change_time: Option<NaiveDateTime>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -323,7 +288,15 @@ pub fn generate_uid() -> String {
mod string { mod string {
use std::{fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
use serde::{de, Deserialize, Deserializer}; use serde::{de, Deserialize, Deserializer, Serializer};
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.collect_str(value)
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error> pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where where
@ -335,34 +308,6 @@ mod string {
} }
} }
mod string_opt {
use std::{fmt::Display, str::FromStr};
use serde::{de, Deserialize, Deserializer, Serializer};
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
match value {
Some(value) => serializer.collect_str(value),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
Option::deserialize(deserializer)?
.map(|d: String| d.parse().map_err(de::Error::custom))
.transpose()
}
}
mod base64s { mod base64s {
use serde::{de, Deserialize, Deserializer, Serializer}; use serde::{de, Deserialize, Deserializer, Serializer};
@ -407,7 +352,7 @@ pub async fn create_reminder(
pool: impl sqlx::Executor<'_, Database = Database> + Copy, pool: impl sqlx::Executor<'_, Database = Database> + Copy,
guild_id: GuildId, guild_id: GuildId,
user_id: UserId, user_id: UserId,
reminder: ReminderCreate, reminder: Reminder,
) -> JsonResult { ) -> JsonResult {
// check guild in db // check guild in db
match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.0) match sqlx::query!("SELECT 1 as A FROM guilds WHERE guild = ?", guild_id.0)
@ -435,7 +380,7 @@ pub async fn create_reminder(
if !channel_matches_guild || !channel_exists { if !channel_matches_guild || !channel_exists {
warn!( warn!(
"Error in `create_reminder`: channel {:?} not found for guild {} (channel exists: {})", "Error in `create_reminder`: channel {} not found for guild {} (channel exists: {})",
reminder.channel, guild_id, channel_exists reminder.channel, guild_id, channel_exists
); );
@ -529,13 +474,11 @@ pub async fn create_reminder(
// write to db // write to db
match sqlx::query!( match sqlx::query!(
" "INSERT INTO reminders (
INSERT INTO reminders (
uid, uid,
attachment, attachment,
attachment_name, attachment_name,
channel_id, channel_id,
guild_id,
avatar, avatar,
content, content,
embed_author, embed_author,
@ -558,14 +501,11 @@ pub async fn create_reminder(
tts, tts,
username, username,
`utc_time` `utc_time`
) VALUES (?, ?, ?, ?, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(SELECT id FROM guilds WHERE guild = ?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?)",
new_uid, new_uid,
attachment_data, attachment_data,
reminder.attachment_name, reminder.attachment_name,
channel, channel,
guild_id.0,
reminder.avatar, reminder.avatar,
reminder.content, reminder.content,
reminder.embed_author, reminder.embed_author,
@ -620,10 +560,7 @@ pub async fn create_reminder(
reminders.tts, reminders.tts,
reminders.uid, reminders.uid,
reminders.username, reminders.username,
reminders.utc_time, reminders.utc_time
reminders.status,
reminders.status_change_time,
reminders.status_message
FROM reminders FROM reminders
LEFT JOIN channels ON channels.id = reminders.channel_id LEFT JOIN channels ON channels.id = reminders.channel_id
WHERE uid = ?", WHERE uid = ?",
@ -725,17 +662,7 @@ pub async fn dashboard_home(cookies: &CookieJar<'_>) -> Result<Template, Redirec
} }
#[get("/<_>")] #[get("/<_>")]
pub async fn dashboard_1(cookies: &CookieJar<'_>) -> Result<Template, Redirect> { pub async fn dashboard(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
if cookies.get_private("userid").is_some() {
let map: HashMap<&str, String> = HashMap::new();
Ok(Template::render("dashboard", &map))
} else {
Err(Redirect::to("/login/discord"))
}
}
#[get("/<_>/<_>")]
pub async fn dashboard_2(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
if cookies.get_private("userid").is_some() { if cookies.get_private("userid").is_some() {
let map: HashMap<&str, String> = HashMap::new(); let map: HashMap<&str, String> = HashMap::new();
Ok(Template::render("dashboard", &map)) Ok(Template::render("dashboard", &map))

View File

@ -54,8 +54,6 @@ pub async fn get_user_info(
ctx: &State<Context>, ctx: &State<Context>,
pool: &State<Pool<MySql>>, pool: &State<Pool<MySql>>,
) -> JsonValue { ) -> JsonValue {
offline!(json!(UserInfo { name: "Discord".to_string(), patreon: true, timezone: None }));
if let Some(user_id) = if let Some(user_id) =
cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten() cookies.get_private("userid").map(|u| u.value().parse::<u64>().ok()).flatten()
{ {
@ -118,8 +116,6 @@ pub async fn update_user_info(
#[get("/api/user/guilds")] #[get("/api/user/guilds")]
pub async fn get_user_guilds(cookies: &CookieJar<'_>, reqwest_client: &State<Client>) -> JsonValue { pub async fn get_user_guilds(cookies: &CookieJar<'_>, reqwest_client: &State<Client>) -> JsonValue {
offline!(json!(vec![GuildInfo { id: "1".to_string(), name: "Guild".to_string() }]));
if let Some(access_token) = cookies.get_private("access_token") { if let Some(access_token) = cookies.get_private("access_token") {
let request_res = reqwest_client let request_res = reqwest_client
.get(format!("{}/users/@me/guilds", DISCORD_API)) .get(format!("{}/users/@me/guilds", DISCORD_API))

View File

@ -15,18 +15,6 @@ div.reminderContent.is-collapsed .column.settings {
display: none; display: none;
} }
div.reminderContent.is-collapsed .button-row {
display: none;
}
div.reminderContent.is-collapsed .button-row-edit {
display: none;
}
div.reminderContent.is-collapsed .reminder-topbar {
padding-bottom: 0;
}
div.reminderContent.is-collapsed .invert-collapses { div.reminderContent.is-collapsed .invert-collapses {
display: inline-flex; display: inline-flex;
} }
@ -141,12 +129,6 @@ div.split-controls {
margin-top: 0 !important; margin-top: 0 !important;
} }
.reminder-settings > .column {
flex-grow: 0;
flex-shrink: 0;
flex-basis: 50%;
}
div.reminderContent { div.reminderContent {
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
@ -267,11 +249,11 @@ div#pageNavbar a {
.navbar-item.pageTitle { .navbar-item.pageTitle {
flex-shrink: 1; flex-shrink: 1;
white-space: nowrap; text-wrap: nowrap;
overflow: hidden; overflow: hidden;
} }
.dashboard-burger, .dashboard-burger:active, .dashboard-burger.is-active { .navbar-burger, .navbar-burger:active, .navbar-burger.is-active {
background-color: #adc99c !important; background-color: #adc99c !important;
border-radius: 14px; border-radius: 14px;
padding: 6px; padding: 6px;
@ -309,19 +291,10 @@ div.dashboard-sidebar:not(.mobile-sidebar) {
flex-direction: column; flex-direction: column;
} }
ul.guildList {
flex-grow: 1;
flex-shrink: 1;
overflow: auto;
}
div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer { div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer {
flex-shrink: 0; position: fixed;
flex-grow: 0; bottom: 0;
} width: 226px;
div.dashboard-sidebar svg {
flex-shrink: 0;
} }
div.mobile-sidebar { div.mobile-sidebar {
@ -471,7 +444,8 @@ input.default-width {
.customizable.is-400x300 img { .customizable.is-400x300 img {
margin-top: 10px; margin-top: 10px;
width: 100%; width: 100%;
height: 100px; min-height: 100px;
max-height: 400px;
} }
.customizable.is-32x32 img { .customizable.is-32x32 img {
@ -615,14 +589,6 @@ input.default-width {
border-bottom: 1px solid #fff; border-bottom: 1px solid #fff;
} }
.channel-selector {
width: 100%;
}
.select {
width: 100%;
}
li.highlight { li.highlight {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
@ -646,22 +612,7 @@ li.highlight {
padding: 2px; padding: 2px;
} }
@media only screen and (max-width: 1023px) { @media only screen and (max-width: 1408px) {
p.title.pageTitle {
display: none;
}
.dashboard-frame {
margin-top: 4rem !important;
}
.customizable.thumbnail img {
width: 60px;
height: 60px;
}
}
@media only screen and (max-width: 768px) {
.button-row { .button-row {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -679,20 +630,41 @@ li.highlight {
.button-row button { .button-row button {
width: 100%; width: 100%;
} }
}
.reminder-settings { @media only screen and (max-width: 768px) {
margin-bottom: 0 !important; .button-row-edit {
display: flex;
flex-direction: column;
} }
.tts-row { .button-row-edit > button {
padding-bottom: 0; width: 100%;
margin: 4px;
}
p.title.pageTitle {
visibility: hidden;
text-wrap: nowrap;
overflow: hidden;
}
}
@media only screen and (max-width: 768px) {
.customizable.thumbnail img {
width: 60px;
height: 60px;
}
.customizable.is-24x24 img {
width: 16px;
height: 16px;
} }
} }
/* loader */ /* loader */
#loader { #loader {
position: fixed; position: fixed;
top: 0;
background-color: rgba(255, 255, 255, 0.8); background-color: rgba(255, 255, 255, 0.8);
width: 100vw; width: 100vw;
z-index: 999; z-index: 999;
@ -704,86 +676,6 @@ li.highlight {
/* END */ /* END */
div.reminderError {
margin: 10px;
padding: 14px;
background-color: #f5f5f5;
border-radius: 8px;
}
div.reminderError .errorHead {
display: flex;
flex-direction: row;
}
div.reminderError .errorIcon {
padding: 8px;
border-radius: 4px;
margin-right: 12px;
}
div.reminderError .errorIcon .fas {
display: none
}
div.reminderError[data-case="deleted"] .errorIcon {
background-color: #e7e5e4;
}
div.reminderError[data-case="failed"] .errorIcon {
background-color: #fecaca;
}
div.reminderError[data-case="sent"] .errorIcon {
background-color: #d9f99d;
}
div.reminderError[data-case="deleted"] .errorIcon .fas.fa-trash {
display: block;
}
div.reminderError[data-case="failed"] .errorIcon .fas.fa-exclamation-triangle {
display: block;
}
div.reminderError[data-case="sent"] .errorIcon .fas.fa-check {
display: block;
}
div.reminderError .errorHead .reminderName {
font-size: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
color: rgb(54, 54, 54);
flex-grow: 1;
}
div.reminderError .errorHead .reminderTime {
font-size: 1rem;
display: flex;
flex-direction: column;
flex-shrink: 1;
justify-content: center;
color: rgb(54, 54, 54);
background-color: #ffffff;
padding: 8px;
border-radius: 4px;
border-color: #e5e5e5;
border-width: 1px;
border-style: solid;
}
div.reminderError .reminderMessage {
font-size: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
color: rgb(54, 54, 54);
flex-grow: 1;
font-style: italic;
}
/* other stuff */ /* other stuff */
.half-rem { .half-rem {
@ -821,38 +713,15 @@ a.switch-pane {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.guild-submenu {
display: none;
}
.guild-submenu li {
font-size: 0.8rem;
}
a.switch-pane.is-active ~ .guild-submenu {
display: block;
}
.feedback { .feedback {
background-color: #5865F2; background-color: #5865F2;
} }
.is-locked { .is-locked {
pointer-events: none; pointer-events: none;
}
.is-locked > :not(.patreon-invert) {
opacity: 0.4; opacity: 0.4;
} }
.is-locked .patreon-invert {
display: block;
}
.patreon-invert {
display: none;
}
.is-locked .foreground { .is-locked .foreground {
pointer-events: auto; pointer-events: auto;
} }
@ -882,5 +751,5 @@ a.switch-pane.is-active ~ .guild-submenu {
} }
.figure-num { .figure-num {
font-size: 2rem; font-size: 2em;
} }

View File

@ -18,7 +18,6 @@ const $downloader = document.querySelector("a#downloader");
const $uploader = document.querySelector("input#uploader"); const $uploader = document.querySelector("input#uploader");
let channels = []; let channels = [];
let reminderErrors = [];
let guildNames = {}; let guildNames = {};
let roles = []; let roles = [];
let templates = {}; let templates = {};
@ -34,11 +33,7 @@ let globalPatreon = false;
let guildPatreon = false; let guildPatreon = false;
function guildId() { function guildId() {
return document.querySelector("li > a.is-active").parentElement.dataset["guild"]; return document.querySelector(".guildList a.is-active").dataset["guild"];
}
function guildName() {
return guildNames[guildId()];
} }
function colorToInt(r, g, b) { function colorToInt(r, g, b) {
@ -57,7 +52,7 @@ function switch_pane(selector) {
el.classList.add("is-hidden"); el.classList.add("is-hidden");
}); });
document.querySelector(`*[data-name=${selector}]`).classList.remove("is-hidden"); document.getElementById(selector).classList.remove("is-hidden");
} }
function update_select(sel) { function update_select(sel) {
@ -454,27 +449,21 @@ document.addEventListener("guildSwitched", async (e) => {
.querySelectorAll(".patreon-only") .querySelectorAll(".patreon-only")
.forEach((el) => el.classList.add("is-locked")); .forEach((el) => el.classList.add("is-locked"));
let $li = document.querySelectorAll(`li[data-guild="${e.detail.guild_id}"]`); let $anchor = document.querySelector(
`.switch-pane[data-guild="${e.detail.guild_id}"]`
);
if ($li.length === 0) { let hasError = false;
if ($anchor === null) {
switch_pane("user-error"); switch_pane("user-error");
hasError = true;
return; return;
} }
switch_pane(e.detail.pane); switch_pane($anchor.dataset["pane"]);
reset_guild_pane(); reset_guild_pane();
document $anchor.classList.add("is-active");
.querySelectorAll(`li[data-guild="${e.detail.guild_id}"] > a`)
.forEach((el) => {
el.classList.add("is-active");
});
document
.querySelectorAll(
`li[data-guild="${e.detail.guild_id}"] *[data-pane="${e.detail.pane}"]`
)
.forEach((el) => {
el.classList.add("is-active");
});
if (globalPatreon || (await fetch_patreon(e.detail.guild_id))) { if (globalPatreon || (await fetch_patreon(e.detail.guild_id))) {
document document
@ -482,26 +471,15 @@ document.addEventListener("guildSwitched", async (e) => {
.forEach((el) => el.classList.remove("is-locked")); .forEach((el) => el.classList.remove("is-locked"));
} }
const event = new CustomEvent("paneLoad", { hasError = await fetch_channels(e.detail.guild_id);
detail: {
guild_id: e.detail.guild_id,
pane: e.detail.pane,
},
});
document.dispatchEvent(event);
});
document.addEventListener("paneLoad", async (ev) => {
const hasError = await fetch_channels(ev.detail.guild_id);
if (!hasError) { if (!hasError) {
fetch_roles(ev.detail.guild_id); fetch_roles(e.detail.guild_id);
fetch_templates(ev.detail.guild_id); fetch_templates(e.detail.guild_id);
fetch_reminders(ev.detail.guild_id); fetch_reminders(e.detail.guild_id);
document.querySelectorAll("p.pageTitle").forEach((el) => { document.querySelectorAll("p.pageTitle").forEach((el) => {
el.textContent = `${guildName()} Reminders`; el.textContent = `${e.detail.guild_name} Reminders`;
}); });
document.querySelectorAll("select.channel-selector").forEach((el) => { document.querySelectorAll("select.channel-selector").forEach((el) => {
el.addEventListener("change", (e) => { el.addEventListener("change", (e) => {
update_select(e.target); update_select(e.target);
@ -706,56 +684,36 @@ document.addEventListener("DOMContentLoaded", async () => {
"%guildname%", "%guildname%",
guild.name guild.name
); );
$anchor.dataset["guild"] = guild.id;
$anchor.dataset["name"] = guild.name; $anchor.dataset["name"] = guild.name;
$anchor.href = `/dashboard/${guild.id}/reminders`; $anchor.href = `/dashboard/${guild.id}?name=${guild.name}`;
const $li = $anchor.parentElement; $anchor.addEventListener("click", async (e) => {
$li.dataset["guild"] = guild.id;
$li.querySelectorAll("a").forEach((el) => {
el.addEventListener("click", (e) => {
const pane = el.dataset["pane"];
const slug = el.dataset["slug"];
if (pane !== undefined && slug !== undefined) {
e.preventDefault(); e.preventDefault();
window.history.pushState({}, "", `/dashboard/${guild.id}`);
switch_pane(pane);
window.history.pushState(
{},
"",
`/dashboard/${guild.id}/${slug}`
);
const event = new CustomEvent("guildSwitched", { const event = new CustomEvent("guildSwitched", {
detail: { detail: {
guild_name: guild.name,
guild_id: guild.id, guild_id: guild.id,
pane,
}, },
}); });
document.dispatchEvent(event); document.dispatchEvent(event);
}
});
}); });
element.append($clone); element.append($clone);
}); });
} }
const matches = window.location.href.match( const matches = window.location.href.match(/dashboard\/(\d+)/);
/dashboard\/(\d+)(\/)?([a-zA-Z\-]+)?/
);
if (matches) { if (matches) {
let id = matches[1]; let id = matches[1];
let kind = matches[3];
let name = guildNames[id]; let name = guildNames[id];
const event = new CustomEvent("guildSwitched", { const event = new CustomEvent("guildSwitched", {
detail: { detail: {
guild_name: name, guild_name: name,
guild_id: id, guild_id: id,
pane: kind,
}, },
}); });

View File

@ -1,45 +0,0 @@
function loadErrors() {
return fetch(
`/dashboard/api/guild/${guildId()}/reminders?status=deleted,sent,failed`
).then((response) => response.json());
}
document.addEventListener("paneLoad", (ev) => {
if (ev.detail.pane !== "errors") {
return;
}
document.querySelectorAll(".reminderError").forEach((el) => el.remove());
const template = document.getElementById("reminderError");
const container = document.getElementById("reminderLog");
loadErrors()
.then((res) => {
res = res
.filter((r) => r.status_change_time !== null)
.sort((a, b) => a.status_change_time < b.status_change_time);
for (const reminder of res) {
const newRow = template.content.cloneNode(true);
newRow.querySelector(".reminderError").dataset["case"] = reminder.status;
const statusTime = new luxon.DateTime.fromISO(
reminder.status_change_time,
{ zone: "UTC" }
);
newRow.querySelector(".reminderName").textContent = reminder.name;
newRow.querySelector(".reminderMessage").textContent =
reminder.status_message;
newRow.querySelector(".reminderTime").textContent = statusTime
.toLocal()
.toLocaleString(luxon.DateTime.DATETIME_MED);
container.appendChild(newRow);
}
})
.finally(() => {
$loader.classList.add("is-hidden");
});
});

View File

@ -0,0 +1,19 @@
let _reminderErrors = [];
const reminderErrors = () => {
return _reminderErrors;
}
const guildId = () => {
let selected: HTMLElement = document.querySelector(".guildList a.is-active");
return selected.dataset["guild"];
}
function loadErrors() {
fetch(`/dashboard/api/guild/${guildId()}/errors`).then(response => response.json())
}
document.addEventListener('DOMContentLoaded', () => {
})

View File

@ -40,14 +40,14 @@
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/"> <a class="navbar-item" href="/">
<figure class="image"> <figure class="image">
<img width="28px" height="28px" src="/static/img/logo_nobg.webp" alt="Reminder Bot Logo"> <img src="/static/img/logo_nobg.webp" alt="Reminder Bot Logo">
</figure> </figure>
</a> </a>
<p class="navbar-item pageTitle"> <p class="navbar-item pageTitle">
</p> </p>
<a role="button" class="dashboard-burger navbar-burger is-right" aria-label="menu" aria-expanded="false" <a role="button" class="navbar-burger is-right" aria-label="menu" aria-expanded="false"
data-target="mobileSidebar"> data-target="mobileSidebar">
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
@ -234,7 +234,6 @@
<a href="/"> <a href="/">
<div class="brand"> <div class="brand">
<img src="/static/img/logo_nobg.webp" alt="Reminder bot logo" <img src="/static/img/logo_nobg.webp" alt="Reminder bot logo"
width="52px" height="52px"
class="dashboard-brand"> class="dashboard-brand">
</div> </div>
</a> </a>
@ -333,16 +332,16 @@
<p class="subtitle is-hidden-desktop">Press the <span class="icon"><i class="fal fa-bars"></i></span> to get started</p> <p class="subtitle is-hidden-desktop">Press the <span class="icon"><i class="fal fa-bars"></i></span> to get started</p>
</div> </div>
</section> </section>
<section data-name="reminders" class="is-hidden"> <section id="guild" class="is-hidden">
{% include "reminder_dashboard/reminder_dashboard" %} {% include "reminder_dashboard/reminder_dashboard" %}
</section> </section>
<section data-name="errors" class="is-hidden"> <section id="reminder-errors" class="is-hidden">
{% include "reminder_dashboard/reminder_errors" %} {% include "reminder_dashboard/reminder_errors" %}
</section> </section>
<section data-name="guild-error" class="is-hidden"> <section id="guild-error" class="is-hidden">
{% include "reminder_dashboard/guild_error" %} {% include "reminder_dashboard/guild_error" %}
</section> </section>
<section data-name="user-error" class="is-hidden"> <section id="user-error" class="is-hidden">
{% include "reminder_dashboard/user_error" %} {% include "reminder_dashboard/user_error" %}
</section> </section>
</div> </div>
@ -376,28 +375,14 @@
<template id="guildListEntry"> <template id="guildListEntry">
<li> <li>
<a class="switch-pane" data-pane="reminders" data-slug="reminders"> <a class="switch-pane" data-pane="guild">
<span class="icon"><i class="fas fa-map-pin"></i></span> <span class="guild-name">%guildname%</span> <span class="icon"><i class="fas fa-map-pin"></i></span> <span class="guild-name">%guildname%</span>
</a> </a>
<ul class="guild-submenu">
<li>
<a class="switch-pane" data-pane="reminders" data-slug="reminders">
<span class="icon"><i class="fas fa-calendar-alt"></i></span> Reminders
</a>
<a class="switch-pane" data-pane="errors" data-slug="errors">
<span class="icon"><i class="fas fa-file-alt"></i></span> Logs
</a>
</li>
</ul>
</li> </li>
</template> </template>
<template id="guildReminder"> <template id="guildReminder">
{% include "reminder_dashboard/templates/guild_reminder" %} {% include "reminder_dashboard/guild_reminder" %}
</template>
<template id="reminderError">
{% include "reminder_dashboard/templates/reminder_error" %}
</template> </template>
<script src="/static/js/iro.js"></script> <script src="/static/js/iro.js"></script>

View File

@ -133,6 +133,8 @@
</article> </article>
</div> </div>
<div class="column settings"> <div class="column settings">
<div class="columns">
<div class="column">
<div class="field channel-field"> <div class="field channel-field">
<div class="collapses"> <div class="collapses">
<label class="label" for="channelOption">Channel*</label> <label class="label" for="channelOption">Channel*</label>
@ -147,7 +149,8 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="column">
<div class="field"> <div class="field">
<div class="control"> <div class="control">
<label class="label collapses"> <label class="label collapses">
@ -156,13 +159,12 @@
</label> </label>
</div> </div>
</div> </div>
</div>
</div>
<div class="collapses split-controls"> <div class="collapses split-controls">
<div> <div>
<div class="patreon-only"> <div class="patreon-only">
<div class="patreon-invert foreground">
Intervals available on <a href="https://patreon.com/jellywx">Patreon</a> or <a href="https://gitea.jellypro.xyz/jude/reminder-bot">self-hosting</a>
</div>
<div class="field"> <div class="field">
<label class="label">Interval <a class="foreground" href="/help/intervals"><i class="fas fa-question-circle"></i></a></label> <label class="label">Interval <a class="foreground" href="/help/intervals"><i class="fas fa-question-circle"></i></a></label>
<div class="control intervalSelector"> <div class="control intervalSelector">
@ -231,9 +233,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div>
{% if creating %} {% if creating %}
<div class="button-row"> <div class="button-row">
<div class="button-row-reminder"> <div class="button-row-reminder">
@ -266,4 +266,7 @@
</button> </button>
</div> </div>
{% endif %} {% endif %}
</div>
</div>
</div>
</div> </div>

View File

@ -2,7 +2,7 @@
<strong>Create Reminder</strong> <strong>Create Reminder</strong>
<div id="reminderCreator"> <div id="reminderCreator">
{% set creating = true %} {% set creating = true %}
{% include "reminder_dashboard/templates/guild_reminder" %} {% include "reminder_dashboard/guild_reminder" %}
{% set creating = false %} {% set creating = false %}
</div> </div>
<br> <br>
@ -46,10 +46,6 @@
<div id="guildReminders"> <div id="guildReminders">
</div> </div>
<div id="guildErrors">
</div>
</div> </div>
<script src="/static/js/sort.js"></script> <script src="/static/js/sort.js"></script>

View File

@ -1,4 +1,4 @@
<div id="reminderLog"> <div>
</div> </div>

View File

@ -1,20 +0,0 @@
<div class="reminderError" data-case="success">
<div class="errorHead">
<div class="errorIcon">
<span class="icon">
<i class="fas fa-trash"></i>
<i class="fas fa-check"></i>
<i class="fas fa-exclamation-triangle"></i>
</span>
</div>
<div class="reminderName">
Reminder
</div>
<div class="reminderMessage">
</div>
<div class="reminderTime">
</div>
</div>
</div>