blob: 2e64320f58f38d44716122e4a6a6def9e821eb1f [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" };
24
pineafan4edb7762022-06-26 19:21:04 +010025export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010026 guilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050027 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010028
29 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010030 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050031 this.defaultData = defaultData;
pineafan63fc5e22022-08-04 22:04:10 +010032 }
33
Skyler Greyad002172022-08-16 18:48:26 +010034 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050035 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010036 const entry = await this.guilds.findOne({ id: guild });
TheCodedProff8ef7942023-03-03 15:32:32 -050037 const data = _.clone(this.defaultData!);
38 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010039 }
40
Skyler Grey11236ba2022-08-08 21:13:33 +010041 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050042 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010043 // eslint-disable-next-line @typescript-eslint/no-explicit-any
44 const uo: Record<string, any> = {};
45 if (!Array.isArray(unset)) unset = [unset];
46 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010047 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010048 }
Skyler Grey75ea9172022-08-06 10:22:23 +010049 const out = { $set: {}, $unset: {} };
50 if (set) out.$set = set;
51 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010052 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010053 }
54
pineafan63fc5e22022-08-04 22:04:10 +010055 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010056 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050057 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010058 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010059 await this.guilds.updateOne(
60 { id: guild },
61 {
62 $addToSet: { [key]: { $each: value } }
63 },
64 { upsert: true }
65 );
pineafan6702cef2022-06-13 17:52:37 +010066 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010067 await this.guilds.updateOne(
68 { id: guild },
69 {
70 $addToSet: { [key]: value }
71 },
72 { upsert: true }
73 );
pineafan6702cef2022-06-13 17:52:37 +010074 }
75 }
76
Skyler Grey75ea9172022-08-06 10:22:23 +010077 async remove(
78 guild: string,
79 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010080 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010081 value: any,
82 innerKey?: string | null
83 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -050084 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +010085 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010086 await this.guilds.updateOne(
87 { id: guild },
88 {
89 $pull: { [key]: { [innerKey]: { $eq: value } } }
90 },
91 { upsert: true }
92 );
pineafan0bc04162022-07-25 17:22:26 +010093 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010094 await this.guilds.updateOne(
95 { id: guild },
96 {
97 $pullAll: { [key]: value }
98 },
99 { upsert: true }
100 );
pineafan6702cef2022-06-13 17:52:37 +0100101 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100102 await this.guilds.updateOne(
103 { id: guild },
104 {
105 $pullAll: { [key]: [value] }
106 },
107 { upsert: true }
108 );
pineafan6702cef2022-06-13 17:52:37 +0100109 }
pineafan6fb3e072022-05-20 19:27:23 +0100110 }
pineafane23c4ec2022-07-27 21:56:27 +0100111
112 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500113 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100114 await this.guilds.deleteOne({ id: guild });
115 }
pineafan6fb3e072022-05-20 19:27:23 +0100116}
117
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500118interface TranscriptEmbed {
119 title?: string;
120 description?: string;
121 fields?: {
122 name: string;
123 value: string;
124 inline: boolean;
125 }[];
126 footer?: {
127 text: string;
128 iconURL?: string;
129 };
TheCodedProffaae5332023-03-01 18:16:05 -0500130 color?: number;
131 timestamp?: string;
132 author?: {
133 name: string;
134 iconURL?: string;
135 url?: string;
136 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500137}
138
139interface TranscriptComponent {
140 type: number;
141 style?: ButtonStyle;
142 label?: string;
143 description?: string;
144 placeholder?: string;
145 emojiURL?: string;
146}
147
148interface TranscriptAuthor {
149 username: string;
150 discriminator: number;
151 nickname?: string;
152 id: string;
153 iconURL?: string;
154 topRole: {
155 color: number;
156 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500157 };
158 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500159}
160
161interface TranscriptAttachment {
162 url: string;
163 filename: string;
164 size: number;
165 log?: string;
166}
167
168interface TranscriptMessage {
169 id: string;
170 author: TranscriptAuthor;
171 content?: string;
172 embeds?: TranscriptEmbed[];
173 components?: TranscriptComponent[][];
174 editedTimestamp?: number;
175 createdTimestamp: number;
176 flags?: string[];
177 attachments?: TranscriptAttachment[];
178 stickerURLs?: string[];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500179 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500180}
181
182interface TranscriptSchema {
183 code: string;
184 for: TranscriptAuthor;
185 type: "ticket" | "purge"
186 guild: string;
187 channel: string;
188 messages: TranscriptMessage[];
189 createdTimestamp: number;
190 createdBy: TranscriptAuthor;
191}
192
193export class Transcript {
194 transcripts: Collection<TranscriptSchema>;
195
196 constructor() {
197 this.transcripts = database.collection<TranscriptSchema>("transcripts");
198 }
199
200 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500201 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500202 let code;
203 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500204 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500205 } while (await this.transcripts.findOne({ code: code }));
206
TheCodedProffaae5332023-03-01 18:16:05 -0500207 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500208 if(doc.acknowledged) return code;
209 else return null;
210 }
211
212 async read(code: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500213 // console.log("Transcript read")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500214 return await this.transcripts.findOne({ code: code });
215 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500216
217 async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
218 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
219 const newOut: Omit<TranscriptSchema, "code"> = {
220 type: "ticket",
221 for: {
222 username: member!.user.username,
223 discriminator: parseInt(member!.user.discriminator),
224 id: member!.user.id,
225 topRole: {
226 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500227 },
228 iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
229 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500230 },
231 guild: interaction.guild!.id,
232 channel: interaction.channel!.id,
233 messages: [],
234 createdTimestamp: Date.now(),
235 createdBy: {
236 username: interaction.user.username,
237 discriminator: parseInt(interaction.user.discriminator),
238 id: interaction.user.id,
239 topRole: {
240 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500241 },
242 iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
243 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500244 }
245 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500246 if(member.nickname) newOut.for.nickname = member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500247 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
248 messages.reverse().forEach((message) => {
249 const msg: TranscriptMessage = {
250 id: message.id,
251 author: {
252 username: message.author.username,
253 discriminator: parseInt(message.author.discriminator),
254 id: message.author.id,
255 topRole: {
256 color: message.member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500257 },
TheCodedProffaae5332023-03-01 18:16:05 -0500258 iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}),
TheCodedProf088b1b22023-02-28 17:31:11 -0500259 bot: message.author.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500260 },
261 createdTimestamp: message.createdTimestamp
262 };
TheCodedProf088b1b22023-02-28 17:31:11 -0500263 if(message.member?.nickname) msg.author.nickname = message.member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500264 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
265 if (message.content) msg.content = message.content;
266 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
267 const obj: TranscriptEmbed = {};
268 if (embed.title) obj.title = embed.title;
269 if (embed.description) obj.description = embed.description;
270 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
271 return {
272 name: field.name,
273 value: field.value,
274 inline: field.inline ?? false
275 }
276 });
TheCodedProffaae5332023-03-01 18:16:05 -0500277 if (embed.color) obj.color = embed.color;
278 if (embed.timestamp) obj.timestamp = embed.timestamp
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500279 if (embed.footer) obj.footer = {
280 text: embed.footer.text,
281 };
282 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
TheCodedProffaae5332023-03-01 18:16:05 -0500283 if (embed.author) obj.author = {
284 name: embed.author.name
285 };
286 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
287 if (embed.author?.url) obj.author!.url = embed.author.url;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500288 return obj;
289 });
290 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
291 const obj: TranscriptComponent = {
292 type: child.type
293 }
294 if (child.type === ComponentType.Button) {
295 obj.style = child.style;
296 obj.label = child.label ?? "";
297 } else if (child.type > 2) {
298 obj.placeholder = child.placeholder ?? "";
299 }
300 return obj
301 }));
302 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
303 msg.flags = message.flags.toArray();
304
305 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
306 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
307 newOut.messages.push(msg);
308 });
309 return newOut;
310 }
311
312 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
313 let out = "";
314 for (const message of transcript.messages) {
315 if (message.referencedMessage) {
316 if (Array.isArray(message.referencedMessage)) {
317 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
318 }
319 else out += `> [Reply To] ${message.referencedMessage}\n`;
320 }
TheCodedProff8ef7942023-03-03 15:32:32 -0500321 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id})`;
322 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
323 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500324 out += "\n";
325 if (message.content) out += `[Content]\n${message.content}\n\n`;
326 if (message.embeds) {
327 for (const embed of message.embeds) {
328 out += `[Embed]\n`;
329 if (embed.title) out += `| Title: ${embed.title}\n`;
330 if (embed.description) out += `| Description: ${embed.description}\n`;
331 if (embed.fields) {
332 for (const field of embed.fields) {
333 out += `| Field: ${field.name} - ${field.value}\n`;
334 }
335 }
336 if (embed.footer) {
337 out += `|Footer: ${embed.footer.text}\n`;
338 }
339 out += "\n";
340 }
341 }
342 if (message.components) {
343 for (const component of message.components) {
344 out += `[Component]\n`;
345 for (const button of component) {
346 out += `| Button: ${button.label ?? button.description}\n`;
347 }
348 out += "\n";
349 }
350 }
351 if (message.attachments) {
352 for (const attachment of message.attachments) {
353 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
354 }
355 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500356 out += "\n\n"
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500357 }
358 return out
359 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500360}
361
pineafan4edb7762022-06-26 19:21:04 +0100362export class History {
363 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100364
pineafan3a02ea32022-08-11 21:35:04 +0100365 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100366 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100367 }
368
Skyler Grey75ea9172022-08-06 10:22:23 +0100369 async create(
370 type: string,
371 guild: string,
372 user: Discord.User,
373 moderator: Discord.User | null,
374 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100375 before?: string | null,
376 after?: string | null,
377 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100378 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500379 // console.log("History create");
pineafan4edb7762022-06-26 19:21:04 +0100380 await this.histories.insertOne({
381 type: type,
382 guild: guild,
383 user: user.id,
pineafan3a02ea32022-08-11 21:35:04 +0100384 moderator: moderator ? moderator.id : null,
pineafan4edb7762022-06-26 19:21:04 +0100385 reason: reason,
386 occurredAt: new Date(),
pineafan3a02ea32022-08-11 21:35:04 +0100387 before: before ?? null,
388 after: after ?? null,
389 amount: amount ?? null
TheCodedProffaae5332023-03-01 18:16:05 -0500390 }, collectionOptions);
pineafan4edb7762022-06-26 19:21:04 +0100391 }
392
393 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500394 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100395 const entry = (await this.histories
396 .find({
397 guild: guild,
398 user: user,
399 occurredAt: {
400 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
401 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
402 }
403 })
404 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100405 return entry;
406 }
pineafane23c4ec2022-07-27 21:56:27 +0100407
408 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500409 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100410 await this.histories.deleteMany({ guild: guild });
411 }
pineafan4edb7762022-06-26 19:21:04 +0100412}
413
TheCodedProfb5e9d552023-01-29 15:43:26 -0500414interface ScanCacheSchema {
415 addedAt: Date;
416 hash: string;
417 data: boolean;
418 tags: string[];
419}
420
421export class ScanCache {
422 scanCache: Collection<ScanCacheSchema>;
423
424 constructor() {
425 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
426 }
427
428 async read(hash: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500429 // console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500430 return await this.scanCache.findOne({ hash: hash });
431 }
432
433 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500434 // console.log("ScanCache write");
TheCodedProffaae5332023-03-01 18:16:05 -0500435 await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500436 }
437
438 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500439 // console.log("ScanCache cleanup");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500440 await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
441 }
442}
443
PineaFan538d3752023-01-12 21:48:23 +0000444export class PerformanceTest {
445 performanceData: Collection<PerformanceDataSchema>;
446
447 constructor() {
448 this.performanceData = database.collection<PerformanceDataSchema>("performance");
449 }
450
451 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500452 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000453 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500454 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000455 }
456 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500457 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000458 return await this.performanceData.find({}).toArray();
459 }
460}
461
462export interface PerformanceDataSchema {
463 timestamp?: Date;
464 discord: number;
465 databaseRead: number;
466 resources: {
467 cpu: number;
468 memory: number;
469 temperature: number;
470 }
471}
472
pineafan4edb7762022-06-26 19:21:04 +0100473export class ModNotes {
474 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100475
pineafan3a02ea32022-08-11 21:35:04 +0100476 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100477 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100478 }
479
480 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500481 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100482 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100483 }
484
485 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500486 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100487 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100488 return entry?.note ?? null;
489 }
TheCodedProf267563a2023-01-21 17:00:57 -0500490
491 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500492 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500493 await this.modNotes.deleteMany({ guild: guild });
494 }
pineafan4edb7762022-06-26 19:21:04 +0100495}
496
pineafan73a7c4a2022-07-24 10:38:04 +0100497export class Premium {
498 premium: Collection<PremiumSchema>;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500499 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
500 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100501
pineafan3a02ea32022-08-11 21:35:04 +0100502 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100503 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500504 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100505 }
506
TheCodedProf633866f2023-02-03 17:06:00 -0500507 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500508 // console.log("Premium updateUser");
TheCodedProf633866f2023-02-03 17:06:00 -0500509 if(!(await this.userExists(user))) await this.createUser(user, level);
510 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
511 }
512
513 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500514 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500515 const entry = await this.premium.findOne({ user: user });
516 return entry ? true : false;
517 }
518
519 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500520 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500521 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500522 }
523
TheCodedProfaa3fe992023-02-25 21:53:09 -0500524 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500525 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500526 // [Has premium, user giving premium, level, is mod: if given automatically]
527 const cached = this.cache.get(guild);
528 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500529 const entries = await this.premium.find({}).toArray();
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500530 const members = (await client.guilds.fetch(guild)).members.cache
TheCodedProf94ff6de2023-02-22 17:47:26 -0500531 for(const {user} of entries) {
532 const member = members.get(user);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500533 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 -0500534 const modPerms = //TODO: Create list in config for perms
535 member.permissions.has("Administrator") ||
536 member.permissions.has("ManageChannels") ||
537 member.permissions.has("ManageRoles") ||
538 member.permissions.has("ManageEmojisAndStickers") ||
539 member.permissions.has("ManageWebhooks") ||
540 member.permissions.has("ManageGuild") ||
541 member.permissions.has("KickMembers") ||
542 member.permissions.has("BanMembers") ||
543 member.permissions.has("ManageEvents") ||
544 member.permissions.has("ManageMessages") ||
545 member.permissions.has("ManageThreads")
546 const entry = entries.find(e => e.user === member.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500547 if(entry && (entry.level === 3) && modPerms) {
548 this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
549 return [true, member.id, entry.level, true];
550 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500551 }
552 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100553 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500554 appliesTo: {
555 $elemMatch: {
556 $eq: guild
557 }
558 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100559 });
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500560 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 -0500561 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500562 }
563
TheCodedProf633866f2023-02-03 17:06:00 -0500564 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500565 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500566 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500567 if (!entry) return null;
568 return entry;
569 }
570
TheCodedProf94ff6de2023-02-22 17:47:26 -0500571 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500572 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500573 const entries = await this.premium.find({}).toArray();
TheCodedProf94ff6de2023-02-22 17:47:26 -0500574 if(member) {
575 const entry = entries.find(e => e.user === member.id);
576 if(entry) {
577 const expiresAt = entry.expiresAt;
578 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
579 }
580 const roles = member.roles;
581 let level = 0;
582 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500583 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500584 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500585 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500586 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500587 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500588 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500589 level = 3;
590 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500591 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500592 if (level > 0) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500593 await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
TheCodedProf633866f2023-02-03 17:06:00 -0500594 } else {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500595 await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
596 }
597 } else {
598 const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
599 for(const {roles, id} of members.values()) {
600 const entry = entries.find(e => e.user === id);
601 if(entry) {
602 const expiresAt = entry.expiresAt;
603 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
604 }
605 let level: number = 0;
606 if (roles.cache.has("1066468879309750313")) {
607 level = 99;
608 } else if (roles.cache.has("1066465491713003520")) {
609 level = 1;
610 } else if (roles.cache.has("1066439526496604194")) {
611 level = 2;
612 } else if (roles.cache.has("1066464134322978912")) {
613 level = 3;
614 }
615 await this.updateUser(id, level);
616 if (level > 0) {
617 await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
618 } else {
619 await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
620 }
TheCodedProf633866f2023-02-03 17:06:00 -0500621 }
622 }
TheCodedProf267563a2023-01-21 17:00:57 -0500623 }
624
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500625 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500626 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500627 const { level } = (await this.fetchUser(user))!;
628 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500629 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100630 }
TheCodedProffc420b72023-01-24 17:14:38 -0500631
632 removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500633 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500634 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProffc420b72023-01-24 17:14:38 -0500635 return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
636 }
pineafan4edb7762022-06-26 19:21:04 +0100637}
638
pineafan6fb3e072022-05-20 19:27:23 +0100639export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100640 id: string;
641 version: number;
PineaFan100df682023-01-02 13:26:08 +0000642 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100643 filters: {
644 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100645 NSFW: boolean;
646 size: boolean;
647 };
648 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100649 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100650 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100651 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100652 strict: string[];
653 loose: string[];
654 };
pineafan6fb3e072022-05-20 19:27:23 +0100655 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100656 users: string[];
657 roles: string[];
658 channels: string[];
659 };
660 };
pineafan6fb3e072022-05-20 19:27:23 +0100661 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100662 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000663 allowed: {
664 channels: string[];
665 roles: string[];
666 users: string[];
667 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100668 };
pineafan6fb3e072022-05-20 19:27:23 +0100669 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100670 mass: number;
671 everyone: boolean;
672 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100673 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100674 roles: string[];
675 rolesToMention: string[];
676 users: string[];
677 channels: string[];
678 };
679 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500680 clean: {
681 channels: string[];
682 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500683 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500684 roles: string[];
685 }
686 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100687 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500688 autoPublish: {
689 enabled: boolean;
690 channels: string[];
691 }
pineafan6fb3e072022-05-20 19:27:23 +0100692 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100693 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100694 role: string | null;
695 ping: string | null;
696 channel: string | null;
697 message: string | null;
698 };
699 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100700 logging: {
701 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100702 enabled: boolean;
703 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100704 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100705 };
pineafan6fb3e072022-05-20 19:27:23 +0100706 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100707 channel: string | null;
708 };
pineafan73a7c4a2022-07-24 10:38:04 +0100709 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100710 channel: string | null;
711 saved: Record<string, string>;
712 };
713 };
pineafan6fb3e072022-05-20 19:27:23 +0100714 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000715 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100716 role: string | null;
717 };
pineafan6fb3e072022-05-20 19:27:23 +0100718 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100719 enabled: boolean;
720 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100721 types: string;
722 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100723 useCustom: boolean;
724 supportRole: string | null;
725 maxTickets: number;
726 };
pineafan6fb3e072022-05-20 19:27:23 +0100727 moderation: {
728 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100729 timeout: boolean;
730 role: string | null;
731 text: string | null;
732 link: string | null;
733 };
pineafan6fb3e072022-05-20 19:27:23 +0100734 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100735 text: string | null;
736 link: string | null;
737 };
pineafan6fb3e072022-05-20 19:27:23 +0100738 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100739 text: string | null;
740 link: string | null;
741 };
pineafan6fb3e072022-05-20 19:27:23 +0100742 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100743 text: string | null;
744 link: string | null;
745 };
pineafan6fb3e072022-05-20 19:27:23 +0100746 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100747 text: string | null;
748 link: string | null;
749 };
pineafan6fb3e072022-05-20 19:27:23 +0100750 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100751 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500752 text: null;
753 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100754 };
PineaFane6ba7882023-01-18 20:41:16 +0000755 nick: {
756 text: string | null;
757 link: string | null;
758 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100759 };
pineafan6fb3e072022-05-20 19:27:23 +0100760 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100761 name: string;
762 retainPrevious: boolean;
763 nullable: boolean;
764 track: string[];
765 manageableBy: string[];
766 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100767 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100768 enabled: boolean;
769 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100770 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100771 name: string;
772 description: string;
773 min: number;
774 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100775 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100776 name: string;
777 description: string | null;
778 role: string;
779 }[];
780 }[];
781 };
782 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100783}
pineafan4edb7762022-06-26 19:21:04 +0100784
785export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100786 type: string;
787 guild: string;
788 user: string;
789 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100790 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100791 occurredAt: Date;
792 before: string | null;
793 after: string | null;
794 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100795}
796
797export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100798 guild: string;
799 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100800 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100801}
802
pineafan73a7c4a2022-07-24 10:38:04 +0100803export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100804 user: string;
805 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100806 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -0500807 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100808}