Compare commits
	
		
			16 Commits
		
	
	
		
			jude/confi
			...
			jude/orpha
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5f703e8538 | ||
| 
						 | 
					2993505a47 | ||
| 
						 | 
					b225ad7e45 | ||
| 
						 | 
					ee89cb40c5 | ||
| 
						 | 
					b6b5e6d2b2 | ||
| 
						 | 
					adf29dca5d | ||
| 
						 | 
					ea3fe3f543 | ||
| 
						 | 
					109cf16dbb | ||
| 
						 | 
					6726ca0c2d | ||
| 38133be15d | |||
| 
						 | 
					8587bed703 | ||
| 
						 | 
					6c9af1ae8e | ||
| 
						 | 
					7695b7a476 | ||
| 651da7b28e | |||
| eb086146bf | |||
| 4ebd705e5e | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,5 +3,3 @@
 | 
				
			|||||||
/venv
 | 
					/venv
 | 
				
			||||||
.cargo
 | 
					.cargo
 | 
				
			||||||
/.idea
 | 
					/.idea
 | 
				
			||||||
 | 
					 | 
				
			||||||
node_modules/
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -2217,7 +2217,7 @@ checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "reminder-rs"
 | 
					name = "reminder-rs"
 | 
				
			||||||
version = "1.6.36"
 | 
					version = "1.6.38"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "base64 0.21.2",
 | 
					 "base64 0.21.2",
 | 
				
			||||||
 "chrono",
 | 
					 "chrono",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "reminder-rs"
 | 
					name = "reminder-rs"
 | 
				
			||||||
version = "1.6.36"
 | 
					version = "1.6.38"
 | 
				
			||||||
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"
 | 
				
			||||||
@@ -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"],
 | 
				
			||||||
    ["$OUT_DIR/web/static/**/*", "lib/reminder-rs/static", "644"],
 | 
					    ["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"],
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										96
									
								
								build.rs
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								build.rs
									
									
									
									
									
								
							@@ -1,99 +1,3 @@
 | 
				
			|||||||
