blob: 0d21979a2d65ca0b186e74de658d1dabff4014d8 [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";
TheCodedProff8ef7942023-03-03 15:32:32 -05007import _ from "lodash";
8import defaultData from '../config/default.js';
TheCodedProffaae5332023-03-01 18:16:05 -05009// 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();
TheCodedProf8d577fa2023-03-01 13:06:40 -050021const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010022
TheCodedProffaae5332023-03-01 18:16:05 -050023const collectionOptions = { authdb: "admin" };
TheCodedProf75c51be2023-03-03 17:18:18 -050024const getIV = () => crypto.randomBytes(16);
TheCodedProffaae5332023-03-01 18:16:05 -050025
pineafan4edb7762022-06-26 19:21:04 +010026export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010027 guilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050028 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010029
30 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010031 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050032 this.defaultData = defaultData;
pineafan63fc5e22022-08-04 22:04:10 +010033 }
34
Skyler Greyad002172022-08-16 18:48:26 +010035 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050036 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010037 const entry = await this.guilds.findOne({ id: guild });
TheCodedProff8ef7942023-03-03 15:32:32 -050038 const data = _.clone(this.defaultData!);
39 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010040 }
41
Skyler Grey11236ba2022-08-08 21:13:33 +010042 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050043 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010044 // eslint-disable-next-line @typescript-eslint/no-explicit-any
45 const uo: Record<string, any> = {};
46 if (!Array.isArray(unset)) unset = [unset];
47 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010048 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010049 }
Skyler Grey75ea9172022-08-06 10:22:23 +010050 const out = { $set: {}, $unset: {} };
51 if (set) out.$set = set;
52 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010053 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010054 }
55
pineafan63fc5e22022-08-04 22:04:10 +010056 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010057 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050058 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010059 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010060 await this.guilds.updateOne(
61 { id: guild },
62 {
63 $addToSet: { [key]: { $each: value } }
64 },
65 { upsert: true }
66 );
pineafan6702cef2022-06-13 17:52:37 +010067 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010068 await this.guilds.updateOne(
69 { id: guild },
70 {
71 $addToSet: { [key]: value }
72 },
73 { upsert: true }
74 );
pineafan6702cef2022-06-13 17:52:37 +010075 }
76 }
77
Skyler Grey75ea9172022-08-06 10:22:23 +010078 async remove(
79 guild: string,
80 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010081 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010082 value: any,
83 innerKey?: string | null
84 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -050085 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +010086 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010087 await this.guilds.updateOne(
88 { id: guild },
89 {
90 $pull: { [key]: { [innerKey]: { $eq: value } } }
91 },
92 { upsert: true }
93 );
pineafan0bc04162022-07-25 17:22:26 +010094 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010095 await this.guilds.updateOne(
96 { id: guild },
97 {
98 $pullAll: { [key]: value }
99 },
100 { upsert: true }
101 );
pineafan6702cef2022-06-13 17:52:37 +0100102 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100103 await this.guilds.updateOne(
104 { id: guild },
105 {
106 $pullAll: { [key]: [value] }
107 },
108 { upsert: true }
109 );
pineafan6702cef2022-06-13 17:52:37 +0100110 }
pineafan6fb3e072022-05-20 19:27:23 +0100111 }
pineafane23c4ec2022-07-27 21:56:27 +0100112
113 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500114 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100115 await this.guilds.deleteOne({ id: guild });
116 }
pineafan6fb3e072022-05-20 19:27:23 +0100117}
118
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500119interface TranscriptEmbed {
120 title?: string;
121 description?: string;
122 fields?: {
123 name: string;
124 value: string;
125 inline: boolean;
126 }[];
127 footer?: {
128 text: string;
129 iconURL?: string;
130 };
TheCodedProffaae5332023-03-01 18:16:05 -0500131 color?: number;
132 timestamp?: string;
133 author?: {
134 name: string;
135 iconURL?: string;
136 url?: string;
137 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500138}
139
140interface TranscriptComponent {
141 type: number;
142 style?: ButtonStyle;
143 label?: string;
144 description?: string;
145 placeholder?: string;
146 emojiURL?: string;
147}
148
149interface TranscriptAuthor {
150 username: string;
151 discriminator: number;
152 nickname?: string;
153 id: string;
154 iconURL?: string;
155 topRole: {
156 color: number;
157 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500158 };
159 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500160}
161
162interface TranscriptAttachment {
163 url: string;
164 filename: string;
165 size: number;
166 log?: string;
167}
168
169interface TranscriptMessage {
170 id: string;
171 author: TranscriptAuthor;
172 content?: string;
173 embeds?: TranscriptEmbed[];
174 components?: TranscriptComponent[][];
175 editedTimestamp?: number;
176 createdTimestamp: number;
177 flags?: string[];
178 attachments?: TranscriptAttachment[];
179 stickerURLs?: string[];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500180 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500181}
182
183interface TranscriptSchema {
184 code: string;
185 for: TranscriptAuthor;
186 type: "ticket" | "purge"
187 guild: string;
188 channel: string;
189 messages: TranscriptMessage[];
190 createdTimestamp: number;
191 createdBy: TranscriptAuthor;
192}
193
194export class Transcript {
195 transcripts: Collection<TranscriptSchema>;
196
197 constructor() {
198 this.transcripts = database.collection<TranscriptSchema>("transcripts");
199 }
200
201 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500202 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500203 let code;
204 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500205 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500206 } while (await this.transcripts.findOne({ code: code }));
TheCodedProf75c51be2023-03-03 17:18:18 -0500207 const key = crypto.randomBytes(32**2).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-").substring(0, 32);
208 const iv = getIV().toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
209 for(const message of transcript.messages) {
210 if(message.content) {
211 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
212 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
213 }
214 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500215
TheCodedProffaae5332023-03-01 18:16:05 -0500216 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
TheCodedProf75c51be2023-03-03 17:18:18 -0500217 if(doc.acknowledged) return [code, key, iv];
218 else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500219 }
220
TheCodedProf75c51be2023-03-03 17:18:18 -0500221 async read(code: string, key: string, iv: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500222 // console.log("Transcript read")
TheCodedProf75c51be2023-03-03 17:18:18 -0500223 const doc = await this.transcripts.findOne({ code: code });
224 if(!doc) return null;
225 for(const message of doc.messages) {
226 if(message.content) {
227 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
228 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
229 }
230 }
231 return doc;
232 }
233
234 async deleteAll(guild: string) {
235 // console.log("Transcript delete")
236 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
237 for (const doc of filteredDocs) {
238 await this.transcripts.deleteOne({ code: doc.code });
239 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500240 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500241
242 async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
243 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
244 const newOut: Omit<TranscriptSchema, "code"> = {
245 type: "ticket",
246 for: {
247 username: member!.user.username,
248 discriminator: parseInt(member!.user.discriminator),
249 id: member!.user.id,
250 topRole: {
251 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500252 },
253 iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
254 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500255 },
256 guild: interaction.guild!.id,
257 channel: interaction.channel!.id,
258 messages: [],
259 createdTimestamp: Date.now(),
260 createdBy: {
261 username: interaction.user.username,
262 discriminator: parseInt(interaction.user.discriminator),
263 id: interaction.user.id,
264 topRole: {
265 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500266 },
267 iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
268 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500269 }
270 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500271 if(member.nickname) newOut.for.nickname = member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500272 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
273 messages.reverse().forEach((message) => {
274 const msg: TranscriptMessage = {
275 id: message.id,
276 author: {
277 username: message.author.username,
278 discriminator: parseInt(message.author.discriminator),
279 id: message.author.id,
280 topRole: {
281 color: message.member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500282 },
TheCodedProffaae5332023-03-01 18:16:05 -0500283 iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}),
TheCodedProf088b1b22023-02-28 17:31:11 -0500284 bot: message.author.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500285 },
286 createdTimestamp: message.createdTimestamp
287 };
TheCodedProf088b1b22023-02-28 17:31:11 -0500288 if(message.member?.nickname) msg.author.nickname = message.member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500289 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
290 if (message.content) msg.content = message.content;
291 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
292 const obj: TranscriptEmbed = {};
293 if (embed.title) obj.title = embed.title;
294 if (embed.description) obj.description = embed.description;
295 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
296 return {
297 name: field.name,
298 value: field.value,
299 inline: field.inline ?? false
300 }
301 });
TheCodedProffaae5332023-03-01 18:16:05 -0500302 if (embed.color) obj.color = embed.color;
303 if (embed.timestamp) obj.timestamp = embed.timestamp
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500304 if (embed.footer) obj.footer = {
305 text: embed.footer.text,
306 };
307 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
TheCodedProffaae5332023-03-01 18:16:05 -0500308 if (embed.author) obj.author = {
309 name: embed.author.name
310 };
311 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
312 if (embed.author?.url) obj.author!.url = embed.author.url;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500313 return obj;
314 });
315 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
316 const obj: TranscriptComponent = {
317 type: child.type
318 }
319 if (child.type === ComponentType.Button) {
320 obj.style = child.style;
321 obj.label = child.label ?? "";
322 } else if (child.type > 2) {
323 obj.placeholder = child.placeholder ?? "";
324 }
325 return obj
326 }));
327 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
328 msg.flags = message.flags.toArray();
329
330 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
331 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
332 newOut.messages.push(msg);
333 });
334 return newOut;
335 }
336
337 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
338 let out = "";
339 for (const message of transcript.messages) {
340 if (message.referencedMessage) {
341 if (Array.isArray(message.referencedMessage)) {
342 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
343 }
344 else out += `> [Reply To] ${message.referencedMessage}\n`;
345 }
TheCodedProff8ef7942023-03-03 15:32:32 -0500346 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id})`;
347 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
348 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500349 out += "\n";
350 if (message.content) out += `[Content]\n${message.content}\n\n`;
351 if (message.embeds) {
352 for (const embed of message.embeds) {
353 out += `[Embed]\n`;
354 if (embed.title) out += `| Title: ${embed.title}\n`;
355 if (embed.description) out += `| Description: ${embed.description}\n`;
356 if (embed.fields) {
357 for (const field of embed.fields) {
358 out += `| Field: ${field.name} - ${field.value}\n`;
359 }
360 }
361 if (embed.footer) {
362 out += `|Footer: ${embed.footer.text}\n`;
363 }
364 out += "\n";
365 }
366 }
367 if (message.components) {
368 for (const component of message.components) {
369 out += `[Component]\n`;
370 for (const button of component) {
371 out += `| Button: ${button.label ?? button.description}\n`;
372 }
373 out += "\n";
374 }
375 }
376 if (message.attachments) {
377 for (const attachment of message.attachments) {
378 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
379 }
380 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500381 out += "\n\n"
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500382 }
383 return out
384 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500385}
386
pineafan4edb7762022-06-26 19:21:04 +0100387export class History {
388 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100389
pineafan3a02ea32022-08-11 21:35:04 +0100390 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100391 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100392 }
393
Skyler Grey75ea9172022-08-06 10:22:23 +0100394 async create(
395 type: string,
396 guild: string,
397 user: Discord.User,
398 moderator: Discord.User | null,
399 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100400 before?: string | null,
401 after?: string | null,
402 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100403 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500404 // console.log("History create");
pineafan4edb7762022-06-26 19:21:04 +0100405 await this.histories.insertOne({
406 type: type,
407 guild: guild,
408 user: user.id,
pineafan3a02ea32022-08-11 21:35:04 +0100409 moderator: moderator ? moderator.id : null,
pineafan4edb7762022-06-26 19:21:04 +0100410 reason: reason,
411 occurredAt: new Date(),
pineafan3a02ea32022-08-11 21:35:04 +0100412 before: before ?? null,
413 after: after ?? null,
414 amount: amount ?? null
TheCodedProffaae5332023-03-01 18:16:05 -0500415 }, collectionOptions);
pineafan4edb7762022-06-26 19:21:04 +0100416 }
417
418 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500419 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100420 const entry = (await this.histories
421 .find({
422 guild: guild,
423 user: user,
424 occurredAt: {
425 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
426 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
427 }
428 })
429 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100430 return entry;
431 }
pineafane23c4ec2022-07-27 21:56:27 +0100432
433 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500434 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100435 await this.histories.deleteMany({ guild: guild });
436 }
pineafan4edb7762022-06-26 19:21:04 +0100437}
438
TheCodedProfb5e9d552023-01-29 15:43:26 -0500439interface ScanCacheSchema {
440 addedAt: Date;
441 hash: string;
442 data: boolean;
443 tags: string[];
444}
445
446export class ScanCache {
447 scanCache: Collection<ScanCacheSchema>;
448
449 constructor() {
450 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
451 }
452
453 async read(hash: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500454 // console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500455 return await this.scanCache.findOne({ hash: hash });
456 }
457
458 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500459 // console.log("ScanCache write");
TheCodedProffaae5332023-03-01 18:16:05 -0500460 await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500461 }
462
463 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500464 // console.log("ScanCache cleanup");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500465 await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
466 }
467}
468
PineaFan538d3752023-01-12 21:48:23 +0000469export class PerformanceTest {
470 performanceData: Collection<PerformanceDataSchema>;
471
472 constructor() {
473 this.performanceData = database.collection<PerformanceDataSchema>("performance");
474 }
475
476 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500477 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000478 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500479 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000480 }
481 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500482 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000483 return await this.performanceData.find({}).toArray();
484 }
485}
486
487export interface PerformanceDataSchema {
488 timestamp?: Date;
489 discord: number;
490 databaseRead: number;
491 resources: {
492 cpu: number;
493 memory: number;
494 temperature: number;
495 }
496}
497
pineafan4edb7762022-06-26 19:21:04 +0100498export class ModNotes {
499 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100500
pineafan3a02ea32022-08-11 21:35:04 +0100501 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100502 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100503 }
504
505 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500506 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100507 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100508 }
509
510 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500511 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100512 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100513 return entry?.note ?? null;
514 }
TheCodedProf267563a2023-01-21 17:00:57 -0500515
516 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500517 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500518 await this.modNotes.deleteMany({ guild: guild });
519 }
pineafan4edb7762022-06-26 19:21:04 +0100520}
521
pineafan73a7c4a2022-07-24 10:38:04 +0100522export class Premium {
523 premium: Collection<PremiumSchema>;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500524 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
525 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100526
pineafan3a02ea32022-08-11 21:35:04 +0100527 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100528 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500529 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100530 }
531
TheCodedProf633866f2023-02-03 17:06:00 -0500532 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500533 // console.log("Premium updateUser");
TheCodedProf633866f2023-02-03 17:06:00 -0500534 if(!(await this.userExists(user))) await this.createUser(user, level);
535 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
536 }
537
538 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500539 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500540 const entry = await this.premium.findOne({ user: user });
541 return entry ? true : false;
542 }
543
544 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500545 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500546 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500547 }
548
TheCodedProfaa3fe992023-02-25 21:53:09 -0500549 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500550 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500551 // [Has premium, user giving premium, level, is mod: if given automatically]
552 const cached = this.cache.get(guild);
553 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500554 const entries = await this.premium.find({}).toArray();
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500555 const members = (await client.guilds.fetch(guild)).members.cache
TheCodedProf94ff6de2023-02-22 17:47:26 -0500556 for(const {user} of entries) {
557 const member = members.get(user);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500558 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 -0500559 const modPerms = //TODO: Create list in config for perms
560 member.permissions.has("Administrator") ||
561 member.permissions.has("ManageChannels") ||
562 member.permissions.has("ManageRoles") ||
563 member.permissions.has("ManageEmojisAndStickers") ||
564 member.permissions.has("ManageWebhooks") ||
565 member.permissions.has("ManageGuild") ||
566 member.permissions.has("KickMembers") ||
567 member.permissions.has("BanMembers") ||
568 member.permissions.has("ManageEvents") ||
569 member.permissions.has("ManageMessages") ||
570 member.permissions.has("ManageThreads")
571 const entry = entries.find(e => e.user === member.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500572 if(entry && (entry.level === 3) && modPerms) {
573 this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
574 return [true, member.id, entry.level, true];
575 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500576 }
577 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100578 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500579 appliesTo: {
580 $elemMatch: {
581 $eq: guild
582 }
583 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100584 });
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500585 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 -0500586 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500587 }
588
TheCodedProf633866f2023-02-03 17:06:00 -0500589 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500590 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500591 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500592 if (!entry) return null;
593 return entry;
594 }
595
TheCodedProf94ff6de2023-02-22 17:47:26 -0500596 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500597 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500598 const entries = await this.premium.find({}).toArray();
TheCodedProf94ff6de2023-02-22 17:47:26 -0500599 if(member) {
600 const entry = entries.find(e => e.user === member.id);
601 if(entry) {
602 const expiresAt = entry.expiresAt;
603 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
604 }
605 const roles = member.roles;
606 let level = 0;
607 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500608 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500609 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500610 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500611 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500612 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500613 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500614 level = 3;
615 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500616 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500617 if (level > 0) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500618 await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
TheCodedProf633866f2023-02-03 17:06:00 -0500619 } else {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500620 await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
621 }
622 } else {
623 const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
624 for(const {roles, id} of members.values()) {
625 const entry = entries.find(e => e.user === id);
626 if(entry) {
627 const expiresAt = entry.expiresAt;
628 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
629 }
630 let level: number = 0;
631 if (roles.cache.has("1066468879309750313")) {
632 level = 99;
633 } else if (roles.cache.has("1066465491713003520")) {
634 level = 1;
635 } else if (roles.cache.has("1066439526496604194")) {
636 level = 2;
637 } else if (roles.cache.has("1066464134322978912")) {
638 level = 3;
639 }
640 await this.updateUser(id, level);
641 if (level > 0) {
642 await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
643 } else {
644 await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
645 }
TheCodedProf633866f2023-02-03 17:06:00 -0500646 }
647 }
TheCodedProf267563a2023-01-21 17:00:57 -0500648 }
649
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500650 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500651 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500652 const { level } = (await this.fetchUser(user))!;
653 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500654 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100655 }
TheCodedProffc420b72023-01-24 17:14:38 -0500656
657 removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500658 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500659 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProffc420b72023-01-24 17:14:38 -0500660 return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
661 }
pineafan4edb7762022-06-26 19:21:04 +0100662}
663
pineafan6fb3e072022-05-20 19:27:23 +0100664export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100665 id: string;
666 version: number;
PineaFan100df682023-01-02 13:26:08 +0000667 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100668 filters: {
669 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100670 NSFW: boolean;
671 size: boolean;
672 };
673 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100674 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100675 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100676 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100677 strict: string[];
678 loose: string[];
679 };
pineafan6fb3e072022-05-20 19:27:23 +0100680 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100681 users: string[];
682 roles: string[];
683 channels: string[];
684 };
685 };
pineafan6fb3e072022-05-20 19:27:23 +0100686 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100687 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000688 allowed: {
689 channels: string[];
690 roles: string[];
691 users: string[];
692 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100693 };
pineafan6fb3e072022-05-20 19:27:23 +0100694 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100695 mass: number;
696 everyone: boolean;
697 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100698 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100699 roles: string[];
700 rolesToMention: string[];
701 users: string[];
702 channels: string[];
703 };
704 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500705 clean: {
706 channels: string[];
707 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500708 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500709 roles: string[];
710 }
711 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100712 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500713 autoPublish: {
714 enabled: boolean;
715 channels: string[];
716 }
pineafan6fb3e072022-05-20 19:27:23 +0100717 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100718 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100719 role: string | null;
720 ping: string | null;
721 channel: string | null;
722 message: string | null;
723 };
724 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100725 logging: {
726 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100727 enabled: boolean;
728 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100729 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100730 };
pineafan6fb3e072022-05-20 19:27:23 +0100731 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100732 channel: string | null;
733 };
pineafan73a7c4a2022-07-24 10:38:04 +0100734 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100735 channel: string | null;
736 saved: Record<string, string>;
737 };
738 };
pineafan6fb3e072022-05-20 19:27:23 +0100739 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000740 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100741 role: string | null;
742 };
pineafan6fb3e072022-05-20 19:27:23 +0100743 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100744 enabled: boolean;
745 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100746 types: string;
747 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100748 useCustom: boolean;
749 supportRole: string | null;
750 maxTickets: number;
751 };
pineafan6fb3e072022-05-20 19:27:23 +0100752 moderation: {
753 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100754 timeout: boolean;
755 role: string | null;
756 text: string | null;
757 link: string | null;
758 };
pineafan6fb3e072022-05-20 19:27:23 +0100759 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100760 text: string | null;
761 link: string | null;
762 };
pineafan6fb3e072022-05-20 19:27:23 +0100763 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100764 text: string | null;
765 link: string | null;
766 };
pineafan6fb3e072022-05-20 19:27:23 +0100767 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100768 text: string | null;
769 link: string | null;
770 };
pineafan6fb3e072022-05-20 19:27:23 +0100771 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100772 text: string | null;
773 link: string | null;
774 };
pineafan6fb3e072022-05-20 19:27:23 +0100775 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100776 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500777 text: null;
778 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100779 };
PineaFane6ba7882023-01-18 20:41:16 +0000780 nick: {
781 text: string | null;
782 link: string | null;
783 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100784 };
pineafan6fb3e072022-05-20 19:27:23 +0100785 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100786 name: string;
787 retainPrevious: boolean;
788 nullable: boolean;
789 track: string[];
790 manageableBy: string[];
791 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100792 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100793 enabled: boolean;
794 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100795 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100796 name: string;
797 description: string;
798 min: number;
799 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100800 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100801 name: string;
802 description: string | null;
803 role: string;
804 }[];
805 }[];
806 };
807 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100808}
pineafan4edb7762022-06-26 19:21:04 +0100809
810export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100811 type: string;
812 guild: string;
813 user: string;
814 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100815 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100816 occurredAt: Date;
817 before: string | null;
818 after: string | null;
819 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100820}
821
822export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100823 guild: string;
824 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100825 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100826}
827
pineafan73a7c4a2022-07-24 10:38:04 +0100828export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100829 user: string;
830 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100831 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -0500832 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100833}