blob: 1e2d3baad11ab6562401e7170512978e71af6bd5 [file] [log] [blame]
TheCodedProf9c51a7e2023-02-27 17:11:13 -05001import { ButtonStyle, CommandInteraction, ComponentType, GuildMember, Message, MessageComponentInteraction } from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +01002import type Discord from "discord.js";
3import { Collection, MongoClient } from "mongodb";
pineafana2e39c72023-02-21 18:37:32 +00004import config from "../config/main.js";
TheCodedProf633866f2023-02-03 17:06:00 -05005import client from "../utils/client.js";
TheCodedProf088b1b22023-02-28 17:31:11 -05006import * as crypto from "crypto";
pineafan4edb7762022-06-26 19:21:04 +01007
TheCodedProffaae5332023-03-01 18:16:05 -05008// config.mongoOptions.host, {
9// auth: {
10// username: config.mongoOptions.username,
11// password: config.mongoOptions.password
12// },
13// authSource: config.mongoOptions.authSource
14// }
15// mongodb://emails:SweetLife2023!!@127.0.0.1:28180/saveEmail?retryWrites=true&w=majority
16const username = encodeURIComponent(config.mongoOptions.username);
17const password = encodeURIComponent(config.mongoOptions.password);
18const mongoClient = new MongoClient(username ? `mongodb://${username}:${password}@${config.mongoOptions.host}` : `mongodb://${config.mongoOptions.host}`, {authSource: "admin"});
pineafan63fc5e22022-08-04 22:04:10 +010019await mongoClient.connect();
TheCodedProf8d577fa2023-03-01 13:06:40 -050020const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010021
TheCodedProffaae5332023-03-01 18:16:05 -050022const collectionOptions = { authdb: "admin" };
23
pineafan4edb7762022-06-26 19:21:04 +010024export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010025 guilds: Collection<GuildConfig>;
pineafan63fc5e22022-08-04 22:04:10 +010026 defaultData: GuildConfig | null;
27
28 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010029 this.guilds = database.collection<GuildConfig>("guilds");
pineafan63fc5e22022-08-04 22:04:10 +010030 this.defaultData = null;
pineafan6fb3e072022-05-20 19:27:23 +010031 }
32
Skyler Greyad002172022-08-16 18:48:26 +010033 async setup(): Promise<Guilds> {
Skyler Grey11236ba2022-08-08 21:13:33 +010034 this.defaultData = (await import("../config/default.json", { assert: { type: "json" } }))
35 .default as unknown as GuildConfig;
36 return this;
pineafan63fc5e22022-08-04 22:04:10 +010037 }
38
Skyler Greyad002172022-08-16 18:48:26 +010039 async read(guild: string): Promise<GuildConfig> {
TheCodedProffaae5332023-03-01 18:16:05 -050040 console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010041 const entry = await this.guilds.findOne({ id: guild });
PineaFandf4996f2023-01-01 14:20:06 +000042 return Object.assign({}, this.defaultData, entry);
pineafan6fb3e072022-05-20 19:27:23 +010043 }
44
Skyler Grey11236ba2022-08-08 21:13:33 +010045 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProffaae5332023-03-01 18:16:05 -050046 console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010047 // eslint-disable-next-line @typescript-eslint/no-explicit-any
48 const uo: Record<string, any> = {};
49 if (!Array.isArray(unset)) unset = [unset];
50 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010051 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010052 }
Skyler Grey75ea9172022-08-06 10:22:23 +010053 const out = { $set: {}, $unset: {} };
54 if (set) out.$set = set;
55 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010056 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010057 }
58
pineafan63fc5e22022-08-04 22:04:10 +010059 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010060 async append(guild: string, key: string, value: any) {
TheCodedProffaae5332023-03-01 18:16:05 -050061 console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010062 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010063 await this.guilds.updateOne(
64 { id: guild },
65 {
66 $addToSet: { [key]: { $each: value } }
67 },
68 { upsert: true }
69 );
pineafan6702cef2022-06-13 17:52:37 +010070 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010071 await this.guilds.updateOne(
72 { id: guild },
73 {
74 $addToSet: { [key]: value }
75 },
76 { upsert: true }
77 );
pineafan6702cef2022-06-13 17:52:37 +010078 }
79 }
80
Skyler Grey75ea9172022-08-06 10:22:23 +010081 async remove(
82 guild: string,
83 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010084 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010085 value: any,
86 innerKey?: string | null
87 ) {
TheCodedProffaae5332023-03-01 18:16:05 -050088 console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +010089 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010090 await this.guilds.updateOne(
91 { id: guild },
92 {
93 $pull: { [key]: { [innerKey]: { $eq: value } } }
94 },
95 { upsert: true }
96 );
pineafan0bc04162022-07-25 17:22:26 +010097 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010098 await this.guilds.updateOne(
99 { id: guild },
100 {
101 $pullAll: { [key]: value }
102 },
103 { upsert: true }
104 );
pineafan6702cef2022-06-13 17:52:37 +0100105 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 await this.guilds.updateOne(
107 { id: guild },
108 {
109 $pullAll: { [key]: [value] }
110 },
111 { upsert: true }
112 );
pineafan6702cef2022-06-13 17:52:37 +0100113 }
pineafan6fb3e072022-05-20 19:27:23 +0100114 }
pineafane23c4ec2022-07-27 21:56:27 +0100115
116 async delete(guild: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500117 console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100118 await this.guilds.deleteOne({ id: guild });
119 }
pineafan6fb3e072022-05-20 19:27:23 +0100120}
121
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500122interface TranscriptEmbed {
123 title?: string;
124 description?: string;
125 fields?: {
126 name: string;
127 value: string;
128 inline: boolean;
129 }[];
130 footer?: {
131 text: string;
132 iconURL?: string;
133 };
TheCodedProffaae5332023-03-01 18:16:05 -0500134 color?: number;
135 timestamp?: string;
136 author?: {
137 name: string;
138 iconURL?: string;
139 url?: string;
140 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500141}
142
143interface TranscriptComponent {
144 type: number;
145 style?: ButtonStyle;
146 label?: string;
147 description?: string;
148 placeholder?: string;
149 emojiURL?: string;
150}
151
152interface TranscriptAuthor {
153 username: string;
154 discriminator: number;
155 nickname?: string;
156 id: string;
157 iconURL?: string;
158 topRole: {
159 color: number;
160 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500161 };
162 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500163}
164
165interface TranscriptAttachment {
166 url: string;
167 filename: string;
168 size: number;
169 log?: string;
170}
171
172interface TranscriptMessage {
173 id: string;
174 author: TranscriptAuthor;
175 content?: string;
176 embeds?: TranscriptEmbed[];
177 components?: TranscriptComponent[][];
178 editedTimestamp?: number;
179 createdTimestamp: number;
180 flags?: string[];
181 attachments?: TranscriptAttachment[];
182 stickerURLs?: string[];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500183 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500184}
185
186interface TranscriptSchema {
187 code: string;
188 for: TranscriptAuthor;
189 type: "ticket" | "purge"
190 guild: string;
191 channel: string;
192 messages: TranscriptMessage[];
193 createdTimestamp: number;
194 createdBy: TranscriptAuthor;
195}
196
197export class Transcript {
198 transcripts: Collection<TranscriptSchema>;
199
200 constructor() {
201 this.transcripts = database.collection<TranscriptSchema>("transcripts");
202 }
203
204 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProffaae5332023-03-01 18:16:05 -0500205 console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500206 let code;
207 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500208 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500209 } while (await this.transcripts.findOne({ code: code }));
210
TheCodedProffaae5332023-03-01 18:16:05 -0500211 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500212 if(doc.acknowledged) return code;
213 else return null;
214 }
215
216 async read(code: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500217 console.log("Transcript read")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500218 return await this.transcripts.findOne({ code: code });
219 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500220
221 async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
222 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
223 const newOut: Omit<TranscriptSchema, "code"> = {
224 type: "ticket",
225 for: {
226 username: member!.user.username,
227 discriminator: parseInt(member!.user.discriminator),
228 id: member!.user.id,
229 topRole: {
230 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500231 },
232 iconURL: member!.user.displayAvatarURL({ forceStatic: true}),
233 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500234 },
235 guild: interaction.guild!.id,
236 channel: interaction.channel!.id,
237 messages: [],
238 createdTimestamp: Date.now(),
239 createdBy: {
240 username: interaction.user.username,
241 discriminator: parseInt(interaction.user.discriminator),
242 id: interaction.user.id,
243 topRole: {
244 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500245 },
246 iconURL: interaction.user.displayAvatarURL({ forceStatic: true}),
247 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500248 }
249 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500250 if(member.nickname) newOut.for.nickname = member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500251 if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
252 messages.reverse().forEach((message) => {
253 const msg: TranscriptMessage = {
254 id: message.id,
255 author: {
256 username: message.author.username,
257 discriminator: parseInt(message.author.discriminator),
258 id: message.author.id,
259 topRole: {
260 color: message.member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500261 },
TheCodedProffaae5332023-03-01 18:16:05 -0500262 iconURL: message.member!.user.displayAvatarURL({ forceStatic: true}),
TheCodedProf088b1b22023-02-28 17:31:11 -0500263 bot: message.author.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500264 },
265 createdTimestamp: message.createdTimestamp
266 };
TheCodedProf088b1b22023-02-28 17:31:11 -0500267 if(message.member?.nickname) msg.author.nickname = message.member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500268 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
269 if (message.content) msg.content = message.content;
270 if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => {
271 const obj: TranscriptEmbed = {};
272 if (embed.title) obj.title = embed.title;
273 if (embed.description) obj.description = embed.description;
274 if (embed.fields.length > 0) obj.fields = embed.fields.map(field => {
275 return {
276 name: field.name,
277 value: field.value,
278 inline: field.inline ?? false
279 }
280 });
TheCodedProffaae5332023-03-01 18:16:05 -0500281 if (embed.color) obj.color = embed.color;
282 if (embed.timestamp) obj.timestamp = embed.timestamp
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500283 if (embed.footer) obj.footer = {
284 text: embed.footer.text,
285 };
286 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
TheCodedProffaae5332023-03-01 18:16:05 -0500287 if (embed.author) obj.author = {
288 name: embed.author.name
289 };
290 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
291 if (embed.author?.url) obj.author!.url = embed.author.url;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500292 return obj;
293 });
294 if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => {
295 const obj: TranscriptComponent = {
296 type: child.type
297 }
298 if (child.type === ComponentType.Button) {
299 obj.style = child.style;
300 obj.label = child.label ?? "";
301 } else if (child.type > 2) {
302 obj.placeholder = child.placeholder ?? "";
303 }
304 return obj
305 }));
306 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
307 msg.flags = message.flags.toArray();
308
309 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url);
310 if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""];
311 newOut.messages.push(msg);
312 });
313 return newOut;
314 }
315
316 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
317 let out = "";
318 for (const message of transcript.messages) {
319 if (message.referencedMessage) {
320 if (Array.isArray(message.referencedMessage)) {
321 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
322 }
323 else out += `> [Reply To] ${message.referencedMessage}\n`;
324 }
325 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id}) `;
326 out += `[${new Date(message.createdTimestamp).toISOString()}] `;
327 if (message.editedTimestamp) out += `[Edited: ${new Date(message.editedTimestamp).toISOString()}] `;
328 out += "\n";
329 if (message.content) out += `[Content]\n${message.content}\n\n`;
330 if (message.embeds) {
331 for (const embed of message.embeds) {
332 out += `[Embed]\n`;
333 if (embed.title) out += `| Title: ${embed.title}\n`;
334 if (embed.description) out += `| Description: ${embed.description}\n`;
335 if (embed.fields) {
336 for (const field of embed.fields) {
337 out += `| Field: ${field.name} - ${field.value}\n`;
338 }
339 }
340 if (embed.footer) {
341 out += `|Footer: ${embed.footer.text}\n`;
342 }
343 out += "\n";
344 }
345 }
346 if (message.components) {
347 for (const component of message.components) {
348 out += `[Component]\n`;
349 for (const button of component) {
350 out += `| Button: ${button.label ?? button.description}\n`;
351 }
352 out += "\n";
353 }
354 }
355 if (message.attachments) {
356 for (const attachment of message.attachments) {
357 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
358 }
359 }
TheCodedProf088b1b22023-02-28 17:31:11 -0500360 out += "\n\n"
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500361 }
362 return out
363 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500364}
365
pineafan4edb7762022-06-26 19:21:04 +0100366export class History {
367 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100368
pineafan3a02ea32022-08-11 21:35:04 +0100369 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100370 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100371 }
372
Skyler Grey75ea9172022-08-06 10:22:23 +0100373 async create(
374 type: string,
375 guild: string,
376 user: Discord.User,
377 moderator: Discord.User | null,
378 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100379 before?: string | null,
380 after?: string | null,
381 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100382 ) {
TheCodedProffaae5332023-03-01 18:16:05 -0500383 console.log("History create");
pineafan4edb7762022-06-26 19:21:04 +0100384 await this.histories.insertOne({
385 type: type,
386 guild: guild,
387 user: user.id,
pineafan3a02ea32022-08-11 21:35:04 +0100388 moderator: moderator ? moderator.id : null,
pineafan4edb7762022-06-26 19:21:04 +0100389 reason: reason,
390 occurredAt: new Date(),
pineafan3a02ea32022-08-11 21:35:04 +0100391 before: before ?? null,
392 after: after ?? null,
393 amount: amount ?? null
TheCodedProffaae5332023-03-01 18:16:05 -0500394 }, collectionOptions);
pineafan4edb7762022-06-26 19:21:04 +0100395 }
396
397 async read(guild: string, user: string, year: number) {
TheCodedProffaae5332023-03-01 18:16:05 -0500398 console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100399 const entry = (await this.histories
400 .find({
401 guild: guild,
402 user: user,
403 occurredAt: {
404 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
405 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
406 }
407 })
408 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100409 return entry;
410 }
pineafane23c4ec2022-07-27 21:56:27 +0100411
412 async delete(guild: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500413 console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100414 await this.histories.deleteMany({ guild: guild });
415 }
pineafan4edb7762022-06-26 19:21:04 +0100416}
417
TheCodedProfb5e9d552023-01-29 15:43:26 -0500418interface ScanCacheSchema {
419 addedAt: Date;
420 hash: string;
421 data: boolean;
422 tags: string[];
423}
424
425export class ScanCache {
426 scanCache: Collection<ScanCacheSchema>;
427
428 constructor() {
429 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
430 }
431
432 async read(hash: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500433 console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500434 return await this.scanCache.findOne({ hash: hash });
435 }
436
437 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProffaae5332023-03-01 18:16:05 -0500438 console.log("ScanCache write");
439 await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }, collectionOptions);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500440 }
441
442 async cleanup() {
TheCodedProffaae5332023-03-01 18:16:05 -0500443 console.log("ScanCache cleanup");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500444 await this.scanCache.deleteMany({ addedAt: { $lt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 31)) }, hash: { $not$text: "http"} });
445 }
446}
447
PineaFan538d3752023-01-12 21:48:23 +0000448export class PerformanceTest {
449 performanceData: Collection<PerformanceDataSchema>;
450
451 constructor() {
452 this.performanceData = database.collection<PerformanceDataSchema>("performance");
453 }
454
455 async record(data: PerformanceDataSchema) {
TheCodedProffaae5332023-03-01 18:16:05 -0500456 console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000457 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500458 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000459 }
460 async read() {
TheCodedProffaae5332023-03-01 18:16:05 -0500461 console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000462 return await this.performanceData.find({}).toArray();
463 }
464}
465
466export interface PerformanceDataSchema {
467 timestamp?: Date;
468 discord: number;
469 databaseRead: number;
470 resources: {
471 cpu: number;
472 memory: number;
473 temperature: number;
474 }
475}
476
pineafan4edb7762022-06-26 19:21:04 +0100477export class ModNotes {
478 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100479
pineafan3a02ea32022-08-11 21:35:04 +0100480 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100481 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100482 }
483
484 async create(guild: string, user: string, note: string | null) {
TheCodedProffaae5332023-03-01 18:16:05 -0500485 console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100486 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100487 }
488
489 async read(guild: string, user: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500490 console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100491 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100492 return entry?.note ?? null;
493 }
TheCodedProf267563a2023-01-21 17:00:57 -0500494
495 async delete(guild: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500496 console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500497 await this.modNotes.deleteMany({ guild: guild });
498 }
pineafan4edb7762022-06-26 19:21:04 +0100499}
500
pineafan73a7c4a2022-07-24 10:38:04 +0100501export class Premium {
502 premium: Collection<PremiumSchema>;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500503 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
504 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100505
pineafan3a02ea32022-08-11 21:35:04 +0100506 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100507 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500508 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100509 }
510
TheCodedProf633866f2023-02-03 17:06:00 -0500511 async updateUser(user: string, level: number) {
TheCodedProffaae5332023-03-01 18:16:05 -0500512 console.log("Premium updateUser");
TheCodedProf633866f2023-02-03 17:06:00 -0500513 if(!(await this.userExists(user))) await this.createUser(user, level);
514 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
515 }
516
517 async userExists(user: string): Promise<boolean> {
TheCodedProffaae5332023-03-01 18:16:05 -0500518 console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500519 const entry = await this.premium.findOne({ user: user });
520 return entry ? true : false;
521 }
522
523 async createUser(user: string, level: number) {
TheCodedProffaae5332023-03-01 18:16:05 -0500524 console.log("Premium createUser");
525 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500526 }
527
TheCodedProfaa3fe992023-02-25 21:53:09 -0500528 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProffaae5332023-03-01 18:16:05 -0500529 console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500530 // [Has premium, user giving premium, level, is mod: if given automatically]
531 const cached = this.cache.get(guild);
532 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500533 const entries = await this.premium.find({}).toArray();
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500534 const members = (await client.guilds.fetch(guild)).members.cache
TheCodedProf94ff6de2023-02-22 17:47:26 -0500535 for(const {user} of entries) {
536 const member = members.get(user);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500537 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 -0500538 const modPerms = //TODO: Create list in config for perms
539 member.permissions.has("Administrator") ||
540 member.permissions.has("ManageChannels") ||
541 member.permissions.has("ManageRoles") ||
542 member.permissions.has("ManageEmojisAndStickers") ||
543 member.permissions.has("ManageWebhooks") ||
544 member.permissions.has("ManageGuild") ||
545 member.permissions.has("KickMembers") ||
546 member.permissions.has("BanMembers") ||
547 member.permissions.has("ManageEvents") ||
548 member.permissions.has("ManageMessages") ||
549 member.permissions.has("ManageThreads")
550 const entry = entries.find(e => e.user === member.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500551 if(entry && (entry.level === 3) && modPerms) {
552 this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]);
553 return [true, member.id, entry.level, true];
554 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500555 }
556 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100557 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500558 appliesTo: {
559 $elemMatch: {
560 $eq: guild
561 }
562 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100563 });
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500564 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 -0500565 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500566 }
567
TheCodedProf633866f2023-02-03 17:06:00 -0500568 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProffaae5332023-03-01 18:16:05 -0500569 console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500570 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500571 if (!entry) return null;
572 return entry;
573 }
574
TheCodedProf94ff6de2023-02-22 17:47:26 -0500575 async checkAllPremium(member?: GuildMember) {
TheCodedProffaae5332023-03-01 18:16:05 -0500576 console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500577 const entries = await this.premium.find({}).toArray();
TheCodedProf94ff6de2023-02-22 17:47:26 -0500578 if(member) {
579 const entry = entries.find(e => e.user === member.id);
580 if(entry) {
581 const expiresAt = entry.expiresAt;
582 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: member.id}) : null;
583 }
584 const roles = member.roles;
585 let level = 0;
586 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500587 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500588 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500589 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500590 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500591 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500592 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500593 level = 3;
594 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500595 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500596 if (level > 0) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500597 await this.premium.updateOne({ user: member.id }, {$unset: { expiresAt: ""}})
TheCodedProf633866f2023-02-03 17:06:00 -0500598 } else {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500599 await this.premium.updateOne({ user: member.id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
600 }
601 } else {
602 const members = await (await client.guilds.fetch('684492926528651336')).members.fetch();
603 for(const {roles, id} of members.values()) {
604 const entry = entries.find(e => e.user === id);
605 if(entry) {
606 const expiresAt = entry.expiresAt;
607 if(expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({user: id}) : null;
608 }
609 let level: number = 0;
610 if (roles.cache.has("1066468879309750313")) {
611 level = 99;
612 } else if (roles.cache.has("1066465491713003520")) {
613 level = 1;
614 } else if (roles.cache.has("1066439526496604194")) {
615 level = 2;
616 } else if (roles.cache.has("1066464134322978912")) {
617 level = 3;
618 }
619 await this.updateUser(id, level);
620 if (level > 0) {
621 await this.premium.updateOne({ user: id }, {$unset: { expiresAt: ""}})
622 } else {
623 await this.premium.updateOne({ user: id }, {$set: { expiresAt: (Date.now() + (1000*60*60*24*3)) }})
624 }
TheCodedProf633866f2023-02-03 17:06:00 -0500625 }
626 }
TheCodedProf267563a2023-01-21 17:00:57 -0500627 }
628
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500629 async addPremium(user: string, guild: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500630 console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500631 const { level } = (await this.fetchUser(user))!;
632 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500633 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100634 }
TheCodedProffc420b72023-01-24 17:14:38 -0500635
636 removePremium(user: string, guild: string) {
TheCodedProffaae5332023-03-01 18:16:05 -0500637 console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500638 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProffc420b72023-01-24 17:14:38 -0500639 return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
640 }
pineafan4edb7762022-06-26 19:21:04 +0100641}
642
pineafan6fb3e072022-05-20 19:27:23 +0100643export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100644 id: string;
645 version: number;
PineaFan100df682023-01-02 13:26:08 +0000646 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100647 filters: {
648 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100649 NSFW: boolean;
650 size: boolean;
651 };
652 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100653 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100654 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100655 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100656 strict: string[];
657 loose: string[];
658 };
pineafan6fb3e072022-05-20 19:27:23 +0100659 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100660 users: string[];
661 roles: string[];
662 channels: string[];
663 };
664 };
pineafan6fb3e072022-05-20 19:27:23 +0100665 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100666 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000667 allowed: {
668 channels: string[];
669 roles: string[];
670 users: string[];
671 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100672 };
pineafan6fb3e072022-05-20 19:27:23 +0100673 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100674 mass: number;
675 everyone: boolean;
676 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100677 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100678 roles: string[];
679 rolesToMention: string[];
680 users: string[];
681 channels: string[];
682 };
683 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500684 clean: {
685 channels: string[];
686 allowed: {
687 user: string[];
688 roles: string[];
689 }
690 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100691 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500692 autoPublish: {
693 enabled: boolean;
694 channels: string[];
695 }
pineafan6fb3e072022-05-20 19:27:23 +0100696 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100697 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100698 role: string | null;
699 ping: string | null;
700 channel: string | null;
701 message: string | null;
702 };
703 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100704 logging: {
705 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100706 enabled: boolean;
707 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100708 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100709 };
pineafan6fb3e072022-05-20 19:27:23 +0100710 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100711 channel: string | null;
712 };
pineafan73a7c4a2022-07-24 10:38:04 +0100713 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100714 channel: string | null;
715 saved: Record<string, string>;
716 };
717 };
pineafan6fb3e072022-05-20 19:27:23 +0100718 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000719 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100720 role: string | null;
721 };
pineafan6fb3e072022-05-20 19:27:23 +0100722 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100723 enabled: boolean;
724 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100725 types: string;
726 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100727 useCustom: boolean;
728 supportRole: string | null;
729 maxTickets: number;
730 };
pineafan6fb3e072022-05-20 19:27:23 +0100731 moderation: {
732 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100733 timeout: boolean;
734 role: string | null;
735 text: string | null;
736 link: string | null;
737 };
pineafan6fb3e072022-05-20 19:27:23 +0100738 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100739 text: string | null;
740 link: string | null;
741 };
pineafan6fb3e072022-05-20 19:27:23 +0100742 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100743 text: string | null;
744 link: string | null;
745 };
pineafan6fb3e072022-05-20 19:27:23 +0100746 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100747 text: string | null;
748 link: string | null;
749 };
pineafan6fb3e072022-05-20 19:27:23 +0100750 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100751 text: string | null;
752 link: string | null;
753 };
pineafan6fb3e072022-05-20 19:27:23 +0100754 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100755 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500756 text: null;
757 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100758 };
PineaFane6ba7882023-01-18 20:41:16 +0000759 nick: {
760 text: string | null;
761 link: string | null;
762 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100763 };
pineafan6fb3e072022-05-20 19:27:23 +0100764 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100765 name: string;
766 retainPrevious: boolean;
767 nullable: boolean;
768 track: string[];
769 manageableBy: string[];
770 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100771 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100772 enabled: boolean;
773 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100774 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100775 name: string;
776 description: string;
777 min: number;
778 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100779 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100780 name: string;
781 description: string | null;
782 role: string;
783 }[];
784 }[];
785 };
786 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100787}
pineafan4edb7762022-06-26 19:21:04 +0100788
789export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100790 type: string;
791 guild: string;
792 user: string;
793 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100794 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100795 occurredAt: Date;
796 before: string | null;
797 after: string | null;
798 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100799}
800
801export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100802 guild: string;
803 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100804 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100805}
806
pineafan73a7c4a2022-07-24 10:38:04 +0100807export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100808 user: string;
809 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100810 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -0500811 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100812}