loads of bug fixes
diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts
index 7f3b16d..6b8c058 100644
--- a/src/utils/calculate.ts
+++ b/src/utils/calculate.ts
@@ -11,14 +11,14 @@
     "messageDelete",
     "messageDeleteBulk",
     "messageReactionUpdate",
-    "messagePing",
     "messageMassPing",
     "messageAnnounce",
     "threadUpdate",
     "webhookUpdate",
     "guildMemberVerify",
-    "autoModeratorDeleted", // TODO: Not implemented
-    "nucleusSettingsUpdated"
+    "autoModeratorDeleted",
+    "nucleusSettingsUpdated",
+    "ticketUpdate"
 ]
 
 const tickets = [
diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts
index 7eaa369..da10cfb 100644
--- a/src/utils/confirmationMessage.ts
+++ b/src/utils/confirmationMessage.ts
@@ -1,33 +1,27 @@
-import Discord, { CommandInteraction, MessageActionRow, Message } from "discord.js";
+import Discord, { CommandInteraction, MessageActionRow, Message, MessageButton, TextInputComponent } from "discord.js";
+import { modalInteractionCollector } from "./dualCollector.js";
 import EmojiEmbed from "./generateEmojiEmbed.js"
 import getEmojiByName from "./getEmojiByName.js";
 
 class confirmationMessage {
     interaction: CommandInteraction;
-    title: string;
-    emoji: string;
-    description: string;
-    color: string;
-    customCallback: () => any;
+    title: string = "";
+    emoji: string = "";
+    description: string = "";
+    color: string = "";
+    customCallback: () => any = () => {};
     customButtonTitle: string;
     customButtonDisabled: boolean;
     customCallbackString: string = "";
     customCallbackClicked: boolean = false;
     customCallbackResponse: any = null;
-    customBoolean: () => any;
+    customBoolean: () => any = () => {}; // allow multiple booleans
     customBooleanClicked: boolean = null;
-    inverted: boolean;
+    inverted: boolean = false;
+    reason: string | null = null;
 
     constructor(interaction: CommandInteraction) {
         this.interaction = interaction;
-
-        this.title = "";
-        this.emoji = "";
-        this.description = "";
-        this.color = "";
-        this.inverted = false;
-        this.customCallback = () => {};
-        this.customBoolean = () => {};
     }
 
     setTitle(title: string) { this.title = title; return this }
@@ -52,8 +46,10 @@
         this.customBooleanClicked = false;
         return this;
     }
-
-
+    addReasonButton(reason: string) {
+        this.reason = reason;
+        return this;
+    }
     async send(editOnly?: boolean) {
         while (true) {
             let object = {
@@ -86,6 +82,12 @@
                         )
                         .setDisabled(this.customButtonDisabled)
                         .setEmoji(getEmojiByName("CONTROL.TICKET", "id"))
+                    ] : [])
+                    .concat(this.reason !== null ? [new Discord.MessageButton()
+                        .setCustomId("reason")
+                        .setLabel(`Edit Reason`)
+                        .setStyle("PRIMARY")
+                        .setEmoji(getEmojiByName("ICONS.EDIT", "id"))
                     ] : []))
                 ],
                 ephemeral: true,
@@ -132,9 +134,42 @@
                     this.customButtonDisabled = true;
                 }
                 editOnly = true;
