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);
+}