blob: 45c7661fb7fea62cfc877df0431f92ae31748bd2 [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';
TheCodedProf75276572023-03-04 13:49:16 -05009
TheCodedProffaae5332023-03-01 18:16:05 -050010const username = encodeURIComponent(config.mongoOptions.username);
11const password = encodeURIComponent(config.mongoOptions.password);
TheCodedProf78b90332023-03-04 14:02:21 -050012const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT` : `mongodb://${config.mongoOptions.host}`, {authSource: config.mongoOptions.authSource});
pineafan63fc5e22022-08-04 22:04:10 +010013await mongoClient.connect();
TheCodedProf8d577fa2023-03-01 13:06:40 -050014const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010015
TheCodedProf78b90332023-03-04 14:02:21 -050016const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" };
TheCodedProf75c51be2023-03-03 17:18:18 -050017const getIV = () => crypto.randomBytes(16);
TheCodedProffaae5332023-03-01 18:16:05 -050018
pineafan4edb7762022-06-26 19:21:04 +010019export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010020 guilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050021 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010022
23 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010024 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050025 this.defaultData = defaultData;
pineafan63fc5e22022-08-04 22:04:10 +010026 }
27
Skyler Greyad002172022-08-16 18:48:26 +010028 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050029 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010030 const entry = await this.guilds.findOne({ id: guild });
TheCodedProff8ef7942023-03-03 15:32:32 -050031 const data = _.clone(this.defaultData!);
32 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010033 }
34
Skyler Grey11236ba2022-08-08 21:13:33 +010035 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050036 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010037 // eslint-disable-next-line @typescript-eslint/no-explicit-any
38 const uo: Record<string, any> = {};
39 if (!Array.isArray(unset)) unset = [unset];
40 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010041 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010042 }
Skyler Grey75ea9172022-08-06 10:22:23 +010043 const out = { $set: {}, $unset: {} };
44 if (set) out.$set = set;
45 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010046 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010047 }
48
pineafan63fc5e22022-08-04 22:04:10 +010049 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010050 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050051 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010052 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010053 await this.guilds.updateOne(
54 { id: guild },
55 {
56 $addToSet: { [key]: { $each: value } }
57 },
58 { upsert: true }
59 );
pineafan6702cef2022-06-13 17:52:37 +010060 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010061 await this.guilds.updateOne(
62 { id: guild },
63 {
64 $addToSet: { [key]: value }
65 },
66 { upsert: true }
67 );
pineafan6702cef2022-06-13 17:52:37 +010068 }
69 }
70
Skyler Grey75ea9172022-08-06 10:22:23 +010071 async remove(
72 guild: string,
73 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010074 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010075 value: any,
76 innerKey?: string | null
77 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -050078 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +010079 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010080 await this.guilds.updateOne(
81 { id: guild },
82 {
83 $pull: { [key]: { [innerKey]: { $eq: value } } }
84 },
85 { upsert: true }
86 );
pineafan0bc04162022-07-25 17:22:26 +010087 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010088 await this.guilds.updateOne(
89 { id: guild },
90 {
91 $pullAll: { [key]: value }
92 },
93 { upsert: true }
94 );
pineafan6702cef2022-06-13 17:52:37 +010095 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010096 await this.guilds.updateOne(
97 { id: guild },
98 {
99 $pullAll: { [key]: [value] }
100 },
101 { upsert: true }
102 );
pineafan6702cef2022-06-13 17:52:37 +0100103 }
pineafan6fb3e072022-05-20 19:27:23 +0100104 }
pineafane23c4ec2022-07-27 21:56:27 +0100105
106 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500107 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100108 await this.guilds.deleteOne({ id: guild });
109 }
pineafan6fb3e072022-05-20 19:27:23 +0100110}
111
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500112interface TranscriptEmbed {
113 title?: string;
114 description?: string;
115 fields?: {
116 name: string;
117 value: string;
118 inline: boolean;
119 }[];
120 footer?: {
121 text: string;
122 iconURL?: string;
123 };
TheCodedProffaae5332023-03-01 18:16:05 -0500124 color?: number;
125 timestamp?: string;
126 author?: {
127 name: string;
128 iconURL?: string;
129 url?: string;
130 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500131}
132
133interface TranscriptComponent {
134 type: number;
135 style?: ButtonStyle;
136 label?: string;
137 description?: string;
138 placeholder?: string;
139 emojiURL?: string;
140}
141
142interface TranscriptAuthor {
143 username: string;
144 discriminator: number;
145 nickname?: string;
146 id: string;
147 iconURL?: string;
148 topRole: {
149 color: number;
150 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500151 };
152 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500153}
154
155interface TranscriptAttachment {
156 url: string;
157 filename: string;
158 size: number;
159 log?: string;
160}
161
162interface TranscriptMessage {
163 id: string;
164 author: TranscriptAuthor;
165 content?: string;
166 embeds?: TranscriptEmbed[];
167 components?: TranscriptComponent[][];
168 editedTimestamp?: number;
169 createdTimestamp: number;
170 flags?: string[];
171 attachments?: TranscriptAttachment[];
172 stickerURLs?: string[];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500173 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500174}
175
176interface TranscriptSchema {
177 code: string;
178 for: TranscriptAuthor;
179 type: "ticket" | "purge"
180 guild: string;
181 channel: string;
182 messages: TranscriptMessage[];
183 createdTimestamp: number;
184 createdBy: TranscriptAuthor;
185}
186
187export class Transcript {
188 transcripts: Collection<TranscriptSchema>;
189
190 constructor() {
191 this.transcripts = database.collection<TranscriptSchema>("transcripts");
192 }
193
194 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500195 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500196 let code;
197 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500198 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500199 } while (await this.transcripts.findOne({ code: code }));
TheCodedProf75c51be2023-03-03 17:18:18 -0500200 const key = crypto.randomBytes(32**2).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-").substring(0, 32);
201 const iv = getIV().toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
202 for(const message of transcript.messages) {
203 if(message.content) {
204 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
205 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
206 }
207 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500208
TheCodedProffaae5332023-03-01 18:16:05 -0500209 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
TheCodedProf75c51be2023-03-03 17:18:18 -0500210 if(doc.acknowledged) return [code, key, iv];
211 else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500212 }
213
TheCodedProf75c51be2023-03-03 17:18:18 -0500214 async read(code: string, key: string, iv: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500215 // console.log("Transcript read")
TheCodedProf75c51be2023-03-03 17:18:18 -0500216 const doc = await this.transcripts.findOne({ code: code });
217 if(!doc) return null;
218 for(const message of doc.messages) {
219 if(message.content) {
220 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
221 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
222 }
223 }
224 return doc;
225 }
226
227 async deleteAll(guild: string) {
228 // console.log("Transcript delete")
229 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
230 for (const doc of filteredDocs) {
231 await this.transcripts.deleteOne({ code: doc.code });
232 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500233 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500234
235 async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
236 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
237 const newOut: Omit<TranscriptSchema, "code"> = {
238 type: "ticket",
239 for: {
240 username: member!.user.username,
241 discriminator: parseInt(member!.user.discriminator),
242 id: member!.user.id,
243 topRole: {
244 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500245 },
246 iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
247 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500248 },
249 guild: interaction.guild!.id,
250 channel: interaction.channel!.id,
251 messages: [],
252 createdTimestamp: Date.now(),
253 createdBy: {
254 username: interaction.user.username,
255 discriminator: parseInt(interaction.user.discriminator),
256 id: interaction.user.id,
257 topRole: {
258 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500259 },
260 iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
261 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500262 }
263 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500264 if(member.nickname) newOut.for.nickname = member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500265 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
266 messages.reverse().forEach((message) => {
267 const msg: TranscriptMessage = {
268 id: message.id,
269 author: {
270 username: message.author.username,
271 discriminator: parseInt(message.author.discriminator),
272 id: message.author.id,
273 topRole: {
274 color: message.member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500275 },
TheCodedProffaae5332023-03-01 18:16:05 -0500276 iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}),
TheCodedProf088b1b22023-02-28 17:31:11 -0500277 bot: message.author.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500278 },
279 createdTimestamp: message.createdTimestamp
280 };
TheCodedProf088b1b22023-02-28 17:31:11 -0500281 if(message.member?.nickname) msg.author.nickname = message.member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500282 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
283 if (message.content) msg.content = message.content;
284 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
285 const obj: TranscriptEmbed = {};
286 if (embed.title) obj.title = embed.title;
287 if (embed.description) obj.description = embed.description;
288 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
289 return {
290 name: field.name,
291 value: field.value,
292 inline: field.inline ?? false
293 }
294 });
TheCodedProffaae5332023-03-01 18:16:05 -0500295 if (embed.color) obj.color = embed.color;
296 if (embed.timestamp) obj.timestamp = embed.timestamp
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500297 if (embed.footer) obj.footer = {
298 text: embed.footer.text,
299 };
300 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
TheCodedProffaae5332023-03-01 18:16:05 -0500301 if (embed.author) obj.author = {
302 name: embed.author.name
303 };
304 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
305 if (embed.author?.url) obj.author!.url = embed.author.url;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500306 return obj;
307 });
308 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
309 const obj: TranscriptComponent = {
310 type: child.type
311 }
312 if (child.type === ComponentType.Button) {
313 obj.style = child.style;
314 obj.label = child.label ?? "";
315 } else if (child.type > 2) {
316 obj.placeholder = child.placeholder ?? "";
317 }
318 return obj
319 }));
320 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
321 msg.flags = message.flags.toArray();
322
323 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
324 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
325 newOut.messages.push(msg);
326 });
327 return newOut;
328 }
329
330 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
331 let out = "";
332 for (const message of transcript.messages) {
333 if (message.referencedMessage) {
334 if (Array.isArray(message.referencedMessage)) {
335 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
336 }
337 else out += `> [Reply To] ${message.referencedMessage}\n`;
338 }
TheCodedProff8ef7942023-03-03 15:32:32 -0500339 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id})`;
340 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
341 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500342 out += "\n";
343 if (message.content) out += `[Content]\n${message.content}\n\n`;
344 if (message.embeds) {
345 for (const embed of message.embeds) {
346 out += `[Embed]\n`;
347 if (embed.title) out += `| Title: ${embed.title}\n`;
348 if (embed.description) out += `| Description: ${embed.description}\n`;
349 if (embed.fields) {
350 for (const field of embed.fields) {
351 out += `| Field: ${field.name} - ${field.value}\n`;
352 }
353 }
354 if (embed.footer) {
355 out += `|Footer: ${embed.footer.text}\n`;
356 }
357 out += "\n";
358 }
359 }
360 if (message.components) {
361 for (const component of message.components) {
362 out += `[Component]\n`;
363 for (const button of component) {
364 out += `| Button: ${button.label ?? button.description}\n`;
365 }
366 out += "\n";
367 }
368 }
369 if (message.attachments) {
370 for (const attachment of message.attachments) {
371 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
372 }
373 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500374 out += "\n\n"
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500375 }
376 return out
377 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500378}
379
pineafan4edb7762022-06-26 19:21:04 +0100380export class History {
381 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100382
pineafan3a02ea32022-08-11 21:35:04 +0100383 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100384 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100385 }
386
Skyler Grey75ea9172022-08-06 10:22:23 +0100387 async create(
388 type: string,
389 guild: string,
390 user: Discord.User,
391 moderator: Discord.User | null,
392 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100393 before?: string | null,
394 after?: string | null,
395 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100396 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500397 // console.log("History create");
pineafan4edb7762022-06-26 19:21:04 +0100398 await this.histories.insertOne({
399 type: type,
400 guild: guild,
401 user: user.id,
pineafan3a02ea32022-08-11 21:35:04 +0100402 moderator: moderator ? moderator.id : null,
pineafan4edb7762022-06-26 19:21:04 +0100403 reason: reason,
404 occurredAt: new Date(),
pineafan3a02ea32022-08-11 21:35:04 +0100405 before: before ?? null,
406 after: after ?? null,
407 amount: amount ?? null
TheCodedProffaae5332023-03-01 18:16:05 -0500408 }, collectionOptions);
pineafan4edb7762022-06-26 19:21:04 +0100409 }
410
411 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500412 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100413 const entry = (await this.histories
414 .find({
415 guild: guild,
416 user: user,
417 occurredAt: {
418 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
419 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
420 }
421 })
422 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100423 return entry;
424 }
pineafane23c4ec2022-07-27 21:56:27 +0100425
426 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500427 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100428 await this.histories.deleteMany({ guild: guild });
429 }
pineafan4edb7762022-06-26 19:21:04 +0100430}
431
TheCodedProfb5e9d552023-01-29 15:43:26 -0500432interface ScanCacheSchema {
433 addedAt: Date;
434 hash: string;
435 data: boolean;
436 tags: string[];
437}
438
439export class ScanCache {
440 scanCache: Collection<ScanCacheSchema>;
441
442 constructor() {
443 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
444 }
445
446 async read(hash: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500447 // console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500448 return await this.scanCache.findOne({ hash: hash });
449 }
450
451 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500452 // console.log("ScanCache write");
TheCodedProffaae5332023-03-01 18:16:05 -0500453 await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500454 }
455
456 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500457 // console.log("ScanCache cleanup");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500458 await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
459 }
460}
461
PineaFan538d3752023-01-12 21:48:23 +0000462export class PerformanceTest {
463 performanceData: Collection<PerformanceDataSchema>;
464
465 constructor() {
466 this.performanceData = database.collection<PerformanceDataSchema>("performance");
467 }
468
469 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500470 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000471 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500472 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000473 }
474 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500475 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000476 return await this.performanceData.find({}).toArray();
477 }
478}
479
480export interface PerformanceDataSchema {
481 timestamp?: Date;
482 discord: number;
483 databaseRead: number;
484 resources: {
485 cpu: number;
486 memory: number;
487 temperature: number;
488 }
489}
490
pineafan4edb7762022-06-26 19:21:04 +0100491export class ModNotes {
492 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100493
pineafan3a02ea32022-08-11 21:35:04 +0100494 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100495 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100496 }
497
498 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500499 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100500 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100501 }
502
503 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500504 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100505 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100506 return entry?.note ?? null;
507 }
TheCodedProf267563a2023-01-21 17:00:57 -0500508
509 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500510 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500511 await this.modNotes.deleteMany({ guild: guild });
512 }
pineafan4edb7762022-06-26 19:21:04 +0100513}
514
pineafan73a7c4a2022-07-24 10:38:04 +0100515export class Premium {
516 premium: Collection<PremiumSchema>;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500517 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
518 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100519
pineafan3a02ea32022-08-11 21:35:04 +0100520 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100521 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500522 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100523 }
524
TheCodedProf633866f2023-02-03 17:06:00 -0500525 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500526 // console.log("Premium updateUser");
TheCodedProf633866f2023-02-03 17:06:00 -0500527 if(!(await this.userExists(user))) await this.createUser(user, level);
528 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
529 }
530
531 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500532 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500533 const entry = await this.premium.findOne({ user: user });
534 return entry ? true : false;
535 }
536
537 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500538 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500539 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500540 }
541
TheCodedProfaa3fe992023-02-25 21:53:09 -0500542 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500543 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500544 // [Has premium, user giving premium, level, is mod: if given automatically]
545 const cached = this.cache.get(guild);
546 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500547 const entries = await this.premium.find({}).toArray();
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500548 const members = (await client.guilds.fetch(guild)).members.cache
TheCodedProf94ff6de2023-02-22 17:47:26 -0500549 for(const {user} of entries) {
550 const member = members.get(user);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500551 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 -0500552 const modPerms = //TODO: Create list in config for perms
553 member.permissions.has("Administrator") ||
554 member.permissions.has("ManageChannels") ||
555 member.permissions.has("ManageRoles") ||
556 member.permissions.has("ManageEmojisAndStickers") ||
557 member.permissions.has("ManageWebhooks") ||
558 member.permissions.has("ManageGuild") ||
559 member.permissions.has("KickMembers") ||
560 member.permissions.has("BanMembers") ||
561 member.permissions.has("ManageEvents") ||
562 member.permissions.has("ManageMessages") ||
563 member.permissions.has("ManageThreads")
564 const entry = entries.find(e => e.user === member.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500565 if(entry && (entry.level === 3) && modPerms) {
566 this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
567 return [true, member.id, entry.level, true];
568 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500569 }
570 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100571 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500572 appliesTo: {
573 $elemMatch: {
574 $eq: guild
575 }
576 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100577 });
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500578 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 -0500579 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500580 }
581
TheCodedProf633866f2023-02-03 17:06:00 -0500582 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500583 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500584 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500585 if (!entry) return null;
586 return entry;
587 }
588
TheCodedProf94ff6de2023-02-22 17:47:26 -0500589 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500590 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500591 const entries = await this.premium.find({}).toArray();
TheCodedProf94ff6de2023-02-22 17:47:26 -0500592 if(member) {
593 const entry = entries.find(e => e.user === member.id);
594 if(entry) {
595 const expiresAt = entry.expiresAt;
596 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
597 }
598 const roles = member.roles;
599 let level = 0;
600 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500601 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500602 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500603 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500604 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500605 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500606 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500607 level = 3;
608 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500609 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500610 if (level > 0) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500611 await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
TheCodedProf633866f2023-02-03 17:06:00 -0500612 } else {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500613 await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
614 }
615 } else {
616 const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
617 for(const {roles, id} of members.values()) {
618 const entry = entries.find(e => e.user === id);
619 if(entry) {
620 const expiresAt = entry.expiresAt;
621 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
622 }
623 let level: number = 0;
624 if (roles.cache.has("1066468879309750313")) {
625 level = 99;
626 } else if (roles.cache.has("1066465491713003520")) {
627 level = 1;
628 } else if (roles.cache.has("1066439526496604194")) {
629 level = 2;
630 } else if (roles.cache.has("1066464134322978912")) {
631 level = 3;
632 }
633 await this.updateUser(id, level);
634 if (level > 0) {
635 await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
636 } else {
637 await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
638 }
TheCodedProf633866f2023-02-03 17:06:00 -0500639 }
640 }
TheCodedProf267563a2023-01-21 17:00:57 -0500641 }
642
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500643 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500644 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500645 const { level } = (await this.fetchUser(user))!;
646 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500647 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100648 }
TheCodedProffc420b72023-01-24 17:14:38 -0500649
650 removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500651 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500652 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProffc420b72023-01-24 17:14:38 -0500653 return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
654 }
pineafan4edb7762022-06-26 19:21:04 +0100655}
656
pineafan6fb3e072022-05-20 19:27:23 +0100657export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100658 id: string;
659 version: number;
PineaFan100df682023-01-02 13:26:08 +0000660 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100661 filters: {
662 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100663 NSFW: boolean;
664 size: boolean;
665 };
666 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100667 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100668 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100669 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100670 strict: string[];
671 loose: string[];
672 };
pineafan6fb3e072022-05-20 19:27:23 +0100673 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100674 users: string[];
675 roles: string[];
676 channels: string[];
677 };
678 };
pineafan6fb3e072022-05-20 19:27:23 +0100679 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100680 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000681 allowed: {
682 channels: string[];
683 roles: string[];
684 users: string[];
685 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100686 };
pineafan6fb3e072022-05-20 19:27:23 +0100687 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100688 mass: number;
689 everyone: boolean;
690 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100691 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100692 roles: string[];
693 rolesToMention: string[];
694 users: string[];
695 channels: string[];
696 };
697 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500698 clean: {
699 channels: string[];
700 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500701 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500702 roles: string[];
703 }
704 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100705 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500706 autoPublish: {
707 enabled: boolean;
708 channels: string[];
709 }
pineafan6fb3e072022-05-20 19:27:23 +0100710 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100711 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100712 role: string | null;
713 ping: string | null;
714 channel: string | null;
715 message: string | null;
716 };
717 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100718 logging: {
719 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100720 enabled: boolean;
721 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100722 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100723 };
pineafan6fb3e072022-05-20 19:27:23 +0100724 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100725 channel: string | null;
726 };
pineafan73a7c4a2022-07-24 10:38:04 +0100727 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100728 channel: string | null;
729 saved: Record<string, string>;
730 };
731 };
pineafan6fb3e072022-05-20 19:27:23 +0100732 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000733 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100734 role: string | null;
735 };
pineafan6fb3e072022-05-20 19:27:23 +0100736 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100737 enabled: boolean;
738 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100739 types: string;
740 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100741 useCustom: boolean;
742 supportRole: string | null;
743 maxTickets: number;
744 };
pineafan6fb3e072022-05-20 19:27:23 +0100745 moderation: {
746 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100747 timeout: boolean;
748 role: string | null;
749 text: string | null;
750 link: string | null;
751 };
pineafan6fb3e072022-05-20 19:27:23 +0100752 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100753 text: string | null;
754 link: string | null;
755 };
pineafan6fb3e072022-05-20 19:27:23 +0100756 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100757 text: string | null;
758 link: string | null;
759 };
pineafan6fb3e072022-05-20 19:27:23 +0100760 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100761 text: string | null;
762 link: string | null;
763 };
pineafan6fb3e072022-05-20 19:27:23 +0100764 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100765 text: string | null;
766 link: string | null;
767 };
pineafan6fb3e072022-05-20 19:27:23 +0100768 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100769 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500770 text: null;
771 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100772 };
PineaFane6ba7882023-01-18 20:41:16 +0000773 nick: {
774 text: string | null;
775 link: string | null;
776 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100777 };
pineafan6fb3e072022-05-20 19:27:23 +0100778 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100779 name: string;
780 retainPrevious: boolean;
781 nullable: boolean;
782 track: string[];
783 manageableBy: string[];
784 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100785 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100786 enabled: boolean;
787 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100788 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100789 name: string;
790 description: string;
791 min: number;
792 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100793 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100794 name: string;
795 description: string | null;
796 role: string;
797 }[];
798 }[];
799 };
800 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100801}
pineafan4edb7762022-06-26 19:21:04 +0100802
803export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100804 type: string;
805 guild: string;
806 user: string;
807 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100808 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100809 occurredAt: Date;
810 before: string | null;
811 after: string | null;
812 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100813}
814
815export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100816 guild: string;
817 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100818 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100819}
820
pineafan73a7c4a2022-07-24 10:38:04 +0100821export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100822 user: string;
823 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100824 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -0500825 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100826}