+            } else if (component.customId === "reason") {
+                await component.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Editing reason`).addComponents(
+                    // @ts-ignore
+                    new MessageActionRow().addComponents(new TextInputComponent()
+                        .setCustomId("reason")
+                        .setLabel("Reason")
+                        .setMaxLength(2000)
+                        .setRequired(false)
+                        .setStyle("PARAGRAPH")
+                        .setPlaceholder("Spammed in #general")
+                        .setValue(this.reason ? this.reason : "")
+                    )
+                ))
+                await this.interaction.editReply({
+                    embeds: [new EmojiEmbed()
+                        .setTitle(this.title)
+                        .setDescription("Modal opened. If you can't see it, click back and try again.")
+                        .setStatus(this.color)
+                        .setEmoji(this.emoji)
+                    ], components: [new MessageActionRow().addComponents([new MessageButton()
+                        .setLabel("Back")
+                        .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                        .setStyle("PRIMARY")
+                        .setCustomId("back")
+                    ])]
+                });
+                let out;
+                try {
+                    out = await modalInteractionCollector(m, (m) => m.channel.id == this.interaction.channel.id, (m) => m.customId == "reason")
+                } catch (e) { continue }
+                if (out.fields) {
+                    return {newReason: out.fields.getTextInputValue("reason") ?? ""};
+                } else { return { newReason: this.reason } }
             }
         }
     }
 }
 
-export default confirmationMessage;
\ No newline at end of file
+export default confirmationMessage;
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 66d7e67..c0ae9be 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -132,26 +132,17 @@
     }
 }
 
-export class EventSchedulerDatabase {
-    events: Collection<EventSchedulerSchema>;
-    defaultData: GuildConfig;
+export class Premium {
+    premium: Collection<PremiumSchema>;
 
     async setup() {
-        this.events = database.collection<EventSchedulerSchema>("eventScheduler");
+        this.premium = database.collection<PremiumSchema>("premium");
         return this;
     }
 
-    async create(timestamp: Date, data: object) {
-        await this.events.insertOne({ timestamp: timestamp, data: data});
-    }
-
-    async getNext() {
-        let entry = await this.events.findOne({ timestamp: { $lte: new Date() }});
-        return entry;
-    }
-
-    async remove(timestamp: Date, data: object) {
-        await this.events.deleteOne({ timestamp: timestamp, data: data});
+    async hasPremium(guild: string) {
+        let entry = await this.premium.findOne({ appliesTo: { $in: [guild] } });
+        return entry != null;
     }
 }
 
@@ -222,6 +213,10 @@
         },
         staff: {
             channel: string | null,
+        },
+        attachments: {
+            channel: string | null,
+            saved: {}  // {channelID+messageID: log url (string)}
         }
     }
     verify: {
@@ -307,7 +302,9 @@
     note: string
 }
 
-export interface EventSchedulerSchema {
-    timestamp: Date,
-    data: object
+export interface PremiumSchema {
+    user: string,
+    level: number,
+    expires: Date,
+    appliesTo: string[]
 }
\ No newline at end of file
diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts
index 9210883..396b202 100644
--- a/src/utils/eventScheduler.ts
+++ b/src/utils/eventScheduler.ts
@@ -1,37 +1,70 @@
-import { EventSchedulerDatabase } from './database';
+import { Agenda } from "agenda/es.js";
 import client from './client.js';
+import * as fs from 'fs';
+import * as path from 'path';
+import config from '../config/main.json' assert {type: 'json'};
 
 class EventScheduler {
-    database: any;
-    next: {timestamp: Date, data: object, responded: boolean} | {};
+    private agenda: Agenda;
 
     constructor() {
-        this.database = EventSchedulerDatabase;
-        this.next = {};
+        this.agenda = new Agenda({db: {address: config.mongoUrl + "Nucleus", collection: 'eventScheduler'}})
+
+        this.agenda.define("unmuteRole", async (job: Agenda.job) => {
+            let guild = await client.guilds.fetch(job.attrs.data.guild);
+            let user = await guild.members.fetch(job.attrs.data.user);
+            let role = await guild.roles.fetch(job.attrs.data.role);
+            await user.roles.remove(role);
+            await job.remove();
+        })
+        this.agenda.define("deleteFile", async (job: Agenda.job) => {
+            fs.rm(path.resolve("dist/utils/temp", job.attrs.data.fileName), (err) => {})
+            await job.remove();
+        })
+        this.agenda.define("naturalUnmute", async (job: Agenda.job) => {
+            const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger
+            let guild = await client.guilds.fetch(job.attrs.data.guild);
+            let user = await guild.members.fetch(job.attrs.data.user);
+            if (user.communicationDisabledUntil === null) return
+            try { await client.database.history.create(
+                "unmute", user.guild.id, user.user, null, null, null, null
+            )} catch {}
+            let data = {
+                meta: {
+                    type: 'memberUnmute',
+                    displayName: 'Unmuted',
+                    calculateType: 'guildMemberPunish',
+                    color: NucleusColors.green,
+                    emoji: "PUNISH.MUTE.GREEN",
+                    timestamp: new Date().getTime()
+                },
+                list: {
+                    memberId: entry(user.user.id, `\`${user.user.id}\``),
+                    name: entry(user.user.id, renderUser(user.user)),
+                    unmuted: entry(new Date().getTime(), renderDelta(new Date().getTime())),
+                    unmutedBy: entry(null, "*Time out ended*")
+                },
+                hidden: {
+                    guild: guild.id
+                }
+            }
+            log(data);
+        })
     }
 