#[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();
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								migrations/20230812111348_orphan_reminders.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								migrations/20230812111348_orphan_reminders.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					-- 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);
 | 
				
			||||||
							
								
								
									
										4
									
								
								migrations/20230903131153_reminder_status_timing.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								migrations/20230903131153_reminder_status_timing.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					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`;
 | 
				
			||||||
							
								
								
									
										485
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										485
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,485 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "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"
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "devDependencies": {
 | 
					 | 
				
			||||||
    "minify": "^10.3.0",
 | 
					 | 
				
			||||||
    "prettier": "^3.0.1",
 | 
					 | 
				
			||||||
    "tsc": "^2.0.4"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -310,6 +310,7 @@ WHERE
 | 
				
			|||||||
            reminders
 | 
					            reminders
 | 
				
			||||||
        WHERE
 | 
					        WHERE
 | 
				
			||||||
            reminders.`utc_time` <= NOW() AND
 | 
					            reminders.`utc_time` <= NOW() AND
 | 
				
			||||||
 | 
					            reminders.`channel_id` IS NOT NULL AND
 | 
				
			||||||
            `status` = 'pending' AND
 | 
					            `status` = 'pending' AND
 | 
				
			||||||
            (
 | 
					            (
 | 
				
			||||||
                reminders.`interval_seconds` IS NOT NULL
 | 
					                reminders.`interval_seconds` IS NOT NULL
 | 
				
			||||||
@@ -471,7 +472,14 @@ 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!("UPDATE reminders SET `status` = 'sent' WHERE `id` = ?", self.id)
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					            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));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -166,15 +166,21 @@ impl ComponentDataModel {
 | 
				
			|||||||
                    .await;
 | 
					                    .await;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ComponentDataModel::DelSelector(selector) => {
 | 
					            ComponentDataModel::DelSelector(selector) => {
 | 
				
			||||||
                let selected_id = component.data.values.join(",");
 | 
					                for id in &component.data.values {
 | 
				
			||||||
 | 
					                    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");
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                sqlx::query!(
 | 
					                        Err(e) => {
 | 
				
			||||||
                    "UPDATE reminders SET `status` = 'pending' WHERE FIND_IN_SET(id, ?)",
 | 
					                            warn!("Error casting ID to integer: {:?}.", e);
 | 
				
			||||||
                    selected_id
 | 
					                        }
 | 
				
			||||||
                )
 | 
					                    }
 | 
				
			||||||
                .execute(&data.database)
 | 
					                }
 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
                .unwrap();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let reminders = Reminder::from_guild(
 | 
					                let reminders = Reminder::from_guild(
 | 
				
			||||||
                    &ctx,
 | 
					                    &ctx,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ 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>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,7 +23,11 @@ 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)
 | 
				
			||||||
@@ -30,12 +35,18 @@ impl ChannelData {
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            Ok(c)
 | 
					            Ok(c)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            let props = channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name));
 | 
					            let props =
 | 
				
			||||||
 | 
					                channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let (guild_id, channel_name) = if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
 | 
					            let (guild_id, channel_name) =
 | 
				
			||||||
 | 
					                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
 | 
				
			||||||
@@ -46,7 +57,10 @@ impl ChannelData {
 | 
				
			|||||||
            Ok(sqlx::query_as_unchecked!(
 | 
					            Ok(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
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -58,9 +72,10 @@ SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_u
 | 
				
			|||||||
    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
					    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
				
			||||||
        sqlx::query!(
 | 
					        sqlx::query!(
 | 
				
			||||||
            "
 | 
					            "
 | 
				
			||||||
UPDATE channels SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, paused = ?, paused_until \
 | 
					            UPDATE channels
 | 
				
			||||||
             = ? WHERE id = ?
 | 
					            SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?,
 | 
				
			||||||
            ",
 | 
					                paused = ?, paused_until = ?
 | 
				
			||||||
 | 
					            WHERE id = ?",
 | 
				
			||||||
            self.name,
 | 
					            self.name,
 | 
				
			||||||
            self.nudge,
 | 
					            self.nudge,
 | 
				
			||||||
            self.blacklisted,
 | 
					            self.blacklisted,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,7 @@ 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,
 | 
				
			||||||
@@ -86,6 +87,7 @@ 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`,
 | 
				
			||||||
@@ -110,11 +112,13 @@ 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,
 | 
				
			||||||
