blob: 8ad2fb38e2c7897e6c121dc9828482eaae776057 [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import {
2 ButtonStyle,
3 CommandInteraction,
4 ComponentType,
5 GuildMember,
6 Message,
7 MessageComponentInteraction
8} from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +01009import type Discord from "discord.js";
10import { Collection, MongoClient } from "mongodb";
pineafana2e39c72023-02-21 18:37:32 +000011import config from "../config/main.js";
TheCodedProf633866f2023-02-03 17:06:00 -050012import client from "../utils/client.js";
TheCodedProf088b1b22023-02-28 17:31:11 -050013import * as crypto from "crypto";
TheCodedProff8ef7942023-03-03 15:32:32 -050014import _ from "lodash";
Skyler Greyda16adf2023-03-05 10:22:12 +000015import defaultData from "../config/default.js";
TheCodedProf75276572023-03-04 13:49:16 -050016
pineafan6de4da52023-03-07 20:43:44 +000017let username, password;
18
Skyler Grey5b78b422023-03-07 22:36:20 +000019if ("username" in config.mongoOptions) username = encodeURIComponent(config.mongoOptions.username as string);
20if ("password" in config.mongoOptions) password = encodeURIComponent(config.mongoOptions.password as string);
Samuel Shuertd66098b2023-03-04 14:05:26 -050021
Skyler Greyda16adf2023-03-05 10:22:12 +000022const mongoClient = new MongoClient(
23 username
pineafan6de4da52023-03-07 20:43:44 +000024 ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT&authSource=${config.mongoOptions.authSource}`
25 : `mongodb://${config.mongoOptions.host}`
Skyler Greyda16adf2023-03-05 10:22:12 +000026);
pineafan63fc5e22022-08-04 22:04:10 +010027await mongoClient.connect();
TheCodedProf3d54ade2023-04-22 21:40:42 -040028export const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010029
TheCodedProf78b90332023-03-04 14:02:21 -050030const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" };
TheCodedProf75c51be2023-03-03 17:18:18 -050031const getIV = () => crypto.randomBytes(16);
TheCodedProffaae5332023-03-01 18:16:05 -050032
pineafan4edb7762022-06-26 19:21:04 +010033export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010034 guilds: Collection<GuildConfig>;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050035 oldGuilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050036 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010037
38 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010039 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050040 this.defaultData = defaultData;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050041 this.oldGuilds = database.collection<GuildConfig>("oldGuilds");
42 }
43
44 async readOld(guild: string): Promise<Partial<GuildConfig>> {
45 // console.log("Guild read")
46 const entry = await this.oldGuilds.findOne({ id: guild });
47 return entry ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010048 }
49
TheCodedProfb7a7b992023-03-05 16:11:59 -050050 async updateAllGuilds() {
51 const guilds = await this.guilds.find().toArray();
52 for (const guild of guilds) {
53 let guildObj;
54 try {
55 guildObj = await client.guilds.fetch(guild.id);
56 } catch (e) {
57 guildObj = null;
58 }
Skyler Grey67691762023-03-06 09:58:19 +000059 if (!guildObj) await this.delete(guild.id);
TheCodedProfb7a7b992023-03-05 16:11:59 -050060 }
61 }
62
Skyler Greyad002172022-08-16 18:48:26 +010063 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050064 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010065 const entry = await this.guilds.findOne({ id: guild });
TheCodedProf9f4cf9f2023-03-04 14:18:19 -050066 const data = _.cloneDeep(this.defaultData);
TheCodedProff8ef7942023-03-03 15:32:32 -050067 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010068 }
69
Skyler Grey11236ba2022-08-08 21:13:33 +010070 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050071 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010072 // eslint-disable-next-line @typescript-eslint/no-explicit-any
73 const uo: Record<string, any> = {};
74 if (!Array.isArray(unset)) unset = [unset];
75 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010076 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010077 }
Skyler Grey75ea9172022-08-06 10:22:23 +010078 const out = { $set: {}, $unset: {} };
79 if (set) out.$set = set;
80 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010081 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010082 }
83
pineafan63fc5e22022-08-04 22:04:10 +010084 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010085 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050086 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010087 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010088 await this.guilds.updateOne(
89 { id: guild },
90 {
91 $addToSet: { [key]: { $each: 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 $addToSet: { [key]: value }
100 },
101 { upsert: true }
102 );
pineafan6702cef2022-06-13 17:52:37 +0100103 }
104 }
105
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 async remove(
107 guild: string,
108 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +0100109 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +0100110 value: any,
111 innerKey?: string | null
112 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500113 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +0100114 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100115 await this.guilds.updateOne(
116 { id: guild },
117 {
118 $pull: { [key]: { [innerKey]: { $eq: value } } }
119 },
120 { upsert: true }
121 );
pineafan0bc04162022-07-25 17:22:26 +0100122 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100123 await this.guilds.updateOne(
124 { id: guild },
125 {
126 $pullAll: { [key]: value }
127 },
128 { upsert: true }
129 );
pineafan6702cef2022-06-13 17:52:37 +0100130 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100131 await this.guilds.updateOne(
132 { id: guild },
133 {
134 $pullAll: { [key]: [value] }
135 },
136 { upsert: true }
137 );
pineafan6702cef2022-06-13 17:52:37 +0100138 }
pineafan6fb3e072022-05-20 19:27:23 +0100139 }
pineafane23c4ec2022-07-27 21:56:27 +0100140
141 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500142 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100143 await this.guilds.deleteOne({ id: guild });
144 }
TheCodedProfa38cbb32023-03-11 17:22:25 -0500145
146 async staffChannels(): Promise<string[]> {
Skyler Grey6a0bab52023-03-15 00:10:26 +0000147 const entries = (
148 await this.guilds
149 .find(
150 { "logging.staff.channel": { $exists: true } },
151 { projection: { "logging.staff.channel": 1, _id: 0 } }
152 )
153 .toArray()
154 ).map((e) => e.logging.staff.channel);
TheCodedProfe4ca5142023-03-14 18:09:03 -0400155 const out: string[] = [];
156 for (const entry of entries) {
157 if (entry) out.push(entry);
158 }
159 return out;
TheCodedProfa38cbb32023-03-11 17:22:25 -0500160 }
pineafan6fb3e072022-05-20 19:27:23 +0100161}
162
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500163interface TranscriptEmbed {
164 title?: string;
165 description?: string;
166 fields?: {
167 name: string;
168 value: string;
169 inline: boolean;
170 }[];
171 footer?: {
172 text: string;
173 iconURL?: string;
174 };
TheCodedProffaae5332023-03-01 18:16:05 -0500175 color?: number;
176 timestamp?: string;
177 author?: {
178 name: string;
179 iconURL?: string;
180 url?: string;
181 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500182}
183
184interface TranscriptComponent {
185 type: number;
186 style?: ButtonStyle;
187 label?: string;
188 description?: string;
189 placeholder?: string;
190 emojiURL?: string;
191}
192
193interface TranscriptAuthor {
194 username: string;
Skyler Grey0df04eb2023-05-29 18:40:56 +0200195 discriminator: string | undefined;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500196 nickname?: string;
197 id: string;
198 iconURL?: string;
199 topRole: {
200 color: number;
201 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500202 };
203 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500204}
205
206interface TranscriptAttachment {
207 url: string;
208 filename: string;
209 size: number;
210 log?: string;
211}
212
213interface TranscriptMessage {
214 id: string;
215 author: TranscriptAuthor;
216 content?: string;
217 embeds?: TranscriptEmbed[];
218 components?: TranscriptComponent[][];
219 editedTimestamp?: number;
220 createdTimestamp: number;
221 flags?: string[];
222 attachments?: TranscriptAttachment[];
223 stickerURLs?: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000224 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500225}
226
227interface TranscriptSchema {
228 code: string;
229 for: TranscriptAuthor;
Skyler Greyda16adf2023-03-05 10:22:12 +0000230 type: "ticket" | "purge";
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500231 guild: string;
232 channel: string;
233 messages: TranscriptMessage[];
234 createdTimestamp: number;
235 createdBy: TranscriptAuthor;
236}
237
Skyler Greyda16adf2023-03-05 10:22:12 +0000238interface findDocSchema {
239 channelID: string;
240 messageID: string;
Skyler Grey5b78b422023-03-07 22:36:20 +0000241 code: string;
Skyler Greyda16adf2023-03-05 10:22:12 +0000242}
TheCodedProf003160f2023-03-04 17:09:40 -0500243
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500244export class Transcript {
245 transcripts: Collection<TranscriptSchema>;
TheCodedProf003160f2023-03-04 17:09:40 -0500246 messageToTranscript: Collection<findDocSchema>;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500247
248 constructor() {
249 this.transcripts = database.collection<TranscriptSchema>("transcripts");
TheCodedProf003160f2023-03-04 17:09:40 -0500250 this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
251 }
252
253 async upload(data: findDocSchema) {
254 // console.log("Transcript upload")
255 await this.messageToTranscript.insertOne(data);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500256 }
257
258 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500259 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500260 let code;
261 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500262 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500263 } while (await this.transcripts.findOne({ code: code }));
Skyler Greyda16adf2023-03-05 10:22:12 +0000264 const key = crypto
265 .randomBytes(32 ** 2)
266 .toString("base64")
267 .replace(/=/g, "")
268 .replace(/\//g, "_")
269 .replace(/\+/g, "-")
270 .substring(0, 32);
Skyler Grey67691762023-03-06 09:58:19 +0000271 const iv = getIV()
272 .toString("base64")
273 .substring(0, 16)
274 .replace(/=/g, "")
275 .replace(/\//g, "_")
276 .replace(/\+/g, "-");
Skyler Greyda16adf2023-03-05 10:22:12 +0000277 for (const message of transcript.messages) {
278 if (message.content) {
TheCodedProf75c51be2023-03-03 17:18:18 -0500279 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
280 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
281 }
282 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500283
TheCodedProffaae5332023-03-01 18:16:05 -0500284 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Skyler Greyda16adf2023-03-05 10:22:12 +0000285 if (doc.acknowledged) {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000286 await client.database.eventScheduler.schedule(
Skyler Greyda16adf2023-03-05 10:22:12 +0000287 "deleteTranscript",
288 (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
289 { guild: transcript.guild, code: code, iv: iv, key: key }
290 );
TheCodedProf003160f2023-03-04 17:09:40 -0500291 return [code, key, iv];
Skyler Greyda16adf2023-03-05 10:22:12 +0000292 } else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500293 }
294
TheCodedProf003160f2023-03-04 17:09:40 -0500295 async delete(code: string) {
296 // console.log("Transcript delete")
297 await this.transcripts.deleteOne({ code: code });
TheCodedProf75c51be2023-03-03 17:18:18 -0500298 }
299
300 async deleteAll(guild: string) {
301 // console.log("Transcript delete")
302 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
303 for (const doc of filteredDocs) {
304 await this.transcripts.deleteOne({ code: doc.code });
305 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500306 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500307
TheCodedProf003160f2023-03-04 17:09:40 -0500308 async readEncrypted(code: string) {
309 // console.log("Transcript read")
310 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
311 let findDoc: findDocSchema | null = null;
pineafan6de4da52023-03-07 20:43:44 +0000312 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000313 if (findDoc) {
314 const message = await (
315 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
316 )?.messages.fetch(findDoc.messageID);
317 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500318 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000319 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500320 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000321 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500322 const reader = transcript.getReader();
323 let data: Uint8Array | null = null;
324 let allPacketsReceived = false;
325 while (!allPacketsReceived) {
326 const { value, done } = await reader.read();
Skyler Greyda16adf2023-03-05 10:22:12 +0000327 if (done) {
328 allPacketsReceived = true;
329 continue;
330 }
331 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500332 data = value;
333 } else {
334 data = new Uint8Array(Buffer.concat([data, value]));
335 }
336 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000337 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000338 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500339 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000340 if (!doc) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500341 return doc;
342 }
343
344 async read(code: string, key: string, iv: string) {
TheCodedProf003160f2023-03-04 17:09:40 -0500345 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
346 let findDoc: findDocSchema | null = null;
pineafan6de4da52023-03-07 20:43:44 +0000347 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000348 if (findDoc) {
349 const message = await (
350 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
351 )?.messages.fetch(findDoc.messageID);
352 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500353 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000354 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500355 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000356 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500357 const reader = transcript.getReader();
358 let data: Uint8Array | null = null;
359 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
Skyler Greyda16adf2023-03-05 10:22:12 +0000360 while (true) {
TheCodedProf003160f2023-03-04 17:09:40 -0500361 const { value, done } = await reader.read();
362 if (done) break;
Skyler Greyda16adf2023-03-05 10:22:12 +0000363 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500364 data = value;
365 } else {
366 data = new Uint8Array(Buffer.concat([data, value]));
367 }
368 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000369 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000370 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500371 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000372 if (!doc) return null;
373 for (const message of doc.messages) {
374 if (message.content) {
TheCodedProf003160f2023-03-04 17:09:40 -0500375 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
376 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
377 }
378 }
379 return doc;
380 }
381
Skyler Greyda16adf2023-03-05 10:22:12 +0000382 async createTranscript(
Skyler Greye0c511b2023-03-06 10:30:17 +0000383 type: "ticket" | "purge",
Skyler Greyda16adf2023-03-05 10:22:12 +0000384 messages: Message[],
385 interaction: MessageComponentInteraction | CommandInteraction,
386 member: GuildMember
387 ) {
388 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500389 const newOut: Omit<TranscriptSchema, "code"> = {
Skyler Greye0c511b2023-03-06 10:30:17 +0000390 type: type,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500391 for: {
392 username: member!.user.username,
Skyler Grey0df04eb2023-05-29 18:40:56 +0200393 discriminator: member!.user.discriminator === "0" ? undefined : member!.user.discriminator,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500394 id: member!.user.id,
395 topRole: {
396 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500397 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000398 iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500399 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500400 },
401 guild: interaction.guild!.id,
402 channel: interaction.channel!.id,
403 messages: [],
404 createdTimestamp: Date.now(),
405 createdBy: {
406 username: interaction.user.username,
Skyler Grey0df04eb2023-05-29 18:40:56 +0200407 discriminator: interaction.user.discriminator === "0" ? undefined : interaction.user.discriminator,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500408 id: interaction.user.id,
409 topRole: {
410 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500411 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000412 iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500413 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500414 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000415 };
416 if (member.nickname) newOut.for.nickname = member.nickname;
417 if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500418 messages.reverse().forEach((message) => {
419 const msg: TranscriptMessage = {
420 id: message.id,
421 author: {
422 username: message.author.username,
Skyler Grey0df04eb2023-05-29 18:40:56 +0200423 discriminator: message.author.discriminator === "0" ? undefined : message.author.discriminator,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500424 id: message.author.id,
425 topRole: {
Skyler Greya0c70242023-03-06 09:56:21 +0000426 color: message.member ? message.member.roles.highest.color : 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500427 },
TheCodedProfe92b9b52023-03-06 17:07:34 -0500428 iconURL: (message.member?.user ?? message.author).displayAvatarURL({ forceStatic: true }),
Skyler Grey67691762023-03-06 09:58:19 +0000429 bot: message.author.bot || false
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500430 },
431 createdTimestamp: message.createdTimestamp
432 };
Skyler Greyda16adf2023-03-05 10:22:12 +0000433 if (message.member?.nickname) msg.author.nickname = message.member.nickname;
Skyler Greya0c70242023-03-06 09:56:21 +0000434 if (message.member?.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500435 if (message.content) msg.content = message.content;
Skyler Greyda16adf2023-03-05 10:22:12 +0000436 if (message.embeds.length > 0)
437 msg.embeds = message.embeds.map((embed) => {
438 const obj: TranscriptEmbed = {};
439 if (embed.title) obj.title = embed.title;
440 if (embed.description) obj.description = embed.description;
441 if (embed.fields.length > 0)
442 obj.fields = embed.fields.map((field) => {
443 return {
444 name: field.name,
445 value: field.value,
446 inline: field.inline ?? false
447 };
448 });
449 if (embed.color) obj.color = embed.color;
450 if (embed.timestamp) obj.timestamp = embed.timestamp;
451 if (embed.footer)
452 obj.footer = {
453 text: embed.footer.text
454 };
455 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
456 if (embed.author)
457 obj.author = {
458 name: embed.author.name
459 };
460 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
461 if (embed.author?.url) obj.author!.url = embed.author.url;
462 return obj;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500463 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000464 if (message.components.length > 0)
465 msg.components = message.components.map((component) =>
466 component.components.map((child) => {
467 const obj: TranscriptComponent = {
468 type: child.type
469 };
470 if (child.type === ComponentType.Button) {
471 obj.style = child.style;
472 obj.label = child.label ?? "";
Skyler Greyf96a3372023-06-14 19:08:46 +0200473 } else if (child.type > 2) {
474 // FIXME: Can we write this more clearly to make it obvious what we mean by 2 here?
Skyler Greyda16adf2023-03-05 10:22:12 +0000475 obj.placeholder = child.placeholder ?? "";
476 }
477 return obj;
478 })
479 );
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500480 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
481 msg.flags = message.flags.toArray();
482
Skyler Greyda16adf2023-03-05 10:22:12 +0000483 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
484 if (message.reference)
485 msg.referencedMessage = [
486 message.reference.guildId ?? "",
487 message.reference.channelId,
488 message.reference.messageId ?? ""
489 ];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500490 newOut.messages.push(msg);
491 });
492 return newOut;
493 }
494
495 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
496 let out = "";
497 for (const message of transcript.messages) {
498 if (message.referencedMessage) {
499 if (Array.isArray(message.referencedMessage)) {
500 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
Skyler Greyda16adf2023-03-05 10:22:12 +0000501 } else out += `> [Reply To] ${message.referencedMessage}\n`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500502 }
pineafancfb149f2023-05-28 15:46:30 +0100503 out += `${message.author.nickname ?? message.author.username} (${message.author.id}) (${message.id})`;
TheCodedProff8ef7942023-03-03 15:32:32 -0500504 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
505 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500506 out += "\n";
507 if (message.content) out += `[Content]\n${message.content}\n\n`;
508 if (message.embeds) {
509 for (const embed of message.embeds) {
510 out += `[Embed]\n`;
511 if (embed.title) out += `| Title: ${embed.title}\n`;
512 if (embed.description) out += `| Description: ${embed.description}\n`;
513 if (embed.fields) {
514 for (const field of embed.fields) {
515 out += `| Field: ${field.name} - ${field.value}\n`;
516 }
517 }
518 if (embed.footer) {
519 out += `|Footer: ${embed.footer.text}\n`;
520 }
521 out += "\n";
522 }
523 }
524 if (message.components) {
525 for (const component of message.components) {
526 out += `[Component]\n`;
527 for (const button of component) {
528 out += `| Button: ${button.label ?? button.description}\n`;
529 }
530 out += "\n";
531 }
532 }
533 if (message.attachments) {
534 for (const attachment of message.attachments) {
535 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
536 }
537 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000538 out += "\n\n";
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500539 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000540 return out;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500541 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500542}
543
pineafan4edb7762022-06-26 19:21:04 +0100544export class History {
545 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100546
pineafan3a02ea32022-08-11 21:35:04 +0100547 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100548 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100549 }
550
Skyler Grey75ea9172022-08-06 10:22:23 +0100551 async create(
552 type: string,
553 guild: string,
554 user: Discord.User,
555 moderator: Discord.User | null,
556 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100557 before?: string | null,
558 after?: string | null,
559 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100560 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500561 // console.log("History create");
Skyler Greyda16adf2023-03-05 10:22:12 +0000562 await this.histories.insertOne(
563 {
564 type: type,
565 guild: guild,
566 user: user.id,
567 moderator: moderator ? moderator.id : null,
568 reason: reason,
569 occurredAt: new Date(),
570 before: before ?? null,
571 after: after ?? null,
572 amount: amount ?? null
573 },
574 collectionOptions
575 );
pineafan4edb7762022-06-26 19:21:04 +0100576 }
577
578 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500579 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100580 const entry = (await this.histories
581 .find({
582 guild: guild,
583 user: user,
584 occurredAt: {
585 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
586 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
587 }
588 })
589 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100590 return entry;
591 }
pineafane23c4ec2022-07-27 21:56:27 +0100592
593 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500594 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100595 await this.histories.deleteMany({ guild: guild });
596 }
pineafan4edb7762022-06-26 19:21:04 +0100597}
598
TheCodedProfb5e9d552023-01-29 15:43:26 -0500599interface ScanCacheSchema {
600 addedAt: Date;
601 hash: string;
Skyler Grey14432712023-03-07 23:40:50 +0000602 nsfw?: boolean;
603 malware?: boolean;
604 bad_link?: boolean;
Skyler Greyd1157312023-03-08 10:07:38 +0000605 tags?: string[];
TheCodedProfb5e9d552023-01-29 15:43:26 -0500606}
607
608export class ScanCache {
609 scanCache: Collection<ScanCacheSchema>;
610
611 constructor() {
612 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
613 }
614
615 async read(hash: string) {
616 return await this.scanCache.findOne({ hash: hash });
617 }
618
Skyler Grey14432712023-03-07 23:40:50 +0000619 async write(hash: string, type: "nsfw" | "malware" | "bad_link", data: boolean, tags?: string[]) {
TheCodedProf21d4b0f2023-04-22 21:02:51 -0400620 // await this.scanCache.insertOne(
621 // { hash: hash, [type]: data, tags: tags ?? [], addedAt: new Date() },
622 // collectionOptions
TheCodedProf3be348c2023-04-22 20:59:14 -0400623 // );
TheCodedProf21d4b0f2023-04-22 21:02:51 -0400624 await this.scanCache.updateOne(
625 { hash: hash },
626 {
627 $set: (() => {
628 switch (type) {
629 case "nsfw": {
630 return { nsfw: data, addedAt: new Date() };
631 }
632 case "malware": {
633 return { malware: data, addedAt: new Date() };
634 }
635 case "bad_link": {
636 return { bad_link: data, tags: tags ?? [], addedAt: new Date() };
637 }
638 default: {
639 throw new Error("Invalid type");
640 }
641 }
642 })()
643 // No you can't just do { [type]: data }, yes it's a typescript error, no I don't know how to fix it
644 // cleanly, yes it would be marginally more elegant, no it's not essential, yes I'd be happy to review
645 // PRs that did improve this snippet
646 // Made an attempt... Gave up... Just Leave It
647 // Counter: 2
648 },
649 Object.assign({ upsert: true }, collectionOptions)
650 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500651 }
652
653 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500654 // console.log("ScanCache cleanup");
Skyler Greyda16adf2023-03-05 10:22:12 +0000655 await this.scanCache.deleteMany({
656 addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
657 hash: { $not$text: "http" }
658 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500659 }
660}
661
PineaFan538d3752023-01-12 21:48:23 +0000662export class PerformanceTest {
663 performanceData: Collection<PerformanceDataSchema>;
664
665 constructor() {
666 this.performanceData = database.collection<PerformanceDataSchema>("performance");
667 }
668
669 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500670 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000671 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500672 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000673 }
674 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500675 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000676 return await this.performanceData.find({}).toArray();
677 }
678}
679
680export interface PerformanceDataSchema {
681 timestamp?: Date;
682 discord: number;
683 databaseRead: number;
684 resources: {
685 cpu: number;
686 memory: number;
687 temperature: number;
Skyler Greyda16adf2023-03-05 10:22:12 +0000688 };
PineaFan538d3752023-01-12 21:48:23 +0000689}
690
pineafan4edb7762022-06-26 19:21:04 +0100691export class ModNotes {
692 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100693
pineafan3a02ea32022-08-11 21:35:04 +0100694 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100695 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100696 }
697
TheCodedProfc016f9f2023-04-23 16:01:38 -0400698 async flag(guild: string, user: string, flag: FlagColors | null) {
699 const modNote = await this.modNotes.findOne({ guild: guild, user: user });
700 modNote
701 ? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { flag: flag } }, collectionOptions)
702 : await this.modNotes.insertOne({ guild: guild, user: user, note: null, flag: flag }, collectionOptions);
703 }
704
pineafan4edb7762022-06-26 19:21:04 +0100705 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500706 // console.log("ModNotes create");
TheCodedProfe49649c2023-04-23 14:31:00 -0400707 const modNote = await this.modNotes.findOne({ guild: guild, user: user });
708 modNote
709 ? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, collectionOptions)
TheCodedProfc016f9f2023-04-23 16:01:38 -0400710 : await this.modNotes.insertOne({ guild: guild, user: user, note: note, flag: null }, collectionOptions);
pineafan4edb7762022-06-26 19:21:04 +0100711 }
712
713 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500714 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100715 const entry = await this.modNotes.findOne({ guild: guild, user: user });
TheCodedProfc016f9f2023-04-23 16:01:38 -0400716 return entry ?? null;
pineafan4edb7762022-06-26 19:21:04 +0100717 }
TheCodedProf267563a2023-01-21 17:00:57 -0500718
719 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500720 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500721 await this.modNotes.deleteMany({ guild: guild });
722 }
pineafan4edb7762022-06-26 19:21:04 +0100723}
724
pineafan73a7c4a2022-07-24 10:38:04 +0100725export class Premium {
726 premium: Collection<PremiumSchema>;
Skyler Greyda16adf2023-03-05 10:22:12 +0000727 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
728 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100729
pineafan3a02ea32022-08-11 21:35:04 +0100730 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100731 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500732 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100733 }
734
TheCodedProf633866f2023-02-03 17:06:00 -0500735 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500736 // console.log("Premium updateUser");
Skyler Greyda16adf2023-03-05 10:22:12 +0000737 if (!(await this.userExists(user))) await this.createUser(user, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500738 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
739 }
740
741 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500742 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500743 const entry = await this.premium.findOne({ user: user });
744 return entry ? true : false;
745 }
TheCodedProf633866f2023-02-03 17:06:00 -0500746 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500747 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500748 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500749 }
750
TheCodedProfaa3fe992023-02-25 21:53:09 -0500751 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500752 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500753 // [Has premium, user giving premium, level, is mod: if given automatically]
754 const cached = this.cache.get(guild);
755 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500756 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000757 const members = (await client.guilds.fetch(guild)).members.cache;
758 for (const { user } of entries) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500759 const member = members.get(user);
Skyler Greyda16adf2023-03-05 10:22:12 +0000760 if (member) {
761 //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 -0500762 const modPerms = //TODO: Create list in config for perms
Skyler Greyda16adf2023-03-05 10:22:12 +0000763 member.permissions.has("Administrator") ||
764 member.permissions.has("ManageChannels") ||
765 member.permissions.has("ManageRoles") ||
766 member.permissions.has("ManageEmojisAndStickers") ||
767 member.permissions.has("ManageWebhooks") ||
768 member.permissions.has("ManageGuild") ||
769 member.permissions.has("KickMembers") ||
770 member.permissions.has("BanMembers") ||
771 member.permissions.has("ManageEvents") ||
772 member.permissions.has("ManageMessages") ||
773 member.permissions.has("ManageThreads");
774 const entry = entries.find((e) => e.user === member.id);
775 if (entry && entry.level === 3 && modPerms) {
776 this.cache.set(guild, [
777 true,
778 member.id,
779 entry.level,
780 true,
781 new Date(Date.now() + this.cacheTimeout)
782 ]);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500783 return [true, member.id, entry.level, true];
784 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500785 }
786 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100787 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500788 appliesTo: {
789 $elemMatch: {
790 $eq: guild
791 }
792 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100793 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000794 this.cache.set(guild, [
795 entry ? true : false,
796 entry?.user ?? "",
797 entry?.level ?? 0,
798 false,
799 new Date(Date.now() + this.cacheTimeout)
800 ]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500801 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500802 }
803
TheCodedProf633866f2023-02-03 17:06:00 -0500804 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500805 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500806 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500807 if (!entry) return null;
808 return entry;
809 }
810
TheCodedProf94ff6de2023-02-22 17:47:26 -0500811 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500812 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500813 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000814 if (member) {
815 const entry = entries.find((e) => e.user === member.id);
816 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500817 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000818 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500819 }
820 const roles = member.roles;
821 let level = 0;
822 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500823 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500824 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500825 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500826 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500827 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500828 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500829 level = 3;
830 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500831 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500832 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000833 await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
TheCodedProf633866f2023-02-03 17:06:00 -0500834 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000835 await this.premium.updateOne(
836 { user: member.id },
837 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
838 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500839 }
840 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000841 const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
842 for (const { roles, id } of members.values()) {
843 const entry = entries.find((e) => e.user === id);
844 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500845 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000846 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500847 }
848 let level: number = 0;
849 if (roles.cache.has("1066468879309750313")) {
850 level = 99;
851 } else if (roles.cache.has("1066465491713003520")) {
852 level = 1;
853 } else if (roles.cache.has("1066439526496604194")) {
854 level = 2;
855 } else if (roles.cache.has("1066464134322978912")) {
856 level = 3;
857 }
858 await this.updateUser(id, level);
859 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000860 await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
TheCodedProf94ff6de2023-02-22 17:47:26 -0500861 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000862 await this.premium.updateOne(
863 { user: id },
864 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
865 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500866 }
TheCodedProf633866f2023-02-03 17:06:00 -0500867 }
868 }
TheCodedProf267563a2023-01-21 17:00:57 -0500869 }
870
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500871 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500872 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500873 const { level } = (await this.fetchUser(user))!;
874 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500875 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100876 }
TheCodedProffc420b72023-01-24 17:14:38 -0500877
TheCodedProf48865eb2023-03-05 15:25:25 -0500878 async removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500879 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500880 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf48865eb2023-03-05 15:25:25 -0500881 return await this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
TheCodedProffc420b72023-01-24 17:14:38 -0500882 }
pineafan4edb7762022-06-26 19:21:04 +0100883}
884
pineafan1e462ab2023-03-07 21:34:06 +0000885// export class Plugins {}
pineafan6de4da52023-03-07 20:43:44 +0000886
pineafan6fb3e072022-05-20 19:27:23 +0100887export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100888 id: string;
889 version: number;
PineaFan100df682023-01-02 13:26:08 +0000890 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100891 filters: {
892 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100893 NSFW: boolean;
894 size: boolean;
895 };
896 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100897 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100898 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100899 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100900 strict: string[];
901 loose: string[];
902 };
pineafan6fb3e072022-05-20 19:27:23 +0100903 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100904 users: string[];
905 roles: string[];
906 channels: string[];
907 };
908 };
pineafan6fb3e072022-05-20 19:27:23 +0100909 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100910 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000911 allowed: {
912 channels: string[];
913 roles: string[];
914 users: string[];
915 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100916 };
pineafan6fb3e072022-05-20 19:27:23 +0100917 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100918 mass: number;
919 everyone: boolean;
920 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100921 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100922 roles: string[];
923 rolesToMention: string[];
924 users: string[];
925 channels: string[];
926 };
927 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500928 clean: {
929 channels: string[];
930 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500931 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500932 roles: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000933 };
934 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100935 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500936 autoPublish: {
937 enabled: boolean;
938 channels: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000939 };
pineafan6fb3e072022-05-20 19:27:23 +0100940 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100941 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100942 role: string | null;
943 ping: string | null;
944 channel: string | null;
945 message: string | null;
946 };
947 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100948 logging: {
949 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100950 enabled: boolean;
951 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100952 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100953 };
pineafan6fb3e072022-05-20 19:27:23 +0100954 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100955 channel: string | null;
956 };
pineafan73a7c4a2022-07-24 10:38:04 +0100957 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100958 channel: string | null;
959 saved: Record<string, string>;
960 };
961 };
pineafan6fb3e072022-05-20 19:27:23 +0100962 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000963 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100964 role: string | null;
965 };
pineafan6fb3e072022-05-20 19:27:23 +0100966 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100967 enabled: boolean;
968 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100969 types: string;
970 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100971 useCustom: boolean;
972 supportRole: string | null;
973 maxTickets: number;
974 };
pineafan6fb3e072022-05-20 19:27:23 +0100975 moderation: {
976 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100977 timeout: boolean;
978 role: string | null;
979 text: string | null;
980 link: string | null;
981 };
pineafan6fb3e072022-05-20 19:27:23 +0100982 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100983 text: string | null;
984 link: string | null;
985 };
pineafan6fb3e072022-05-20 19:27:23 +0100986 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100987 text: string | null;
988 link: string | null;
989 };
pineafan6fb3e072022-05-20 19:27:23 +0100990 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100991 text: string | null;
992 link: string | null;
993 };
pineafan6fb3e072022-05-20 19:27:23 +0100994 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100995 text: string | null;
996 link: string | null;
997 };
pineafan6fb3e072022-05-20 19:27:23 +0100998 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100999 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -05001000 text: null;
1001 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +01001002 };
PineaFane6ba7882023-01-18 20:41:16 +00001003 nick: {
1004 text: string | null;
1005 link: string | null;
Skyler Greyda16adf2023-03-05 10:22:12 +00001006 };
Skyler Grey75ea9172022-08-06 10:22:23 +01001007 };
pineafan6fb3e072022-05-20 19:27:23 +01001008 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001009 name: string;
1010 retainPrevious: boolean;
1011 nullable: boolean;
1012 track: string[];
1013 manageableBy: string[];
1014 }[];
pineafan6fb3e072022-05-20 19:27:23 +01001015 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001016 enabled: boolean;
1017 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +01001018 options: {
TheCodedProf4a088b12023-06-06 15:29:59 -04001019 enabled?: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +01001020 name: string;
TheCodedProf4a088b12023-06-06 15:29:59 -04001021 description?: string;
Skyler Grey75ea9172022-08-06 10:22:23 +01001022 min: number;
1023 max: number;
pineafan6fb3e072022-05-20 19:27:23 +01001024 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001025 name: string;
1026 description: string | null;
1027 role: string;
1028 }[];
1029 }[];
1030 };
1031 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +01001032}
pineafan4edb7762022-06-26 19:21:04 +01001033
1034export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001035 type: string;
1036 guild: string;
1037 user: string;
1038 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +01001039 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +01001040 occurredAt: Date;
1041 before: string | null;
1042 after: string | null;
1043 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +01001044}
1045
TheCodedProfc016f9f2023-04-23 16:01:38 -04001046export type FlagColors = "red" | "yellow" | "green" | "blue" | "purple" | "gray";
1047
pineafan4edb7762022-06-26 19:21:04 +01001048export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001049 guild: string;
1050 user: string;
pineafan3a02ea32022-08-11 21:35:04 +01001051 note: string | null;
TheCodedProfc016f9f2023-04-23 16:01:38 -04001052 flag: FlagColors | null;
pineafan4edb7762022-06-26 19:21:04 +01001053}
1054
pineafan73a7c4a2022-07-24 10:38:04 +01001055export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001056 user: string;
1057 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001058 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -05001059 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001060}