blob: 0688888d81fcda30c66bdd291035b9bd27bf219f [file] [log] [blame]
TheCodedProf9c51a7e2023-02-27 17:11:13 -05001import { ButtonStyle, CommandInteraction, ComponentType, GuildMember, Message, MessageComponentInteraction } from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +01002import type Discord from "discord.js";
3import { Collection, MongoClient } from "mongodb";
pineafana2e39c72023-02-21 18:37:32 +00004import config from "../config/main.js";
TheCodedProf633866f2023-02-03 17:06:00 -05005import client from "../utils/client.js";
TheCodedProf088b1b22023-02-28 17:31:11 -05006import * as crypto from "crypto";
pineafan4edb7762022-06-26 19:21:04 +01007
8const mongoClient = new MongoClient(config.mongoUrl);
pineafan63fc5e22022-08-04 22:04:10 +01009await mongoClient.connect();
TheCodedProf8d577fa2023-03-01 13:06:40 -050010const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010011
pineafan4edb7762022-06-26 19:21:04 +010012export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010013 guilds: Collection<GuildConfig>;
pineafan63fc5e22022-08-04 22:04:10 +010014 defaultData: GuildConfig | null;
15
16 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010017 this.guilds = database.collection<GuildConfig>("guilds");
pineafan63fc5e22022-08-04 22:04:10 +010018 this.defaultData = null;
pineafan6fb3e072022-05-20 19:27:23 +010019 }
20
Skyler Greyad002172022-08-16 18:48:26 +010021 async setup(): Promise<Guilds> {
Skyler Grey11236ba2022-08-08 21:13:33 +010022 this.defaultData = (await import("../config/default.json", { assert: { type: "json" } }))
23 .default as unknown as GuildConfig;
24 return this;
pineafan63fc5e22022-08-04 22:04:10 +010025 }
26
Skyler Greyad002172022-08-16 18:48:26 +010027 async read(guild: string): Promise<GuildConfig> {
pineafan63fc5e22022-08-04 22:04:10 +010028 const entry = await this.guilds.findOne({ id: guild });
PineaFandf4996f2023-01-01 14:20:06 +000029 return Object.assign({}, this.defaultData, entry);
pineafan6fb3e072022-05-20 19:27:23 +010030 }
31
Skyler Grey11236ba2022-08-08 21:13:33 +010032 async write(guild: string, set: object | null, unset: string[] | string = []) {
pineafan63fc5e22022-08-04 22:04:10 +010033 // eslint-disable-next-line @typescript-eslint/no-explicit-any
34 const uo: Record<string, any> = {};
35 if (!Array.isArray(unset)) unset = [unset];
36 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010037 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010038 }
Skyler Grey75ea9172022-08-06 10:22:23 +010039 const out = { $set: {}, $unset: {} };
40 if (set) out.$set = set;
41 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010042 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010043 }
44
pineafan63fc5e22022-08-04 22:04:10 +010045 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010046 async append(guild: string, key: string, value: any) {
47 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010048 await this.guilds.updateOne(
49 { id: guild },
50 {
51 $addToSet: { [key]: { $each: value } }
52 },
53 { upsert: true }
54 );
pineafan6702cef2022-06-13 17:52:37 +010055 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010056 await this.guilds.updateOne(
57 { id: guild },
58 {
59 $addToSet: { [key]: value }
60 },
61 { upsert: true }
62 );
pineafan6702cef2022-06-13 17:52:37 +010063 }
64 }
65
Skyler Grey75ea9172022-08-06 10:22:23 +010066 async remove(
67 guild: string,
68 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010069 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010070 value: any,
71 innerKey?: string | null
72 ) {
pineafan02ba0232022-07-24 22:16:15 +010073 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010074 await this.guilds.updateOne(
75 { id: guild },
76 {
77 $pull: { [key]: { [innerKey]: { $eq: value } } }
78 },
79 { upsert: true }
80 );
pineafan0bc04162022-07-25 17:22:26 +010081 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010082 await this.guilds.updateOne(
83 { id: guild },
84 {
85 $pullAll: { [key]: value }
86 },
87 { upsert: true }
88 );
pineafan6702cef2022-06-13 17:52:37 +010089 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010090 await this.guilds.updateOne(
91 { id: guild },
92 {
93 $pullAll: { [key]: [value] }
94 },
95 { upsert: true }
96 );
pineafan6702cef2022-06-13 17:52:37 +010097 }
pineafan6fb3e072022-05-20 19:27:23 +010098 }
pineafane23c4ec2022-07-27 21:56:27 +010099
100 async delete(guild: string) {
101 await this.guilds.deleteOne({ id: guild });
102 }
pineafan6fb3e072022-05-20 19:27:23 +0100103}
104
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500105interface TranscriptEmbed {
106 title?: string;
107 description?: string;
108 fields?: {
109 name: string;
110 value: string;
111 inline: boolean;
112 }[];
113 footer?: {
114 text: string;
115 iconURL?: string;
116 };
117}
118
119interface TranscriptComponent {
120 type: number;
121 style?: ButtonStyle;
122 label?: string;
123 description?: string;
124 placeholder?: string;
125 emojiURL?: string;
126}
127
128interface TranscriptAuthor {
129 username: string;
130 discriminator: number;
131 nickname?: string;
132 id: string;
133 iconURL?: string;
134 topRole: {
135 color: number;
136 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500137 };
138 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500139}
140
141interface TranscriptAttachment {
142 url: string;
143 filename: string;
144 size: number;
145 log?: string;
146}
147
148interface TranscriptMessage {
149 id: string;
150 author: TranscriptAuthor;
151 content?: string;
152 embeds?: TranscriptEmbed[];
153 components?: TranscriptComponent[][];
154 editedTimestamp?: number;
155 createdTimestamp: number;
156 flags?: string[];
157 attachments?: TranscriptAttachment[];
158 stickerURLs?: string[];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500159 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500160}
161
162interface TranscriptSchema {
163 code: string;
164 for: TranscriptAuthor;
165 type: "ticket" | "purge"
166 guild: string;
167 channel: string;
168 messages: TranscriptMessage[];
169 createdTimestamp: number;
170 createdBy: TranscriptAuthor;
171}
172
173export class Transcript {
174 transcripts: Collection<TranscriptSchema>;
175
176 constructor() {
177 this.transcripts = database.collection<TranscriptSchema>("transcripts");
178 }
179
180 async create(transcript: Omit<TranscriptSchema, "code">) {
181 let code;
182 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500183 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500184 } while (await this.transcripts.findOne({ code: code }));
185
186 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }));
187 if(doc.acknowledged) return code;
188 else return null;
189 }
190
191 async read(code: string) {
192 return await this.transcripts.findOne({ code: code });
193 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500194
195 async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
196 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
197 const newOut: Omit<TranscriptSchema, "code"> = {
198 type: "ticket",
199 for: {
200 username: member!.user.username,
201 discriminator: parseInt(member!.user.discriminator),
202 id: member!.user.id,
203 topRole: {
204 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500205 },
206 iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
207 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500208 },
209 guild: interaction.guild!.id,
210 channel: interaction.channel!.id,
211 messages: [],
212 createdTimestamp: Date.now(),
213 createdBy: {
214 username: interaction.user.username,
215 discriminator: parseInt(interaction.user.discriminator),
216 id: interaction.user.id,
217 topRole: {
218 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500219 },
220 iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
221 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500222 }
223 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500224 if(member.nickname) newOut.for.nickname = member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500225 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
226 messages.reverse().forEach((message) => {
227 const msg: TranscriptMessage = {
228 id: message.id,
229 author: {
230 username: message.author.username,
231 discriminator: parseInt(message.author.discriminator),
232 id: message.author.id,
233 topRole: {
234 color: message.member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500235 },
236 iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
237 bot: message.author.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500238 },
239 createdTimestamp: message.createdTimestamp
240 };
TheCodedProf088b1b22023-02-28 17:31:11 -0500241 if(message.member?.nickname) msg.author.nickname = message.member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500242 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
243 if (message.content) msg.content = message.content;
244 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
245 const obj: TranscriptEmbed = {};
246 if (embed.title) obj.title = embed.title;
247 if (embed.description) obj.description = embed.description;
248 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
249 return {
250 name: field.name,
251 value: field.value,
252 inline: field.inline ?? false
253 }
254 });
255 if (embed.footer) obj.footer = {
256 text: embed.footer.text,
257 };
258 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
259 return obj;
260 });
261 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
262 const obj: TranscriptComponent = {
263 type: child.type
264 }
265 if (child.type === ComponentType.Button) {
266 obj.style = child.style;
267 obj.label = child.label ?? "";
268 } else if (child.type > 2) {
269 obj.placeholder = child.placeholder ?? "";
270 }
271 return obj
272 }));
273 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
274 msg.flags = message.flags.toArray();
275
276 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
277 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
278 newOut.messages.push(msg);
279 });
280 return newOut;
281 }
282
283 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
284 let out = "";
285 for (const message of transcript.messages) {
286 if (message.referencedMessage) {
287 if (Array.isArray(message.referencedMessage)) {
288 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
289 }
290 else out += `> [Reply To] ${message.referencedMessage}\n`;
291 }
292 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id}) `;
293 out += `[${new Date(message.createdTimestamp).toISOString()}] `;
294 if (message.editedTimestamp) out += `[Edited: ${new Date(message.editedTimestamp).toISOString()}] `;
295 out += "\n";
296 if (message.content) out += `[Content]\n${message.content}\n\n`;
297 if (message.embeds) {
298 for (const embed of message.embeds) {
299 out += `[Embed]\n`;
300 if (embed.title) out += `| Title: ${embed.title}\n`;
301 if (embed.description) out += `| Description: ${embed.description}\n`;
302 if (embed.fields) {
303 for (const field of embed.fields) {
304 out += `| Field: ${field.name} - ${field.value}\n`;
305 }
306 }
307 if (embed.footer) {
308 out += `|Footer: ${embed.footer.text}\n`;
309 }
310 out += "\n";
311 }
312 }
313 if (message.components) {
314 for (const component of message.components) {
315 out += `[Component]\n`;
316 for (const button of component) {
317 out += `| Button: ${button.label ?? button.description}\n`;
318 }
319 out += "\n";
320 }
321 }
322 if (message.attachments) {
323 for (const attachment of message.attachments) {
324 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
325 }
326 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500327 out += "\n\n"
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500328 }
329 return out
330 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500331}
332
pineafan4edb7762022-06-26 19:21:04 +0100333export class History {
334 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100335
pineafan3a02ea32022-08-11 21:35:04 +0100336 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100337 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100338 }
339
Skyler Grey75ea9172022-08-06 10:22:23 +0100340 async create(
341 type: string,
342 guild: string,
343 user: Discord.User,
344 moderator: Discord.User | null,
345 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100346 before?: string | null,
347 after?: string | null,
348 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100349 ) {
pineafan4edb7762022-06-26 19:21:04 +0100350 await this.histories.insertOne({
351 type: type,
352 guild: guild,
353 user: user.id,
pineafan3a02ea32022-08-11 21:35:04 +0100354 moderator: moderator ? moderator.id : null,
pineafan4edb7762022-06-26 19:21:04 +0100355 reason: reason,
356 occurredAt: new Date(),
pineafan3a02ea32022-08-11 21:35:04 +0100357 before: before ?? null,
358 after: after ?? null,
359 amount: amount ?? null
pineafan4edb7762022-06-26 19:21:04 +0100360 });
361 }
362
363 async read(guild: string, user: string, year: number) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100364 const entry = (await this.histories
365 .find({
366 guild: guild,
367 user: user,
368 occurredAt: {
369 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
370 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
371 }
372 })
373 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100374 return entry;
375 }
pineafane23c4ec2022-07-27 21:56:27 +0100376
377 async delete(guild: string) {
378 await this.histories.deleteMany({ guild: guild });
379 }
pineafan4edb7762022-06-26 19:21:04 +0100380}
381
TheCodedProfb5e9d552023-01-29 15:43:26 -0500382interface ScanCacheSchema {
383 addedAt: Date;
384 hash: string;
385 data: boolean;
386 tags: string[];
387}
388
389export class ScanCache {
390 scanCache: Collection<ScanCacheSchema>;
391
392 constructor() {
393 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
394 }
395
396 async read(hash: string) {
397 return await this.scanCache.findOne({ hash: hash });
398 }
399
400 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProf1f675042023-02-16 17:01:29 -0500401 await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500402 }
403
404 async cleanup() {
405 await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
406 }
407}
408
PineaFan538d3752023-01-12 21:48:23 +0000409export class PerformanceTest {
410 performanceData: Collection<PerformanceDataSchema>;
411
412 constructor() {
413 this.performanceData = database.collection<PerformanceDataSchema>("performance");
414 }
415
416 async record(data: PerformanceDataSchema) {
417 data.timestamp = new Date();
418 await this.performanceData.insertOne(data);
419 }
420 async read() {
421 return await this.performanceData.find({}).toArray();
422 }
423}
424
425export interface PerformanceDataSchema {
426 timestamp?: Date;
427 discord: number;
428 databaseRead: number;
429 resources: {
430 cpu: number;
431 memory: number;
432 temperature: number;
433 }
434}
435
pineafan4edb7762022-06-26 19:21:04 +0100436export class ModNotes {
437 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100438
pineafan3a02ea32022-08-11 21:35:04 +0100439 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100440 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100441 }
442
443 async create(guild: string, user: string, note: string | null) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100444 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100445 }
446
447 async read(guild: string, user: string) {
pineafan63fc5e22022-08-04 22:04:10 +0100448 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100449 return entry?.note ?? null;
450 }
TheCodedProf267563a2023-01-21 17:00:57 -0500451
452 async delete(guild: string) {
453 await this.modNotes.deleteMany({ guild: guild });
454 }
pineafan4edb7762022-06-26 19:21:04 +0100455}
456
pineafan73a7c4a2022-07-24 10:38:04 +0100457export class Premium {
458 premium: Collection<PremiumSchema>;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500459 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
460 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100461
pineafan3a02ea32022-08-11 21:35:04 +0100462 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100463 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500464 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100465 }
466
TheCodedProf633866f2023-02-03 17:06:00 -0500467 async updateUser(user: string, level: number) {
468 if(!(await this.userExists(user))) await this.createUser(user, level);
469 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
470 }
471
472 async userExists(user: string): Promise<boolean> {
473 const entry = await this.premium.findOne({ user: user });
474 return entry ? true : false;
475 }
476
477 async createUser(user: string, level: number) {
478 await this.premium.insertOne({ user: user, appliesTo: [], level: level });
479 }
480
TheCodedProfaa3fe992023-02-25 21:53:09 -0500481 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500482 // [Has premium, user giving premium, level, is mod: if given automatically]
483 const cached = this.cache.get(guild);
484 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500485 const entries = await this.premium.find({}).toArray();
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500486 const members = (await client.guilds.fetch(guild)).members.cache
TheCodedProf94ff6de2023-02-22 17:47:26 -0500487 for(const {user} of entries) {
488 const member = members.get(user);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500489 if(member) { //TODO: Notify user if they've given premium to a server that has since gotten premium via a mod.
TheCodedProf94ff6de2023-02-22 17:47:26 -0500490 const modPerms = //TODO: Create list in config for perms
491 member.permissions.has("Administrator") ||
492 member.permissions.has("ManageChannels") ||
493 member.permissions.has("ManageRoles") ||
494 member.permissions.has("ManageEmojisAndStickers") ||
495 member.permissions.has("ManageWebhooks") ||
496 member.permissions.has("ManageGuild") ||
497 member.permissions.has("KickMembers") ||
498 member.permissions.has("BanMembers") ||
499 member.permissions.has("ManageEvents") ||
500 member.permissions.has("ManageMessages") ||
501 member.permissions.has("ManageThreads")
502 const entry = entries.find(e => e.user === member.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500503 if(entry && (entry.level === 3) && modPerms) {
504 this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
505 return [true, member.id, entry.level, true];
506 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500507 }
508 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100509 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500510 appliesTo: {
511 $elemMatch: {
512 $eq: guild
513 }
514 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100515 });
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500516 this.cache.set(guild, [entry ? true : false, entry?.user ?? "", entry?.level ?? 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500517 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500518 }
519
TheCodedProf633866f2023-02-03 17:06:00 -0500520 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProf267563a2023-01-21 17:00:57 -0500521 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500522 if (!entry) return null;
523 return entry;
524 }
525
TheCodedProf94ff6de2023-02-22 17:47:26 -0500526 async checkAllPremium(member?: GuildMember) {
TheCodedProf633866f2023-02-03 17:06:00 -0500527 const entries = await this.premium.find({}).toArray();
TheCodedProf94ff6de2023-02-22 17:47:26 -0500528 if(member) {
529 const entry = entries.find(e => e.user === member.id);
530 if(entry) {
531 const expiresAt = entry.expiresAt;
532 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
533 }
534 const roles = member.roles;
535 let level = 0;
536 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500537 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500538 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500539 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500540 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500541 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500542 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500543 level = 3;
544 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500545 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500546 if (level > 0) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500547 await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
TheCodedProf633866f2023-02-03 17:06:00 -0500548 } else {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500549 await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
550 }
551 } else {
552 const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
553 for(const {roles, id} of members.values()) {
554 const entry = entries.find(e => e.user === id);
555 if(entry) {
556 const expiresAt = entry.expiresAt;
557 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
558 }
559 let level: number = 0;
560 if (roles.cache.has("1066468879309750313")) {
561 level = 99;
562 } else if (roles.cache.has("1066465491713003520")) {
563 level = 1;
564 } else if (roles.cache.has("1066439526496604194")) {
565 level = 2;
566 } else if (roles.cache.has("1066464134322978912")) {
567 level = 3;
568 }
569 await this.updateUser(id, level);
570 if (level > 0) {
571 await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
572 } else {
573 await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
574 }
TheCodedProf633866f2023-02-03 17:06:00 -0500575 }
576 }
TheCodedProf267563a2023-01-21 17:00:57 -0500577 }
578
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500579 async addPremium(user: string, guild: string) {
580 const { level } = (await this.fetchUser(user))!;
581 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500582 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100583 }
TheCodedProffc420b72023-01-24 17:14:38 -0500584
585 removePremium(user: string, guild: string) {
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500586 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProffc420b72023-01-24 17:14:38 -0500587 return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
588 }
pineafan4edb7762022-06-26 19:21:04 +0100589}
590
pineafan6fb3e072022-05-20 19:27:23 +0100591export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100592 id: string;
593 version: number;
PineaFan100df682023-01-02 13:26:08 +0000594 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100595 filters: {
596 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100597 NSFW: boolean;
598 size: boolean;
599 };
600 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100601 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100602 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100603 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100604 strict: string[];
605 loose: string[];
606 };
pineafan6fb3e072022-05-20 19:27:23 +0100607 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100608 users: string[];
609 roles: string[];
610 channels: string[];
611 };
612 };
pineafan6fb3e072022-05-20 19:27:23 +0100613 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100614 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000615 allowed: {
616 channels: string[];
617 roles: string[];
618 users: string[];
619 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100620 };
pineafan6fb3e072022-05-20 19:27:23 +0100621 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100622 mass: number;
623 everyone: boolean;
624 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100625 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100626 roles: string[];
627 rolesToMention: string[];
628 users: string[];
629 channels: string[];
630 };
631 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500632 clean: {
633 channels: string[];
634 allowed: {
635 user: string[];
636 roles: string[];
637 }
638 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100639 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500640 autoPublish: {
641 enabled: boolean;
642 channels: string[];
643 }
pineafan6fb3e072022-05-20 19:27:23 +0100644 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100645 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100646 role: string | null;
647 ping: string | null;
648 channel: string | null;
649 message: string | null;
650 };
651 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100652 logging: {
653 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100654 enabled: boolean;
655 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100656 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100657 };
pineafan6fb3e072022-05-20 19:27:23 +0100658 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100659 channel: string | null;
660 };
pineafan73a7c4a2022-07-24 10:38:04 +0100661 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100662 channel: string | null;
663 saved: Record<string, string>;
664 };
665 };
pineafan6fb3e072022-05-20 19:27:23 +0100666 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000667 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100668 role: string | null;
669 };
pineafan6fb3e072022-05-20 19:27:23 +0100670 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100671 enabled: boolean;
672 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100673 types: string;
674 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100675 useCustom: boolean;
676 supportRole: string | null;
677 maxTickets: number;
678 };
pineafan6fb3e072022-05-20 19:27:23 +0100679 moderation: {
680 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100681 timeout: boolean;
682 role: string | null;
683 text: string | null;
684 link: string | null;
685 };
pineafan6fb3e072022-05-20 19:27:23 +0100686 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100687 text: string | null;
688 link: string | null;
689 };
pineafan6fb3e072022-05-20 19:27:23 +0100690 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100691 text: string | null;
692 link: string | null;
693 };
pineafan6fb3e072022-05-20 19:27:23 +0100694 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100695 text: string | null;
696 link: string | null;
697 };
pineafan6fb3e072022-05-20 19:27:23 +0100698 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100699 text: string | null;
700 link: string | null;
701 };
pineafan6fb3e072022-05-20 19:27:23 +0100702 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100703 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500704 text: null;
705 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100706 };
PineaFane6ba7882023-01-18 20:41:16 +0000707 nick: {
708 text: string | null;
709 link: string | null;
710 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100711 };
pineafan6fb3e072022-05-20 19:27:23 +0100712 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100713 name: string;
714 retainPrevious: boolean;
715 nullable: boolean;
716 track: string[];
717 manageableBy: string[];
718 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100719 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100720 enabled: boolean;
721 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100722 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100723 name: string;
724 description: string;
725 min: number;
726 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100727 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100728 name: string;
729 description: string | null;
730 role: string;
731 }[];
732 }[];
733 };
734 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100735}
pineafan4edb7762022-06-26 19:21:04 +0100736
737export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100738 type: string;
739 guild: string;
740 user: string;
741 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100742 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100743 occurredAt: Date;
744 before: string | null;
745 after: string | null;
746 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100747}
748
749export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100750 guild: string;
751 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100752 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100753}
754
pineafan73a7c4a2022-07-24 10:38:04 +0100755export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100756 user: string;
757 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100758 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -0500759 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100760}