@@ -247,10 +251,10 @@ impl<'a> MultiReminderBuilder<'a> {
 | 
				
			|||||||
                                {
 | 
					                                {
 | 
				
			||||||
                                    Err(ReminderError::UserBlockedDm)
 | 
					                                    Err(ReminderError::UserBlockedDm)
 | 
				
			||||||
                                } else {
 | 
					                                } else {
 | 
				
			||||||
                                    Ok(user_data.dm_channel)
 | 
					                                    Ok((user_data.dm_channel, None))
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            } else {
 | 
					                            } else {
 | 
				
			||||||
                                Ok(user_data.dm_channel)
 | 
					                                Ok((user_data.dm_channel, None))
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        } else {
 | 
					                        } else {
 | 
				
			||||||
                            Err(ReminderError::InvalidTag)
 | 
					                            Err(ReminderError::InvalidTag)
 | 
				
			||||||
@@ -297,13 +301,13 @@ impl<'a> MultiReminderBuilder<'a> {
 | 
				
			|||||||
                                                .commit_changes(&self.ctx.data().database)
 | 
					                                                .commit_changes(&self.ctx.data().database)
 | 
				
			||||||
                                                .await;
 | 
					                                                .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                            Ok(channel_data.id)
 | 
					                                            Ok((channel_data.id, channel_data.db_guild_id))
 | 
				
			||||||
                                        }
 | 
					                                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                        Err(e) => Err(ReminderError::DiscordError(e.to_string())),
 | 
					                                        Err(e) => Err(ReminderError::DiscordError(e.to_string())),
 | 
				
			||||||
                                    }
 | 
					                                    }
 | 
				
			||||||
                                } else {
 | 
					                                } else {
 | 
				
			||||||
                                    Ok(channel_data.id)
 | 
					                                    Ok((channel_data.id, channel_data.db_guild_id))
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        } else {
 | 
					                        } else {
 | 
				
			||||||
@@ -317,7 +321,8 @@ 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,
 | 
					                            channel: c.0,
 | 
				
			||||||
 | 
					                            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(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -304,7 +304,10 @@ WHERE
 | 
				
			|||||||
        &self,
 | 
					        &self,
 | 
				
			||||||
        db: impl Executor<'_, Database = Database>,
 | 
					        db: impl Executor<'_, Database = Database>,
 | 
				
			||||||
    ) -> Result<(), sqlx::Error> {
 | 
					    ) -> Result<(), sqlx::Error> {
 | 
				
			||||||
        sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", self.uid)
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?",
 | 
				
			||||||
 | 
					            self.uid
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        .execute(db)
 | 
					        .execute(db)
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .map(|_| ())
 | 
					        .map(|_| ())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,10 +72,14 @@ 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(
 | 
				
			||||||
@@ -146,7 +150,8 @@ pub async fn initialize(
 | 
				
			|||||||
        .mount(
 | 
					        .mount(
 | 
				
			||||||
            "/dashboard",
 | 
					            "/dashboard",
 | 
				
			||||||
            routes![
 | 
					            routes![
 | 
				
			||||||
                routes::dashboard::dashboard,
 | 
					                routes::dashboard::dashboard_1,
 | 
				
			||||||
 | 
					                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,
 | 
				
			||||||
@@ -185,6 +190,8 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -206,6 +213,8 @@ 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,11 @@
 | 
				
			|||||||
 | 
					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 {
 | 
				
			||||||
@@ -52,6 +60,7 @@ 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) {
 | 
				
			||||||
@@ -93,6 +102,7 @@ macro_rules! check_authorization {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
macro_rules! update_field {
 | 
					macro_rules! update_field {
 | 
				
			||||||
    ($pool:expr, $error:ident, $reminder:ident.[$field:ident]) => {
 | 
					    ($pool:expr, $error:ident, $reminder:ident.[$field:ident]) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -145,7 +145,7 @@ pub async fn import_reminders(
 | 
				
			|||||||
                                    attachment: record.attachment,
 | 
					                                    attachment: record.attachment,
 | 
				
			||||||
                                    attachment_name: record.attachment_name,
 | 
					                                    attachment_name: record.attachment_name,
 | 
				
			||||||
                                    avatar: record.avatar,
 | 
					                                    avatar: record.avatar,
 | 
				
			||||||
                                    channel: channel_id,
 | 
					                                    channel: Some(channel_id),
 | 
				
			||||||
                                    content: record.content,
 | 
					                                    content: record.content,
 | 
				
			||||||
                                    embed_author: record.embed_author,
 | 
					                                    embed_author: record.embed_author,
 | 
				
			||||||
                                    embed_author_url: record.embed_author_url,
 | 
					                                    embed_author_url: record.embed_author_url,
 | 
				
			||||||
@@ -171,6 +171,8 @@ pub async fn import_reminders(
 | 
				
			|||||||
                                    uid: generate_uid(),
 | 
					                                    uid: generate_uid(),
 | 
				
			||||||
                                    username: record.username,
 | 
					                                    username: record.username,
 | 
				
			||||||
                                    utc_time: record.utc_time,
 | 
					                                    utc_time: record.utc_time,
 | 
				
			||||||
 | 
					                                    status: "pending".to_string(),
 | 
				
			||||||
 | 
					                                    status_change_time: None,
 | 
				
			||||||
                                };
 | 
					                                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                create_reminder(
 | 
					                                create_reminder(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,7 @@ 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()) {
 | 
				
			||||||
@@ -73,6 +74,12 @@ 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()) {
 | 
				
			||||||
@@ -111,6 +118,7 @@ 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);
 | 
				
			||||||
@@ -310,30 +318,22 @@ pub async fn create_guild_reminder(
 | 
				
			|||||||
    .await
 | 
					    .await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/api/guild/<id>/reminders")]
 | 
					#[get("/api/guild/<id>/reminders?<status>")]
 | 
				
			||||||
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 channels_res = GuildId(id).channels(&ctx.inner()).await;
 | 
					    let status = status.unwrap_or("pending".to_string());
 | 
				
			||||||
 | 
					 | 
				
			||||||
    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,11 +359,14 @@ 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
 | 
				
			||||||
        FROM reminders
 | 
					        FROM reminders
 | 
				
			||||||
        LEFT JOIN channels ON channels.id = reminders.channel_id
 | 
					        LEFT JOIN channels ON channels.id = reminders.channel_id
 | 
				
			||||||
                WHERE `status` = 'pending' AND FIND_IN_SET(channels.channel, ?)",
 | 
					        WHERE FIND_IN_SET(`status`, ?) AND reminders.guild_id = (SELECT id FROM guilds WHERE guild = ?)",
 | 
				
			||||||
                channels
 | 
					        status,
 | 
				
			||||||
 | 
					        id
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .fetch_all(pool.inner())
 | 
					    .fetch_all(pool.inner())
 | 
				
			||||||
    .await
 | 
					    .await
 | 
				
			||||||
@@ -374,13 +377,6 @@ 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>")]
 | 
				
			||||||
pub async fn edit_reminder(
 | 
					pub async fn edit_reminder(
 | 
				
			||||||
@@ -578,7 +574,9 @@ 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
 | 
				
			||||||
        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 = ?",
 | 
				
			||||||
@@ -602,7 +600,10 @@ pub async fn delete_reminder(
 | 
				
			|||||||
    reminder: Json<DeleteReminder>,
 | 
					    reminder: Json<DeleteReminder>,
 | 
				
			||||||
    pool: &State<Pool<MySql>>,
 | 
					    pool: &State<Pool<MySql>>,
 | 
				
			||||||
) -> JsonResult {
 | 
					) -> JsonResult {
 | 
				
			||||||
    match sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", reminder.uid)
 | 
					    match sqlx::query!(
 | 
				
			||||||
 | 
					        "UPDATE reminders SET `status` = 'deleted', `status_change_time` = NOW() WHERE uid = ?",
 | 
				
			||||||
 | 
					        reminder.uid
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    .execute(pool.inner())
 | 
					    .execute(pool.inner())
 | 
				
			||||||
    .await
 | 
					    .await
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,8 +124,8 @@ pub struct Reminder {
 | 
				
			|||||||
    attachment: Option<Vec<u8>>,
 | 
					    attachment: Option<Vec<u8>>,
 | 
				
			||||||
    attachment_name: Option<String>,
 | 
					    attachment_name: Option<String>,
 | 
				
			||||||
    avatar: Option<String>,
 | 
					    avatar: Option<String>,
 | 
				
			||||||
    #[serde(with = "string")]
 | 
					    #[serde(with = "string_opt")]
 | 
				
			||||||
    channel: u64,
 | 
					    channel: Option<u64>,
 | 
				
			||||||
    content: String,
 | 
					    content: String,
 | 
				
			||||||
    embed_author: String,
 | 
					    embed_author: String,
 | 
				
			||||||
    embed_author_url: Option<String>,
 | 
					    embed_author_url: Option<String>,
 | 
				
			||||||
@@ -150,6 +150,8 @@ pub struct Reminder {
 | 
				
			|||||||
    uid: String,
 | 
					    uid: String,
 | 
				
			||||||
    username: Option<String>,
 | 
					    username: Option<String>,
 | 
				
			||||||
    utc_time: NaiveDateTime,
 | 
					    utc_time: NaiveDateTime,
 | 
				
			||||||
 | 
					    status: String,
 | 
				
			||||||
 | 
					    status_change_time: Option<NaiveDateTime>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(Serialize, Deserialize)]
 | 
				
			||||||
@@ -308,6 +310,34 @@ 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};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -372,7 +402,7 @@ pub async fn create_reminder(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // validate channel
 | 
					    // validate channel
 | 
				
			||||||
    let channel = ChannelId(reminder.channel).to_channel_cached(&ctx);
 | 
					    let channel = reminder.channel.map(|c| ChannelId(c).to_channel_cached(&ctx)).flatten();
 | 
				
			||||||
    let channel_exists = channel.is_some();
 | 
					    let channel_exists = channel.is_some();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let channel_matches_guild =
 | 
					    let channel_matches_guild =
 | 
				
			||||||
@@ -380,14 +410,14 @@ 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
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Err(json!({"error": "Channel not found"}));
 | 
					        return Err(json!({"error": "Channel not found"}));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let channel = create_database_channel(&ctx, ChannelId(reminder.channel), pool).await;
 | 
					    let channel = create_database_channel(&ctx, ChannelId(reminder.channel.unwrap()), pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Err(e) = channel {
 | 
					    if let Err(e) = channel {
 | 
				
			||||||
        warn!("`create_database_channel` returned an error code: {:?}", e);
 | 
					        warn!("`create_database_channel` returned an error code: {:?}", e);
 | 
				
			||||||
@@ -479,6 +509,7 @@ pub async fn create_reminder(
 | 
				
			|||||||
         attachment,
 | 
					         attachment,
 | 
				
			||||||
         attachment_name,
 | 
					         attachment_name,
 | 
				
			||||||
         channel_id,
 | 
					         channel_id,
 | 
				
			||||||
 | 
					         guild_id,
 | 
				
			||||||
         avatar,
 | 
					         avatar,
 | 
				
			||||||
         content,
 | 
					         content,
 | 
				
			||||||
         embed_author,
 | 
					         embed_author,
 | 
				
			||||||
@@ -501,11 +532,12 @@ 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,
 | 
				
			||||||
        channel,
 | 
					        channel,
 | 
				
			||||||
 | 
					        guild_id.0,
 | 
				
			||||||
        reminder.avatar,
 | 
					        reminder.avatar,
 | 
				
			||||||
        reminder.content,
 | 
					        reminder.content,
 | 
				
			||||||
        reminder.embed_author,
 | 
					        reminder.embed_author,
 | 
				
			||||||
@@ -560,7 +592,9 @@ 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
 | 
				
			||||||
            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 = ?",
 | 
				
			||||||
@@ -662,7 +696,17 @@ pub async fn dashboard_home(cookies: &CookieJar<'_>) -> Result<Template, Redirec
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/<_>")]
 | 
					#[get("/<_>")]
 | 
				
			||||||
pub async fn dashboard(cookies: &CookieJar<'_>) -> Result<Template, Redirect> {
 | 
					pub async fn dashboard_1(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))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,8 @@ 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()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -116,6 +118,8 @@ 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))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -249,11 +249,11 @@ div#pageNavbar a {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.navbar-item.pageTitle {
 | 
					.navbar-item.pageTitle {
 | 
				
			||||||
    flex-shrink: 1;
 | 
					    flex-shrink: 1;
 | 
				
			||||||
    text-wrap: nowrap;
 | 
					    white-space: nowrap;
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.navbar-burger, .navbar-burger:active, .navbar-burger.is-active {
 | 
					.dashboard-burger, .dashboard-burger:active, .dashboard-burger.is-active {
 | 
				
			||||||
    background-color: #adc99c !important;
 | 
					    background-color: #adc99c !important;
 | 
				
			||||||
    border-radius: 14px;
 | 
					    border-radius: 14px;
 | 
				
			||||||
    padding: 6px;
 | 
					    padding: 6px;
 | 
				
			||||||
@@ -291,10 +291,19 @@ div.dashboard-sidebar:not(.mobile-sidebar) {
 | 
				
			|||||||
    flex-direction: column;
 | 
					    flex-direction: column;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.guildList {
 | 
				
			||||||
 | 
					    flex-grow: 1;
 | 
				
			||||||
 | 
					    flex-shrink: 1;
 | 
				
			||||||
 | 
					    overflow: scroll;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer {
 | 
					div.dashboard-sidebar:not(.mobile-sidebar) .aside-footer {
 | 
				
			||||||
    position: fixed;
 | 
					    flex-shrink: 0;
 | 
				
			||||||
    bottom: 0;
 | 
					    flex-grow: 0;
 | 
				
			||||||
    width: 226px;
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.dashboard-sidebar svg {
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
div.mobile-sidebar {
 | 
					div.mobile-sidebar {
 | 
				
			||||||
@@ -644,9 +653,11 @@ li.highlight {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    p.title.pageTitle {
 | 
					    p.title.pageTitle {
 | 
				
			||||||
        visibility: hidden;
 | 
					        display: none;
 | 
				
			||||||
        text-wrap: nowrap;
 | 
					    }
 | 
				
			||||||
        overflow: hidden;
 | 
					
 | 
				
			||||||
 | 
					    .dashboard-frame {
 | 
				
			||||||
 | 
					        margin-top: 4rem !important;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -665,6 +676,7 @@ li.highlight {
 | 
				
			|||||||
/* 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;
 | 
				
			||||||
@@ -676,6 +688,76 @@ 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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* other stuff */
 | 
					/* other stuff */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.half-rem {
 | 
					.half-rem {
 | 
				
			||||||
@@ -713,15 +795,38 @@ 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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -751,5 +856,5 @@ a.switch-pane {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.figure-num {
 | 
					.figure-num {
 | 
				
			||||||
    font-size: 2em;
 | 
					    font-size: 2rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ 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 = {};
 | 
				
			||||||
@@ -33,7 +34,11 @@ let globalPatreon = false;
 | 
				
			|||||||
let guildPatreon = false;
 | 
					let guildPatreon = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function guildId() {
 | 
					function guildId() {
 | 
				
			||||||
    return document.querySelector(".guildList a.is-active").dataset["guild"];
 | 
					    return document.querySelector("li > a.is-active").parentElement.dataset["guild"];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function guildName() {
 | 
				
			||||||
 | 
					    return guildNames[guildId()];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function colorToInt(r, g, b) {
 | 
					function colorToInt(r, g, b) {
 | 
				
			||||||
@@ -52,7 +57,7 @@ function switch_pane(selector) {
 | 
				
			|||||||
        el.classList.add("is-hidden");
 | 
					        el.classList.add("is-hidden");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    document.getElementById(selector).classList.remove("is-hidden");
 | 
					    document.querySelector(`*[data-name=${selector}]`).classList.remove("is-hidden");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function update_select(sel) {
 | 
					function update_select(sel) {
 | 
				
			||||||
@@ -449,21 +454,19 @@ 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 $anchor = document.querySelector(
 | 
					    let $li = document.querySelector(`li[data-guild="${e.detail.guild_id}"]`);
 | 
				
			||||||
        `.switch-pane[data-guild="${e.detail.guild_id}"]`
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let hasError = false;
 | 
					    if ($li === null) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if ($anchor === null) {
 | 
					 | 
				
			||||||
        switch_pane("user-error");
 | 
					        switch_pane("user-error");
 | 
				
			||||||
        hasError = true;
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch_pane($anchor.dataset["pane"]);
 | 
					    switch_pane(e.detail.pane);
 | 
				
			||||||
    reset_guild_pane();
 | 
					    reset_guild_pane();
 | 
				
			||||||
    $anchor.classList.add("is-active");
 | 
					    $li.querySelector("li > a").classList.add("is-active");
 | 
				
			||||||
 | 
					    $li.querySelectorAll(`*[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
 | 
				
			||||||
@@ -471,15 +474,26 @@ document.addEventListener("guildSwitched", async (e) => {
 | 
				
			|||||||
            .forEach((el) => el.classList.remove("is-locked"));
 | 
					            .forEach((el) => el.classList.remove("is-locked"));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hasError = await fetch_channels(e.detail.guild_id);
 | 
					    const event = new CustomEvent("paneLoad", {
 | 
				
			||||||
 | 
					        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(e.detail.guild_id);
 | 
					        fetch_roles(ev.detail.guild_id);
 | 
				
			||||||
        fetch_templates(e.detail.guild_id);
 | 
					        fetch_templates(ev.detail.guild_id);
 | 
				
			||||||
        fetch_reminders(e.detail.guild_id);
 | 
					        fetch_reminders(ev.detail.guild_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        document.querySelectorAll("p.pageTitle").forEach((el) => {
 | 
					        document.querySelectorAll("p.pageTitle").forEach((el) => {
 | 
				
			||||||
            el.textContent = `${e.detail.guild_name} Reminders`;
 | 
					            el.textContent = `${guildName()} 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);
 | 
				
			||||||
@@ -684,36 +698,56 @@ 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}?name=${guild.name}`;
 | 
					                        $anchor.href = `/dashboard/${guild.id}/reminders`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        $anchor.addEventListener("click", async (e) => {
 | 
					                        const $li = $anchor.parentElement;
 | 
				
			||||||
 | 
					                        $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(/dashboard\/(\d+)/);
 | 
					                const matches = window.location.href.match(
 | 
				
			||||||
 | 
					                    /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,
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								web/static/js/reminder_errors.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/static/js/reminder_errors.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					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(".reminderTime").textContent = statusTime
 | 
				
			||||||
 | 
					                    .toLocal()
 | 
				
			||||||
 | 
					                    .toLocaleString(luxon.DateTime.DATETIME_MED);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                container.appendChild(newRow);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .finally(() => {
 | 
				
			||||||
 | 
					            $loader.classList.add("is-hidden");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,19 +0,0 @@
 | 
				
			|||||||
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', () => {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
@@ -47,7 +47,7 @@
 | 
				
			|||||||
        <p class="navbar-item pageTitle">
 | 
					        <p class="navbar-item pageTitle">
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <a role="button" class="navbar-burger is-right" aria-label="menu" aria-expanded="false"
 | 
					        <a role="button" class="dashboard-burger 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>
 | 
				
			||||||
@@ -332,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 id="guild" class="is-hidden">
 | 
					        <section data-name="reminders" class="is-hidden">
 | 
				
			||||||
            {% include "reminder_dashboard/reminder_dashboard" %}
 | 
					            {% include "reminder_dashboard/reminder_dashboard" %}
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
        <section id="reminder-errors" class="is-hidden">
 | 
					        <section data-name="errors" class="is-hidden">
 | 
				
			||||||
            {% include "reminder_dashboard/reminder_errors" %}
 | 
					            {% include "reminder_dashboard/reminder_errors" %}
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
        <section id="guild-error" class="is-hidden">
 | 
					        <section data-name="guild-error" class="is-hidden">
 | 
				
			||||||
            {% include "reminder_dashboard/guild_error" %}
 | 
					            {% include "reminder_dashboard/guild_error" %}
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
        <section id="user-error" class="is-hidden">
 | 
					        <section data-name="user-error" class="is-hidden">
 | 
				
			||||||
            {% include "reminder_dashboard/user_error" %}
 | 
					            {% include "reminder_dashboard/user_error" %}
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@@ -375,14 +375,28 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<template id="guildListEntry">
 | 
					<template id="guildListEntry">
 | 
				
			||||||
    <li>
 | 
					    <li>
 | 
				
			||||||
        <a class="switch-pane" data-pane="guild">
 | 
					        <a class="switch-pane" data-pane="reminders" data-slug="reminders">
 | 
				
			||||||
            <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/guild_reminder" %}
 | 
					    {% include "reminder_dashboard/templates/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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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/guild_reminder" %}
 | 
					        {% include "reminder_dashboard/templates/guild_reminder" %}
 | 
				
			||||||
        {% set creating = false %}
 | 
					        {% set creating = false %}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <br>
 | 
					    <br>
 | 
				
			||||||
@@ -46,6 +46,10 @@
 | 
				
			|||||||
    <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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
<div>
 | 
					<div id="reminderLog">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -165,6 +165,9 @@
 | 
				
			|||||||
            <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">
 | 
				
			||||||
@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<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="reminderTime">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user