-    async create(timestamp: Date, data: object) {
-        await this.database.create(timestamp, data);
-        if (this.next === {}) {
-            this.next = this.next = await this.getNext();
-            return
-        }
-        if (timestamp.getTime() < (this.next as {timestamp: Date}).timestamp.getTime()) {
-            this.next = {timestamp: timestamp, data: data, responded: false};
-        }
+    async start() {
+        await new Promise(resolve => this.agenda.once('ready', resolve));
+        this.agenda.start()
+        return this
     }
 
-    async getNext() {
-        let entry = await this.database.getNext();
-        if (entry) {
-            this.next = entry;
-        }
-        return this.next;
+    async schedule(name: string, time: string, data: any) {
+        await this.agenda.schedule(time, name, data)
     }
 
-    async delete(timestamp: Date, data: object) {
-        await this.database.delete(timestamp, data);
-    } // TODO: add a loop
+    cancel(name: string, data: Object) {
+        this.agenda.cancel({name, data})
+    }
 }
 
 export default EventScheduler;
\ No newline at end of file
diff --git a/src/utils/generateKeyValueList.ts b/src/utils/generateKeyValueList.ts
index c9eb0c8..1a76fa7 100644
--- a/src/utils/generateKeyValueList.ts
+++ b/src/utils/generateKeyValueList.ts
@@ -1,14 +1,18 @@
 const forceCaps = [
     "ID",
-    "NSFW"
+    "NSFW",
+    "URL"
 ]
 
 export function capitalize(s: string) {
     s = s.replace(/([A-Z])/g, ' $1');
-    return forceCaps.includes(s.toUpperCase()) ? s.toUpperCase() : s[0]
-        .toUpperCase() + s.slice(1)
-        .toLowerCase()
-        .replace("discord", "Discord");
+    s = s.split(" ").map(word => {
+        return forceCaps.includes(word.toUpperCase()) ? word.toUpperCase() : word[0]
+            .toUpperCase() + word.slice(1)
+            .toLowerCase()
+            .replace("discord", "Discord")
+    }).join(" ");
+    return s
 }
 
 export function toCapitals(s: string) {
diff --git a/src/utils/plurals.ts b/src/utils/plurals.ts
index f956057..48b889c 100644
--- a/src/utils/plurals.ts
+++ b/src/utils/plurals.ts
@@ -1,4 +1,5 @@
 function addPlural(amount: any, unit: string) {
+    amount = amount.toString();
     if (amount === '1') return `${amount} ${unit}`
     return `${amount} ${unit}s`
 }
diff --git a/src/utils/scanners.ts b/src/utils/scanners.ts
deleted file mode 100644
index 5abd726..0000000
--- a/src/utils/scanners.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as us from 'unscan'
-import fetch from 'node-fetch'
-import { writeFileSync } from 'fs'
-import generateFileName from './temp/generateFileName.js'
-import * as path from 'path'
-import {fileURLToPath} from 'url';
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-export async function testNSFW(link: string): Promise<JSON> {
-    const image = (await (await fetch(link)).buffer()).toString('base64')
-    let fileName = generateFileName(link.split('/').pop().split('.').pop())
-    let p = path.join(__dirname, '/temp', fileName)
-    writeFileSync(p, image, 'base64')
-    let result = await us.nsfw.file(p)
-    return result
-}
-
-export async function testMalware(link: string): Promise<JSON> {
-    const file = (await (await fetch(link)).buffer()).toString('base64')
-    let fileName = generateFileName(link.split('/').pop().split('.').pop())
-    let p = path.join(__dirname, '/temp', fileName)
-    writeFileSync(p, file, 'base64')
-    let result = await us.malware.file(p)
-    return result
-}
-
-export async function testLink(link: string): Promise<JSON> {
-    return await us.link.scan(link)
-}
diff --git a/src/utils/temp/generateFileName.ts b/src/utils/temp/generateFileName.ts
index 98240a1..f9662ad 100644
--- a/src/utils/temp/generateFileName.ts
+++ b/src/utils/temp/generateFileName.ts
@@ -1,5 +1,10 @@
 import * as fs from 'fs';
 import * as crypto from 'crypto';
+import client from '../client.js';
+import * as path from 'path'
+import {fileURLToPath} from 'url';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
 
 export default function generateFileName(ending: string): string {
     let fileName = crypto.randomBytes(35).toString('hex');
@@ -7,5 +12,6 @@
     if (fs.existsSync(`./${fileName}`)) {
         fileName = generateFileName(ending);
     }
-    return fileName + '.' + ending;
-}
\ No newline at end of file
+    client.database.eventScheduler.schedule("deleteFile", new Date().getTime() + (60 * 1000), {fileName: `${fileName}.${ending}`});
+    return path.join(__dirname, fileName + '.' + ending);
+}