blob: 06c41f0bed580d07072097d1f42890f591ba3bba [file] [log] [blame]
Samuel Shuert27bf3cd2023-03-03 15:51:25 -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";
Samuel Shuert27bf3cd2023-03-03 15:51:25 -05004import config from "../config/main.js";
5import client from "../utils/client.js";
6import * as crypto from "crypto";
7import _ from "lodash";
8import defaultData from '../config/default.js';
9// config.mongoOptions.host, {
10// auth: {
11// username: config.mongoOptions.username,
12// password: config.mongoOptions.password
13// },
14// authSource: config.mongoOptions.authSource
15// }
16// mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority
17const username = encodeURIComponent(config.mongoOptions.username);
18const password = encodeURIComponent(config.mongoOptions.password);
19const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"});
pineafan63fc5e22022-08-04 22:04:10 +010020await mongoClient.connect();
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050021const database = mongoClient.db();
22
23const collectionOptions = { authdb: "admin" };
pineafan6fb3e072022-05-20 19:27:23 +010024
Samuel Shuertcc63dee2023-03-03 18:54:29 -050025const getIV = () => crypto.randomBytes(16);
26
27
pineafan4edb7762022-06-26 19:21:04 +010028export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010029 guilds: Collection<GuildConfig>;
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050030 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010031
32 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010033 this.guilds = database.collection<GuildConfig>("guilds");
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050034 this.defaultData = defaultData;
pineafan63fc5e22022-08-04 22:04:10 +010035 }
36
Skyler Greyad002172022-08-16 18:48:26 +010037 async read(guild: string): Promise<GuildConfig> {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050038 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010039 const entry = await this.guilds.findOne({ id: guild });
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050040 const data = _.clone(this.defaultData!);
41 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010042 }
43
Skyler Grey11236ba2022-08-08 21:13:33 +010044 async write(guild: string, set: object | null, unset: string[] | string = []) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050045 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010046 // eslint-disable-next-line @typescript-eslint/no-explicit-any
47 const uo: Record<string, any> = {};
48 if (!Array.isArray(unset)) unset = [unset];
49 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010050 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010051 }
Skyler Grey75ea9172022-08-06 10:22:23 +010052 const out = { $set: {}, $unset: {} };
53 if (set) out.$set = set;
54 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010055 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010056 }
57
pineafan63fc5e22022-08-04 22:04:10 +010058 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010059 async append(guild: string, key: string, value: any) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050060 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010061 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010062 await this.guilds.updateOne(
63 { id: guild },
64 {
65 $addToSet: { [key]: { $each: value } }
66 },
67 { upsert: true }
68 );
pineafan6702cef2022-06-13 17:52:37 +010069 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010070 await this.guilds.updateOne(
71 { id: guild },
72 {
73 $addToSet: { [key]: value }
74 },
75 { upsert: true }
76 );
pineafan6702cef2022-06-13 17:52:37 +010077 }
78 }
79
Skyler Grey75ea9172022-08-06 10:22:23 +010080 async remove(
81 guild: string,
82 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010083 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010084 value: any,
85 innerKey?: string | null
86 ) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -050087 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +010088 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010089 await this.guilds.updateOne(
90 { id: guild },
91 {
92 $pull: { [key]: { [innerKey]: { $eq: value } } }
93 },
94 { upsert: true }
95 );
pineafan0bc04162022-07-25 17:22:26 +010096 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010097 await this.guilds.updateOne(
98 { id: guild },
99 {
100 $pullAll: { [key]: value }
101 },
102 { upsert: true }
103 );
pineafan6702cef2022-06-13 17:52:37 +0100104 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100105 await this.guilds.updateOne(
106 { id: guild },
107 {
108 $pullAll: { [key]: [value] }
109 },
110 { upsert: true }
111 );
pineafan6702cef2022-06-13 17:52:37 +0100112 }
pineafan6fb3e072022-05-20 19:27:23 +0100113 }
pineafane23c4ec2022-07-27 21:56:27 +0100114
115 async delete(guild: string) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500116 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100117 await this.guilds.deleteOne({ id: guild });
118 }
pineafan6fb3e072022-05-20 19:27:23 +0100119}
120
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500121interface TranscriptEmbed {
122 title?: string;
123 description?: string;
124 fields?: {
125 name: string;
126 value: string;
127 inline: boolean;
128 }[];
129 footer?: {
130 text: string;
131 iconURL?: string;
132 };
133 color?: number;
134 timestamp?: string;
135 author?: {
136 name: string;
137 iconURL?: string;
138 url?: string;
139 };
140}
141
142interface TranscriptComponent {
143 type: number;
144 style?: ButtonStyle;
145 label?: string;
146 description?: string;
147 placeholder?: string;
148 emojiURL?: string;
149}
150
151interface TranscriptAuthor {
152 username: string;
153 discriminator: number;
154 nickname?: string;
155 id: string;
156 iconURL?: string;
157 topRole: {
158 color: number;
159 badgeURL?: string;
160 };
161 bot: boolean;
162}
163
164interface TranscriptAttachment {
165 url: string;
166 filename: string;
167 size: number;
168 log?: string;
169}
170
171interface TranscriptMessage {
172 id: string;
173 author: TranscriptAuthor;
174 content?: string;
175 embeds?: TranscriptEmbed[];
176 components?: TranscriptComponent[][];
177 editedTimestamp?: number;
178 createdTimestamp: number;
179 flags?: string[];
180 attachments?: TranscriptAttachment[];
181 stickerURLs?: string[];
182 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
183}
184
185interface TranscriptSchema {
186 code: string;
187 for: TranscriptAuthor;
188 type: "ticket" | "purge"
189 guild: string;
190 channel: string;
191 messages: TranscriptMessage[];
192 createdTimestamp: number;
193 createdBy: TranscriptAuthor;
194}
195
196export class Transcript {
197 transcripts: Collection<TranscriptSchema>;
198
199 constructor() {
200 this.transcripts = database.collection<TranscriptSchema>("transcripts");
201 }
202
203 async create(transcript: Omit<TranscriptSchema, "code">) {
204 // console.log("Transcript create")
205 let code;
206 do {
207 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
208 } while (await this.transcripts.findOne({ code: code }));
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500209 const key = crypto.randomBytes(32**2).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-").substring(0, 32);
210 const iv = getIV().toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
211 for(const message of transcript.messages) {
212 if(message.content) {
213 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
214 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
215 }
216 }
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500217
218 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500219 if(doc.acknowledged) return [code, key, iv];
220 else return [null, null, null];
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500221 }
222
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500223 async read(code: string, key: string, iv: string) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500224 // console.log("Transcript read")
Samuel Shuertcc63dee2023-03-03 18:54:29 -0500225 const doc = await this.transcripts.findOne({ code: code });
226 if(!doc) return null;
227 for(const message of doc.messages) {
228 if(message.content) {
229 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
230 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
231 }
232 }
233 return doc;
234 }
235
236 async deleteAll(guild: string) {
237 // console.log("Transcript delete")
238 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
239 for (const doc of filteredDocs) {
240 await this.transcripts.deleteOne({ code: doc.code });
241 }
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500242 }
243
244 async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
245 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
246 const newOut: Omit<TranscriptSchema, "code"> = {
247 type: "ticket",
248 for: {
249 username: member!.user.username,
250 discriminator: parseInt(member!.user.discriminator),
251 id: member!.user.id,
252 topRole: {
253 color: member!.roles.highest.color
254 },
255 iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
256 bot: member!.user.bot
257 },
258 guild: interaction.guild!.id,
259 channel: interaction.channel!.id,
260 messages: [],
261 createdTimestamp: Date.now(),
262 createdBy: {
263 username: interaction.user.username,
264 discriminator: parseInt(interaction.user.discriminator),
265 id: interaction.user.id,
266 topRole: {
267 color: interactionMember?.roles.highest.color ?? 0x000000
268 },
269 iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
270 bot: interaction.user.bot
271 }
272 }
273 if(member.nickname) newOut.for.nickname = member.nickname;
274 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
275 messages.reverse().forEach((message) => {
276 const msg: TranscriptMessage = {
277 id: message.id,
278 author: {
279 username: message.author.username,
280 discriminator: parseInt(message.author.discriminator),
281 id: message.author.id,
282 topRole: {
283 color: message.member!.roles.highest.color
284 },
285 iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}),
286 bot: message.author.bot
287 },
288 createdTimestamp: message.createdTimestamp
289 };
290 if(message.member?.nickname) msg.author.nickname = message.member.nickname;
291 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
292 if (message.content) msg.content = message.content;
293 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
294 const obj: TranscriptEmbed = {};
295 if (embed.title) obj.title = embed.title;
296 if (embed.description) obj.description = embed.description;
297 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
298 return {
299 name: field.name,
300 value: field.value,
301 inline: field.inline ?? false
302 }
303 });
304 if (embed.color) obj.color = embed.color;
305 if (embed.timestamp) obj.timestamp = embed.timestamp
306 if (embed.footer) obj.footer = {
307 text: embed.footer.text,
308 };
309 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
310 if (embed.author) obj.author = {
311 name: embed.author.name
312 };
313 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
314 if (embed.author?.url) obj.author!.url = embed.author.url;
315 return obj;
316 });
317 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
318 const obj: TranscriptComponent = {
319 type: child.type
320 }
321 if (child.type === ComponentType.Button) {
322 obj.style = child.style;
323 obj.label = child.label ?? "";
324 } else if (child.type > 2) {
325 obj.placeholder = child.placeholder ?? "";
326 }
327 return obj
328 }));
329 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
330 msg.flags = message.flags.toArray();
331
332 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
333 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
334 newOut.messages.push(msg);
335 });
336 return newOut;
337 }
338
339 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
340 let out = "";
341 for (const message of transcript.messages) {
342 if (message.referencedMessage) {
343 if (Array.isArray(message.referencedMessage)) {
344 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
345 }
346 else out += `> [Reply To] ${message.referencedMessage}\n`;
347 }
348 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id})`;
349 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
350 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
351 out += "\n";
352 if (message.content) out += `[Content]\n${message.content}\n\n`;
353 if (message.embeds) {
354 for (const embed of message.embeds) {
355 out += `[Embed]\n`;
356 if (embed.title) out += `| Title: ${embed.title}\n`;
357 if (embed.description) out += `| Description: ${embed.description}\n`;
358 if (embed.fields) {
359 for (const field of embed.fields) {
360 out += `| Field: ${field.name} - ${field.value}\n`;
361 }
362 }
363 if (embed.footer) {
364 out += `|Footer: ${embed.footer.text}\n`;
365 }
366 out += "\n";
367 }
368 }
369 if (message.components) {
370 for (const component of message.components) {
371 out += `[Component]\n`;
372 for (const button of component) {
373 out += `| Button: ${button.label ?? button.description}\n`;
374 }
375 out += "\n";
376 }
377 }
378 if (message.attachments) {
379 for (const attachment of message.attachments) {
380 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
381 }
382 }
383 out += "\n\n"
384 }
385 return out
386 }
387}
388
pineafan4edb7762022-06-26 19:21:04 +0100389export class History {
390 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100391
pineafan3a02ea32022-08-11 21:35:04 +0100392 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100393 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100394 }
395
Skyler Grey75ea9172022-08-06 10:22:23 +0100396 async create(
397 type: string,
398 guild: string,
399 user: Discord.User,
400 moderator: Discord.User | null,
401 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100402 before?: string | null,
403 after?: string | null,
404 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100405 ) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500406 // console.log("History create");
pineafan4edb7762022-06-26 19:21:04 +0100407 await this.histories.insertOne({
408 type: type,
409 guild: guild,
410 user: user.id,
pineafan3a02ea32022-08-11 21:35:04 +0100411 moderator: moderator ? moderator.id : null,
pineafan4edb7762022-06-26 19:21:04 +0100412 reason: reason,
413 occurredAt: new Date(),
pineafan3a02ea32022-08-11 21:35:04 +0100414 before: before ?? null,
415 after: after ?? null,
416 amount: amount ?? null
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500417 }, collectionOptions);
pineafan4edb7762022-06-26 19:21:04 +0100418 }
419
420 async read(guild: string, user: string, year: number) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500421 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100422 const entry = (await this.histories
423 .find({
424 guild: guild,
425 user: user,
426 occurredAt: {
427 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
428 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
429 }
430 })
431 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100432 return entry;
433 }
pineafane23c4ec2022-07-27 21:56:27 +0100434
435 async delete(guild: string) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500436 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100437 await this.histories.deleteMany({ guild: guild });
438 }
pineafan4edb7762022-06-26 19:21:04 +0100439}
440
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500441interface ScanCacheSchema {
442 addedAt: Date;
443 hash: string;
444 data: boolean;
445 tags: string[];
446}
447
448export class ScanCache {
449 scanCache: Collection<ScanCacheSchema>;
450
451 constructor() {
452 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
453 }
454
455 async read(hash: string) {
456 // console.log("ScanCache read");
457 return await this.scanCache.findOne({ hash: hash });
458 }
459
460 async write(hash: string, data: boolean, tags?: string[]) {
461 // console.log("ScanCache write");
462 await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions);
463 }
464
465 async cleanup() {
466 // console.log("ScanCache cleanup");
467 await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
468 }
469}
470
PineaFan538d3752023-01-12 21:48:23 +0000471export class PerformanceTest {
472 performanceData: Collection<PerformanceDataSchema>;
473
474 constructor() {
475 this.performanceData = database.collection<PerformanceDataSchema>("performance");
476 }
477
478 async record(data: PerformanceDataSchema) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500479 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000480 data.timestamp = new Date();
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500481 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000482 }
483 async read() {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500484 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000485 return await this.performanceData.find({}).toArray();
486 }
487}
488
489export interface PerformanceDataSchema {
490 timestamp?: Date;
491 discord: number;
492 databaseRead: number;
493 resources: {
494 cpu: number;
495 memory: number;
496 temperature: number;
497 }
498}
499
pineafan4edb7762022-06-26 19:21:04 +0100500export class ModNotes {
501 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100502
pineafan3a02ea32022-08-11 21:35:04 +0100503 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100504 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100505 }
506
507 async create(guild: string, user: string, note: string | null) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500508 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100509 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100510 }
511
512 async read(guild: string, user: string) {
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500513 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100514 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100515 return entry?.note ?? null;
516 }
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500517
518 async delete(guild: string) {
519 // console.log("ModNotes delete");
520 await this.modNotes.deleteMany({ guild: guild });
521 }
pineafan4edb7762022-06-26 19:21:04 +0100522}
523
pineafan73a7c4a2022-07-24 10:38:04 +0100524export class Premium {
525 premium: Collection<PremiumSchema>;
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500526 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
527 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100528
pineafan3a02ea32022-08-11 21:35:04 +0100529 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100530 this.premium = database.collection<PremiumSchema>("premium");
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500531 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100532 }
533
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500534 async updateUser(user: string, level: number) {
535 // console.log("Premium updateUser");
536 if(!(await this.userExists(user))) await this.createUser(user, level);
537 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
538 }
539
540 async userExists(user: string): Promise<boolean> {
541 // console.log("Premium userExists");
542 const entry = await this.premium.findOne({ user: user });
543 return entry ? true : false;
544 }
545
546 async createUser(user: string, level: number) {
547 // console.log("Premium createUser");
548 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
549 }
550
551 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
552 // console.log("Premium hasPremium");
553 // [Has premium, user giving premium, level, is mod: if given automatically]
554 const cached = this.cache.get(guild);
555 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
556 const entries = await this.premium.find({}).toArray();
557 const members = (await client.guilds.fetch(guild)).members.cache
558 for(const {user} of entries) {
559 const member = members.get(user);
560 if(member) { //TODO: Notify user if they've given premium to a server that has since gotten premium via a mod.
561 const modPerms = //TODO: Create list in config for perms
562 member.permissions.has("Administrator") ||
563 member.permissions.has("ManageChannels") ||
564 member.permissions.has("ManageRoles") ||
565 member.permissions.has("ManageEmojisAndStickers") ||
566 member.permissions.has("ManageWebhooks") ||
567 member.permissions.has("ManageGuild") ||
568 member.permissions.has("KickMembers") ||
569 member.permissions.has("BanMembers") ||
570 member.permissions.has("ManageEvents") ||
571 member.permissions.has("ManageMessages") ||
572 member.permissions.has("ManageThreads")
573 const entry = entries.find(e => e.user === member.id);
574 if(entry && (entry.level === 3) && modPerms) {
575 this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
576 return [true, member.id, entry.level, true];
577 }
578 }
579 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100580 const entry = await this.premium.findOne({
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500581 appliesTo: {
582 $elemMatch: {
583 $eq: guild
584 }
585 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100586 });
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500587 this.cache.set(guild, [entry ? true : false, entry?.user ?? "", entry?.level ?? 0, false, new Date(Date.now() + this.cacheTimeout)]);
588 return entry ? [true, entry.user, entry.level, false] : null;
589 }
590
591 async fetchUser(user: string): Promise<PremiumSchema | null> {
592 // console.log("Premium fetchUser");
593 const entry = await this.premium.findOne({ user: user });
594 if (!entry) return null;
595 return entry;
596 }
597
598 async checkAllPremium(member?: GuildMember) {
599 // console.log("Premium checkAllPremium");
600 const entries = await this.premium.find({}).toArray();
601 if(member) {
602 const entry = entries.find(e => e.user === member.id);
603 if(entry) {
604 const expiresAt = entry.expiresAt;
605 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
606 }
607 const roles = member.roles;
608 let level = 0;
609 if (roles.cache.has("1066468879309750313")) {
610 level = 99;
611 } else if (roles.cache.has("1066465491713003520")) {
612 level = 1;
613 } else if (roles.cache.has("1066439526496604194")) {
614 level = 2;
615 } else if (roles.cache.has("1066464134322978912")) {
616 level = 3;
617 }
618 await this.updateUser(member.id, level);
619 if (level > 0) {
620 await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
621 } else {
622 await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
623 }
624 } else {
625 const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
626 for(const {roles, id} of members.values()) {
627 const entry = entries.find(e => e.user === id);
628 if(entry) {
629 const expiresAt = entry.expiresAt;
630 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
631 }
632 let level: number = 0;
633 if (roles.cache.has("1066468879309750313")) {
634 level = 99;
635 } else if (roles.cache.has("1066465491713003520")) {
636 level = 1;
637 } else if (roles.cache.has("1066439526496604194")) {
638 level = 2;
639 } else if (roles.cache.has("1066464134322978912")) {
640 level = 3;
641 }
642 await this.updateUser(id, level);
643 if (level > 0) {
644 await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
645 } else {
646 await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
647 }
648 }
649 }
650 }
651
652 async addPremium(user: string, guild: string) {
653 // console.log("Premium addPremium");
654 const { level } = (await this.fetchUser(user))!;
655 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
656 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
657 }
658
659 removePremium(user: string, guild: string) {
660 // console.log("Premium removePremium");
661 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
662 return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
pineafan4edb7762022-06-26 19:21:04 +0100663 }
664}
665
pineafan6fb3e072022-05-20 19:27:23 +0100666export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100667 id: string;
668 version: number;
PineaFan100df682023-01-02 13:26:08 +0000669 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100670 filters: {
671 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100672 NSFW: boolean;
673 size: boolean;
674 };
675 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100676 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100677 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100678 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100679 strict: string[];
680 loose: string[];
681 };
pineafan6fb3e072022-05-20 19:27:23 +0100682 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100683 users: string[];
684 roles: string[];
685 channels: string[];
686 };
687 };
pineafan6fb3e072022-05-20 19:27:23 +0100688 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100689 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000690 allowed: {
691 channels: string[];
692 roles: string[];
693 users: string[];
694 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100695 };
pineafan6fb3e072022-05-20 19:27:23 +0100696 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100697 mass: number;
698 everyone: boolean;
699 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100700 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100701 roles: string[];
702 rolesToMention: string[];
703 users: string[];
704 channels: string[];
705 };
706 };
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500707 clean: {
708 channels: string[];
709 allowed: {
710 users: string[];
711 roles: string[];
712 }
713 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100714 };
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500715 autoPublish: {
716 enabled: boolean;
717 channels: string[];
718 }
pineafan6fb3e072022-05-20 19:27:23 +0100719 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100720 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100721 role: string | null;
722 ping: string | null;
723 channel: string | null;
724 message: string | null;
725 };
726 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100727 logging: {
728 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100729 enabled: boolean;
730 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100731 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100732 };
pineafan6fb3e072022-05-20 19:27:23 +0100733 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100734 channel: string | null;
735 };
pineafan73a7c4a2022-07-24 10:38:04 +0100736 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100737 channel: string | null;
738 saved: Record<string, string>;
739 };
740 };
pineafan6fb3e072022-05-20 19:27:23 +0100741 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000742 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100743 role: string | null;
744 };
pineafan6fb3e072022-05-20 19:27:23 +0100745 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100746 enabled: boolean;
747 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100748 types: string;
749 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100750 useCustom: boolean;
751 supportRole: string | null;
752 maxTickets: number;
753 };
pineafan6fb3e072022-05-20 19:27:23 +0100754 moderation: {
755 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100756 timeout: boolean;
757 role: string | null;
758 text: string | null;
759 link: string | null;
760 };
pineafan6fb3e072022-05-20 19:27:23 +0100761 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100762 text: string | null;
763 link: string | null;
764 };
pineafan6fb3e072022-05-20 19:27:23 +0100765 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100766 text: string | null;
767 link: string | null;
768 };
pineafan6fb3e072022-05-20 19:27:23 +0100769 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100770 text: string | null;
771 link: string | null;
772 };
pineafan6fb3e072022-05-20 19:27:23 +0100773 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100774 text: string | null;
775 link: string | null;
776 };
pineafan6fb3e072022-05-20 19:27:23 +0100777 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100778 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500779 text: null;
780 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100781 };
PineaFane6ba7882023-01-18 20:41:16 +0000782 nick: {
783 text: string | null;
784 link: string | null;
785 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100786 };
pineafan6fb3e072022-05-20 19:27:23 +0100787 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100788 name: string;
789 retainPrevious: boolean;
790 nullable: boolean;
791 track: string[];
792 manageableBy: string[];
793 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100794 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100795 enabled: boolean;
796 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100797 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100798 name: string;
799 description: string;
800 min: number;
801 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100802 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100803 name: string;
804 description: string | null;
805 role: string;
806 }[];
807 }[];
808 };
809 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100810}
pineafan4edb7762022-06-26 19:21:04 +0100811
812export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100813 type: string;
814 guild: string;
815 user: string;
816 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100817 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100818 occurredAt: Date;
819 before: string | null;
820 after: string | null;
821 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100822}
823
824export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100825 guild: string;
826 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100827 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100828}
829
pineafan73a7c4a2022-07-24 10:38:04 +0100830export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100831 user: string;
832 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100833 appliesTo: string[];
Samuel Shuert27bf3cd2023-03-03 15:51:25 -0500834 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100835}