blob: 96cd06860ed93eff863182dfdca24c7497959abe [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
Skyler Grey2e13b6b2023-06-14 19:59:56 +020024 ? `mongodb://${username}:${password}@${config.mongoOptions.host}/${config.mongoOptions.database}` +
25 `?authMechanism=DEFAULT&authSource=${config.mongoOptions.authSource}`
pineafan6de4da52023-03-07 20:43:44 +000026 : `mongodb://${config.mongoOptions.host}`
Skyler Greyda16adf2023-03-05 10:22:12 +000027);
pineafan63fc5e22022-08-04 22:04:10 +010028await mongoClient.connect();
TheCodedProf3d54ade2023-04-22 21:40:42 -040029export const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010030
TheCodedProf78b90332023-03-04 14:02:21 -050031const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" };
TheCodedProf75c51be2023-03-03 17:18:18 -050032const getIV = () => crypto.randomBytes(16);
TheCodedProffaae5332023-03-01 18:16:05 -050033
pineafan4edb7762022-06-26 19:21:04 +010034export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010035 guilds: Collection<GuildConfig>;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050036 oldGuilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050037 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010038
39 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010040 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050041 this.defaultData = defaultData;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050042 this.oldGuilds = database.collection<GuildConfig>("oldGuilds");
43 }
44
45 async readOld(guild: string): Promise<Partial<GuildConfig>> {
46 // console.log("Guild read")
47 const entry = await this.oldGuilds.findOne({ id: guild });
48 return entry ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010049 }
50
TheCodedProfb7a7b992023-03-05 16:11:59 -050051 async updateAllGuilds() {
52 const guilds = await this.guilds.find().toArray();
53 for (const guild of guilds) {
54 let guildObj;
55 try {
56 guildObj = await client.guilds.fetch(guild.id);
57 } catch (e) {
58 guildObj = null;
59 }
Skyler Grey67691762023-03-06 09:58:19 +000060 if (!guildObj) await this.delete(guild.id);
TheCodedProfb7a7b992023-03-05 16:11:59 -050061 }
62 }
63
Skyler Greyad002172022-08-16 18:48:26 +010064 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050065 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010066 const entry = await this.guilds.findOne({ id: guild });
TheCodedProf9f4cf9f2023-03-04 14:18:19 -050067 const data = _.cloneDeep(this.defaultData);
TheCodedProff8ef7942023-03-03 15:32:32 -050068 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010069 }
70
Skyler Grey11236ba2022-08-08 21:13:33 +010071 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050072 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010073 // eslint-disable-next-line @typescript-eslint/no-explicit-any
74 const uo: Record<string, any> = {};
75 if (!Array.isArray(unset)) unset = [unset];
76 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010077 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010078 }
Skyler Grey75ea9172022-08-06 10:22:23 +010079 const out = { $set: {}, $unset: {} };
80 if (set) out.$set = set;
81 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010082 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010083 }
84
pineafan63fc5e22022-08-04 22:04:10 +010085 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010086 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050087 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010088 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010089 await this.guilds.updateOne(
90 { id: guild },
91 {
92 $addToSet: { [key]: { $each: value } }
93 },
94 { upsert: true }
95 );
pineafan6702cef2022-06-13 17:52:37 +010096 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010097 await this.guilds.updateOne(
98 { id: guild },
99 {
100 $addToSet: { [key]: value }
101 },
102 { upsert: true }
103 );
pineafan6702cef2022-06-13 17:52:37 +0100104 }
105 }
106
Skyler Grey75ea9172022-08-06 10:22:23 +0100107 async remove(
108 guild: string,
109 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +0100110 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +0100111 value: any,
112 innerKey?: string | null
113 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500114 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +0100115 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100116 await this.guilds.updateOne(
117 { id: guild },
118 {
119 $pull: { [key]: { [innerKey]: { $eq: value } } }
120 },
121 { upsert: true }
122 );
pineafan0bc04162022-07-25 17:22:26 +0100123 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100124 await this.guilds.updateOne(
125 { id: guild },
126 {
127 $pullAll: { [key]: value }
128 },
129 { upsert: true }
130 );
pineafan6702cef2022-06-13 17:52:37 +0100131 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100132 await this.guilds.updateOne(
133 { id: guild },
134 {
135 $pullAll: { [key]: [value] }
136 },
137 { upsert: true }
138 );
pineafan6702cef2022-06-13 17:52:37 +0100139 }
pineafan6fb3e072022-05-20 19:27:23 +0100140 }
pineafane23c4ec2022-07-27 21:56:27 +0100141
142 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500143 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100144 await this.guilds.deleteOne({ id: guild });
145 }
TheCodedProfa38cbb32023-03-11 17:22:25 -0500146
147 async staffChannels(): Promise<string[]> {
Skyler Grey6a0bab52023-03-15 00:10:26 +0000148 const entries = (
149 await this.guilds
150 .find(
151 { "logging.staff.channel": { $exists: true } },
152 { projection: { "logging.staff.channel": 1, _id: 0 } }
153 )
154 .toArray()
155 ).map((e) => e.logging.staff.channel);
TheCodedProfe4ca5142023-03-14 18:09:03 -0400156 const out: string[] = [];
157 for (const entry of entries) {
158 if (entry) out.push(entry);
159 }
160 return out;
TheCodedProfa38cbb32023-03-11 17:22:25 -0500161 }
pineafan6fb3e072022-05-20 19:27:23 +0100162}
163
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500164interface TranscriptEmbed {
165 title?: string;
166 description?: string;
167 fields?: {
168 name: string;
169 value: string;
170 inline: boolean;
171 }[];
172 footer?: {
173 text: string;
174 iconURL?: string;
175 };
TheCodedProffaae5332023-03-01 18:16:05 -0500176 color?: number;
177 timestamp?: string;
178 author?: {
179 name: string;
180 iconURL?: string;
181 url?: string;
182 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500183}
184
185interface TranscriptComponent {
186 type: number;
187 style?: ButtonStyle;
188 label?: string;
189 description?: string;
190 placeholder?: string;
191 emojiURL?: string;
192}
193
194interface TranscriptAuthor {
195 username: string;
Skyler Grey0df04eb2023-05-29 18:40:56 +0200196 discriminator: string | undefined;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500197 nickname?: string;
198 id: string;
199 iconURL?: string;
200 topRole: {
201 color: number;
202 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500203 };
204 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500205}
206
207interface TranscriptAttachment {
208 url: string;
209 filename: string;
210 size: number;
211 log?: string;
212}
213
214interface TranscriptMessage {
215 id: string;
216 author: TranscriptAuthor;
217 content?: string;
218 embeds?: TranscriptEmbed[];
219 components?: TranscriptComponent[][];
220 editedTimestamp?: number;
221 createdTimestamp: number;
222 flags?: string[];
223 attachments?: TranscriptAttachment[];
224 stickerURLs?: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000225 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500226}
227
228interface TranscriptSchema {
229 code: string;
230 for: TranscriptAuthor;
Skyler Greyda16adf2023-03-05 10:22:12 +0000231 type: "ticket" | "purge";
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500232 guild: string;
233 channel: string;
234 messages: TranscriptMessage[];
235 createdTimestamp: number;
236 createdBy: TranscriptAuthor;
237}
238
Skyler Greyda16adf2023-03-05 10:22:12 +0000239interface findDocSchema {
240 channelID: string;
241 messageID: string;
Skyler Grey5b78b422023-03-07 22:36:20 +0000242 code: string;
Skyler Greyda16adf2023-03-05 10:22:12 +0000243}
TheCodedProf003160f2023-03-04 17:09:40 -0500244
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500245export class Transcript {
246 transcripts: Collection<TranscriptSchema>;
TheCodedProf003160f2023-03-04 17:09:40 -0500247 messageToTranscript: Collection<findDocSchema>;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500248
249 constructor() {
250 this.transcripts = database.collection<TranscriptSchema>("transcripts");
TheCodedProf003160f2023-03-04 17:09:40 -0500251 this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
252 }
253
254 async upload(data: findDocSchema) {
255 // console.log("Transcript upload")
256 await this.messageToTranscript.insertOne(data);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500257 }
258
259 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500260 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500261 let code;
262 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500263 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500264 } while (await this.transcripts.findOne({ code: code }));
Skyler Greyda16adf2023-03-05 10:22:12 +0000265 const key = crypto
266 .randomBytes(32 ** 2)
267 .toString("base64")
268 .replace(/=/g, "")
269 .replace(/\//g, "_")
270 .replace(/\+/g, "-")
271 .substring(0, 32);
Skyler Grey67691762023-03-06 09:58:19 +0000272 const iv = getIV()
273 .toString("base64")
274 .substring(0, 16)
275 .replace(/=/g, "")
276 .replace(/\//g, "_")
277 .replace(/\+/g, "-");
Skyler Greyda16adf2023-03-05 10:22:12 +0000278 for (const message of transcript.messages) {
279 if (message.content) {
TheCodedProf75c51be2023-03-03 17:18:18 -0500280 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
281 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
282 }
283 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500284
TheCodedProffaae5332023-03-01 18:16:05 -0500285 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Skyler Greyda16adf2023-03-05 10:22:12 +0000286 if (doc.acknowledged) {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000287 await client.database.eventScheduler.schedule(
Skyler Greyda16adf2023-03-05 10:22:12 +0000288 "deleteTranscript",
289 (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
290 { guild: transcript.guild, code: code, iv: iv, key: key }
291 );
TheCodedProf003160f2023-03-04 17:09:40 -0500292 return [code, key, iv];
Skyler Greyda16adf2023-03-05 10:22:12 +0000293 } else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500294 }
295
TheCodedProf003160f2023-03-04 17:09:40 -0500296 async delete(code: string) {
297 // console.log("Transcript delete")
298 await this.transcripts.deleteOne({ code: code });
TheCodedProf75c51be2023-03-03 17:18:18 -0500299 }
300
301 async deleteAll(guild: string) {
302 // console.log("Transcript delete")
303 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
304 for (const doc of filteredDocs) {
305 await this.transcripts.deleteOne({ code: doc.code });
306 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500307 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500308
TheCodedProf003160f2023-03-04 17:09:40 -0500309 async readEncrypted(code: string) {
310 // console.log("Transcript read")
311 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
312 let findDoc: findDocSchema | null = null;
pineafan6de4da52023-03-07 20:43:44 +0000313 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000314 if (findDoc) {
315 const message = await (
316 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
317 )?.messages.fetch(findDoc.messageID);
318 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500319 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000320 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500321 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000322 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500323 const reader = transcript.getReader();
324 let data: Uint8Array | null = null;
325 let allPacketsReceived = false;
326 while (!allPacketsReceived) {
327 const { value, done } = await reader.read();
Skyler Greyda16adf2023-03-05 10:22:12 +0000328 if (done) {
329 allPacketsReceived = true;
330 continue;
331 }
332 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500333 data = value;
334 } else {
335 data = new Uint8Array(Buffer.concat([data, value]));
336 }
337 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000338 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000339 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500340 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000341 if (!doc) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500342 return doc;
343 }
344
345 async read(code: string, key: string, iv: string) {
TheCodedProf003160f2023-03-04 17:09:40 -0500346 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
347 let findDoc: findDocSchema | null = null;
pineafan6de4da52023-03-07 20:43:44 +0000348 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000349 if (findDoc) {
350 const message = await (
351 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
352 )?.messages.fetch(findDoc.messageID);
353 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500354 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000355 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500356 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000357 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500358 const reader = transcript.getReader();
359 let data: Uint8Array | null = null;
360 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
Skyler Greyda16adf2023-03-05 10:22:12 +0000361 while (true) {
TheCodedProf003160f2023-03-04 17:09:40 -0500362 const { value, done } = await reader.read();
363 if (done) break;
Skyler Greyda16adf2023-03-05 10:22:12 +0000364 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500365 data = value;
366 } else {
367 data = new Uint8Array(Buffer.concat([data, value]));
368 }
369 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000370 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000371 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500372 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000373 if (!doc) return null;
374 for (const message of doc.messages) {
375 if (message.content) {
TheCodedProf003160f2023-03-04 17:09:40 -0500376 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
377 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
378 }
379 }
380 return doc;
381 }
382
Skyler Greyda16adf2023-03-05 10:22:12 +0000383 async createTranscript(
Skyler Greye0c511b2023-03-06 10:30:17 +0000384 type: "ticket" | "purge",
Skyler Greyda16adf2023-03-05 10:22:12 +0000385 messages: Message[],
386 interaction: MessageComponentInteraction | CommandInteraction,
387 member: GuildMember
388 ) {
389 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500390 const newOut: Omit<TranscriptSchema, "code"> = {
Skyler Greye0c511b2023-03-06 10:30:17 +0000391 type: type,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500392 for: {
393 username: member!.user.username,
Skyler Grey0df04eb2023-05-29 18:40:56 +0200394 discriminator: member!.user.discriminator === "0" ? undefined : member!.user.discriminator,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500395 id: member!.user.id,
396 topRole: {
397 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500398 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000399 iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500400 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500401 },
402 guild: interaction.guild!.id,
403 channel: interaction.channel!.id,
404 messages: [],
405 createdTimestamp: Date.now(),
406 createdBy: {
407 username: interaction.user.username,
Skyler Grey0df04eb2023-05-29 18:40:56 +0200408 discriminator: interaction.user.discriminator === "0" ? undefined : interaction.user.discriminator,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500409 id: interaction.user.id,
410 topRole: {
411 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500412 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000413 iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500414 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500415 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000416 };
417 if (member.nickname) newOut.for.nickname = member.nickname;
418 if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500419 messages.reverse().forEach((message) => {
420 const msg: TranscriptMessage = {
421 id: message.id,
422 author: {
423 username: message.author.username,
Skyler Grey0df04eb2023-05-29 18:40:56 +0200424 discriminator: message.author.discriminator === "0" ? undefined : message.author.discriminator,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500425 id: message.author.id,
426 topRole: {
Skyler Greya0c70242023-03-06 09:56:21 +0000427 color: message.member ? message.member.roles.highest.color : 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500428 },
TheCodedProfe92b9b52023-03-06 17:07:34 -0500429 iconURL: (message.member?.user ?? message.author).displayAvatarURL({ forceStatic: true }),
Skyler Grey67691762023-03-06 09:58:19 +0000430 bot: message.author.bot || false
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500431 },
432 createdTimestamp: message.createdTimestamp
433 };
Skyler Greyda16adf2023-03-05 10:22:12 +0000434 if (message.member?.nickname) msg.author.nickname = message.member.nickname;
Skyler Greya0c70242023-03-06 09:56:21 +0000435 if (message.member?.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500436 if (message.content) msg.content = message.content;
Skyler Greyda16adf2023-03-05 10:22:12 +0000437 if (message.embeds.length > 0)
438 msg.embeds = message.embeds.map((embed) => {
439 const obj: TranscriptEmbed = {};
440 if (embed.title) obj.title = embed.title;
441 if (embed.description) obj.description = embed.description;
442 if (embed.fields.length > 0)
443 obj.fields = embed.fields.map((field) => {
444 return {
445 name: field.name,
446 value: field.value,
447 inline: field.inline ?? false
448 };
449 });
450 if (embed.color) obj.color = embed.color;
451 if (embed.timestamp) obj.timestamp = embed.timestamp;
452 if (embed.footer)
453 obj.footer = {
454 text: embed.footer.text
455 };
456 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
457 if (embed.author)
458 obj.author = {
459 name: embed.author.name
460 };
461 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
462 if (embed.author?.url) obj.author!.url = embed.author.url;
463 return obj;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500464 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000465 if (message.components.length > 0)
466 msg.components = message.components.map((component) =>
467 component.components.map((child) => {
468 const obj: TranscriptComponent = {
469 type: child.type
470 };
471 if (child.type === ComponentType.Button) {
472 obj.style = child.style;
473 obj.label = child.label ?? "";
pineafan435a8782023-06-24 12:45:58 +0100474 } else if (
475 child.type === ComponentType.StringSelect ||
476 child.type === ComponentType.UserSelect ||
477 child.type === ComponentType.ChannelSelect ||
478 child.type === ComponentType.RoleSelect
479 ) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000480 obj.placeholder = child.placeholder ?? "";
481 }
482 return obj;
483 })
484 );
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500485 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
486 msg.flags = message.flags.toArray();
487
Skyler Greyda16adf2023-03-05 10:22:12 +0000488 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
489 if (message.reference)
490 msg.referencedMessage = [
491 message.reference.guildId ?? "",
492 message.reference.channelId,
493 message.reference.messageId ?? ""
494 ];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500495 newOut.messages.push(msg);
496 });
497 return newOut;
498 }
499
500 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
501 let out = "";
502 for (const message of transcript.messages) {
503 if (message.referencedMessage) {
504 if (Array.isArray(message.referencedMessage)) {
505 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
Skyler Greyda16adf2023-03-05 10:22:12 +0000506 } else out += `> [Reply To] ${message.referencedMessage}\n`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500507 }
pineafancfb149f2023-05-28 15:46:30 +0100508 out += `${message.author.nickname ?? message.author.username} (${message.author.id}) (${message.id})`;
TheCodedProff8ef7942023-03-03 15:32:32 -0500509 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
510 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500511 out += "\n";
512 if (message.content) out += `[Content]\n${message.content}\n\n`;
513 if (message.embeds) {
514 for (const embed of message.embeds) {
515 out += `[Embed]\n`;
516 if (embed.title) out += `| Title: ${embed.title}\n`;
517 if (embed.description) out += `| Description: ${embed.description}\n`;
518 if (embed.fields) {
519 for (const field of embed.fields) {
520 out += `| Field: ${field.name} - ${field.value}\n`;
521 }
522 }
523 if (embed.footer) {
524 out += `|Footer: ${embed.footer.text}\n`;
525 }
526 out += "\n";
527 }
528 }
529 if (message.components) {
530 for (const component of message.components) {
531 out += `[Component]\n`;
532 for (const button of component) {
533 out += `| Button: ${button.label ?? button.description}\n`;
534 }
535 out += "\n";
536 }
537 }
538 if (message.attachments) {
539 for (const attachment of message.attachments) {
540 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
541 }
542 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000543 out += "\n\n";
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500544 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000545 return out;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500546 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500547}
548
pineafan4edb7762022-06-26 19:21:04 +0100549export class History {
550 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100551
pineafan3a02ea32022-08-11 21:35:04 +0100552 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100553 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100554 }
555
Skyler Grey75ea9172022-08-06 10:22:23 +0100556 async create(
557 type: string,
558 guild: string,
559 user: Discord.User,
560 moderator: Discord.User | null,
561 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100562 before?: string | null,
563 after?: string | null,
564 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100565 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500566 // console.log("History create");
Skyler Greyda16adf2023-03-05 10:22:12 +0000567 await this.histories.insertOne(
568 {
569 type: type,
570 guild: guild,
571 user: user.id,
572 moderator: moderator ? moderator.id : null,
573 reason: reason,
574 occurredAt: new Date(),
575 before: before ?? null,
576 after: after ?? null,
577 amount: amount ?? null
578 },
579 collectionOptions
580 );
pineafan4edb7762022-06-26 19:21:04 +0100581 }
582
583 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500584 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100585 const entry = (await this.histories
586 .find({
587 guild: guild,
588 user: user,
589 occurredAt: {
590 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
591 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
592 }
593 })
594 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100595 return entry;
596 }
pineafane23c4ec2022-07-27 21:56:27 +0100597
598 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500599 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100600 await this.histories.deleteMany({ guild: guild });
601 }
pineafan4edb7762022-06-26 19:21:04 +0100602}
603
TheCodedProfb5e9d552023-01-29 15:43:26 -0500604interface ScanCacheSchema {
605 addedAt: Date;
606 hash: string;
Skyler Grey14432712023-03-07 23:40:50 +0000607 nsfw?: boolean;
608 malware?: boolean;
609 bad_link?: boolean;
Skyler Greyd1157312023-03-08 10:07:38 +0000610 tags?: string[];
TheCodedProfb5e9d552023-01-29 15:43:26 -0500611}
612
613export class ScanCache {
614 scanCache: Collection<ScanCacheSchema>;
615
616 constructor() {
617 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
618 }
619
620 async read(hash: string) {
621 return await this.scanCache.findOne({ hash: hash });
622 }
623
Skyler Grey14432712023-03-07 23:40:50 +0000624 async write(hash: string, type: "nsfw" | "malware" | "bad_link", data: boolean, tags?: string[]) {
TheCodedProf21d4b0f2023-04-22 21:02:51 -0400625 // await this.scanCache.insertOne(
626 // { hash: hash, [type]: data, tags: tags ?? [], addedAt: new Date() },
627 // collectionOptions
TheCodedProf3be348c2023-04-22 20:59:14 -0400628 // );
TheCodedProf21d4b0f2023-04-22 21:02:51 -0400629 await this.scanCache.updateOne(
630 { hash: hash },
631 {
632 $set: (() => {
633 switch (type) {
634 case "nsfw": {
635 return { nsfw: data, addedAt: new Date() };
636 }
637 case "malware": {
638 return { malware: data, addedAt: new Date() };
639 }
640 case "bad_link": {
641 return { bad_link: data, tags: tags ?? [], addedAt: new Date() };
642 }
643 default: {
644 throw new Error("Invalid type");
645 }
646 }
647 })()
648 // No you can't just do { [type]: data }, yes it's a typescript error, no I don't know how to fix it
649 // cleanly, yes it would be marginally more elegant, no it's not essential, yes I'd be happy to review
650 // PRs that did improve this snippet
651 // Made an attempt... Gave up... Just Leave It
652 // Counter: 2
653 },
654 Object.assign({ upsert: true }, collectionOptions)
655 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500656 }
657
658 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500659 // console.log("ScanCache cleanup");
Skyler Greyda16adf2023-03-05 10:22:12 +0000660 await this.scanCache.deleteMany({
661 addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
662 hash: { $not$text: "http" }
663 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500664 }
665}
666
PineaFan538d3752023-01-12 21:48:23 +0000667export class PerformanceTest {
668 performanceData: Collection<PerformanceDataSchema>;
669
670 constructor() {
671 this.performanceData = database.collection<PerformanceDataSchema>("performance");
672 }
673
674 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500675 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000676 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500677 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000678 }
679 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500680 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000681 return await this.performanceData.find({}).toArray();
682 }
683}
684
685export interface PerformanceDataSchema {
686 timestamp?: Date;
687 discord: number;
688 databaseRead: number;
689 resources: {
690 cpu: number;
691 memory: number;
692 temperature: number;
Skyler Greyda16adf2023-03-05 10:22:12 +0000693 };
PineaFan538d3752023-01-12 21:48:23 +0000694}
695
pineafan4edb7762022-06-26 19:21:04 +0100696export class ModNotes {
697 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100698
pineafan3a02ea32022-08-11 21:35:04 +0100699 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100700 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100701 }
702
TheCodedProfc016f9f2023-04-23 16:01:38 -0400703 async flag(guild: string, user: string, flag: FlagColors | null) {
704 const modNote = await this.modNotes.findOne({ guild: guild, user: user });
705 modNote
706 ? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { flag: flag } }, collectionOptions)
707 : await this.modNotes.insertOne({ guild: guild, user: user, note: null, flag: flag }, collectionOptions);
708 }
709
pineafan4edb7762022-06-26 19:21:04 +0100710 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500711 // console.log("ModNotes create");
TheCodedProfe49649c2023-04-23 14:31:00 -0400712 const modNote = await this.modNotes.findOne({ guild: guild, user: user });
713 modNote
714 ? await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, collectionOptions)
TheCodedProfc016f9f2023-04-23 16:01:38 -0400715 : await this.modNotes.insertOne({ guild: guild, user: user, note: note, flag: null }, collectionOptions);
pineafan4edb7762022-06-26 19:21:04 +0100716 }
717
718 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500719 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100720 const entry = await this.modNotes.findOne({ guild: guild, user: user });
TheCodedProfc016f9f2023-04-23 16:01:38 -0400721 return entry ?? null;
pineafan4edb7762022-06-26 19:21:04 +0100722 }
TheCodedProf267563a2023-01-21 17:00:57 -0500723
724 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500725 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500726 await this.modNotes.deleteMany({ guild: guild });
727 }
pineafan4edb7762022-06-26 19:21:04 +0100728}
729
pineafan73a7c4a2022-07-24 10:38:04 +0100730export class Premium {
731 premium: Collection<PremiumSchema>;
Skyler Greyda16adf2023-03-05 10:22:12 +0000732 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
733 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100734
pineafan3a02ea32022-08-11 21:35:04 +0100735 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100736 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500737 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100738 }
739
TheCodedProf633866f2023-02-03 17:06:00 -0500740 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500741 // console.log("Premium updateUser");
Skyler Greyda16adf2023-03-05 10:22:12 +0000742 if (!(await this.userExists(user))) await this.createUser(user, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500743 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
744 }
745
746 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500747 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500748 const entry = await this.premium.findOne({ user: user });
749 return entry ? true : false;
750 }
TheCodedProf633866f2023-02-03 17:06:00 -0500751 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500752 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500753 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500754 }
755
TheCodedProfaa3fe992023-02-25 21:53:09 -0500756 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500757 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500758 // [Has premium, user giving premium, level, is mod: if given automatically]
759 const cached = this.cache.get(guild);
760 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500761 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000762 const members = (await client.guilds.fetch(guild)).members.cache;
763 for (const { user } of entries) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500764 const member = members.get(user);
Skyler Greyda16adf2023-03-05 10:22:12 +0000765 if (member) {
766 //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 -0500767 const modPerms = //TODO: Create list in config for perms
Skyler Greyda16adf2023-03-05 10:22:12 +0000768 member.permissions.has("Administrator") ||
769 member.permissions.has("ManageChannels") ||
770 member.permissions.has("ManageRoles") ||
771 member.permissions.has("ManageEmojisAndStickers") ||
772 member.permissions.has("ManageWebhooks") ||
773 member.permissions.has("ManageGuild") ||
774 member.permissions.has("KickMembers") ||
775 member.permissions.has("BanMembers") ||
776 member.permissions.has("ManageEvents") ||
777 member.permissions.has("ManageMessages") ||
778 member.permissions.has("ManageThreads");
779 const entry = entries.find((e) => e.user === member.id);
780 if (entry && entry.level === 3 && modPerms) {
781 this.cache.set(guild, [
782 true,
783 member.id,
784 entry.level,
785 true,
786 new Date(Date.now() + this.cacheTimeout)
787 ]);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500788 return [true, member.id, entry.level, true];
789 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500790 }
791 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100792 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500793 appliesTo: {
794 $elemMatch: {
795 $eq: guild
796 }
797 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100798 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000799 this.cache.set(guild, [
800 entry ? true : false,
801 entry?.user ?? "",
802 entry?.level ?? 0,
803 false,
804 new Date(Date.now() + this.cacheTimeout)
805 ]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500806 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500807 }
808
TheCodedProf633866f2023-02-03 17:06:00 -0500809 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500810 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500811 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500812 if (!entry) return null;
813 return entry;
814 }
815
TheCodedProf94ff6de2023-02-22 17:47:26 -0500816 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500817 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500818 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000819 if (member) {
820 const entry = entries.find((e) => e.user === member.id);
821 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500822 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000823 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500824 }
825 const roles = member.roles;
826 let level = 0;
827 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500828 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500829 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500830 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500831 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500832 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500833 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500834 level = 3;
835 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500836 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500837 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000838 await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
TheCodedProf633866f2023-02-03 17:06:00 -0500839 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000840 await this.premium.updateOne(
841 { user: member.id },
842 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
843 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500844 }
845 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000846 const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
847 for (const { roles, id } of members.values()) {
848 const entry = entries.find((e) => e.user === id);
849 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500850 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000851 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500852 }
853 let level: number = 0;
854 if (roles.cache.has("1066468879309750313")) {
855 level = 99;
856 } else if (roles.cache.has("1066465491713003520")) {
857 level = 1;
858 } else if (roles.cache.has("1066439526496604194")) {
859 level = 2;
860 } else if (roles.cache.has("1066464134322978912")) {
861 level = 3;
862 }
863 await this.updateUser(id, level);
864 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000865 await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
TheCodedProf94ff6de2023-02-22 17:47:26 -0500866 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000867 await this.premium.updateOne(
868 { user: id },
869 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
870 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500871 }
TheCodedProf633866f2023-02-03 17:06:00 -0500872 }
873 }
TheCodedProf267563a2023-01-21 17:00:57 -0500874 }
875
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500876 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500877 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500878 const { level } = (await this.fetchUser(user))!;
879 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500880 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100881 }
TheCodedProffc420b72023-01-24 17:14:38 -0500882
TheCodedProf48865eb2023-03-05 15:25:25 -0500883 async removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500884 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500885 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf48865eb2023-03-05 15:25:25 -0500886 return await this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
TheCodedProffc420b72023-01-24 17:14:38 -0500887 }
pineafan4edb7762022-06-26 19:21:04 +0100888}
889
pineafan1e462ab2023-03-07 21:34:06 +0000890// export class Plugins {}
pineafan6de4da52023-03-07 20:43:44 +0000891
pineafan6fb3e072022-05-20 19:27:23 +0100892export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100893 id: string;
894 version: number;
PineaFan100df682023-01-02 13:26:08 +0000895 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100896 filters: {
897 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100898 NSFW: boolean;
899 size: boolean;
900 };
901 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100902 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100903 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100904 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100905 strict: string[];
906 loose: string[];
907 };
pineafan6fb3e072022-05-20 19:27:23 +0100908 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100909 users: string[];
910 roles: string[];
911 channels: string[];
912 };
913 };
pineafan6fb3e072022-05-20 19:27:23 +0100914 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100915 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000916 allowed: {
917 channels: string[];
918 roles: string[];
919 users: string[];
920 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100921 };
pineafan6fb3e072022-05-20 19:27:23 +0100922 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100923 mass: number;
924 everyone: boolean;
925 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100926 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100927 roles: string[];
928 rolesToMention: string[];
929 users: string[];
930 channels: string[];
931 };
932 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500933 clean: {
934 channels: string[];
935 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500936 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500937 roles: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000938 };
939 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100940 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500941 autoPublish: {
942 enabled: boolean;
943 channels: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000944 };
pineafan6fb3e072022-05-20 19:27:23 +0100945 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100946 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100947 role: string | null;
948 ping: string | null;
949 channel: string | null;
950 message: string | null;
951 };
952 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100953 logging: {
954 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100955 enabled: boolean;
956 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100957 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100958 };
pineafan6fb3e072022-05-20 19:27:23 +0100959 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100960 channel: string | null;
961 };
pineafan73a7c4a2022-07-24 10:38:04 +0100962 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100963 channel: string | null;
964 saved: Record<string, string>;
965 };
966 };
pineafan6fb3e072022-05-20 19:27:23 +0100967 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000968 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100969 role: string | null;
970 };
pineafan6fb3e072022-05-20 19:27:23 +0100971 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100972 enabled: boolean;
973 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100974 types: string;
975 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100976 useCustom: boolean;
977 supportRole: string | null;
978 maxTickets: number;
979 };
pineafan6fb3e072022-05-20 19:27:23 +0100980 moderation: {
981 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100982 timeout: boolean;
983 role: string | null;
984 text: string | null;
985 link: string | null;
986 };
pineafan6fb3e072022-05-20 19:27:23 +0100987 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100988 text: string | null;
989 link: string | null;
990 };
pineafan6fb3e072022-05-20 19:27:23 +0100991 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100992 text: string | null;
993 link: string | null;
994 };
pineafan6fb3e072022-05-20 19:27:23 +0100995 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100996 text: string | null;
997 link: string | null;
998 };
pineafan6fb3e072022-05-20 19:27:23 +0100999 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001000 text: string | null;
1001 link: string | null;
1002 };
pineafan6fb3e072022-05-20 19:27:23 +01001003 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001004 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -05001005 text: null;
1006 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +01001007 };
PineaFane6ba7882023-01-18 20:41:16 +00001008 nick: {
1009 text: string | null;
1010 link: string | null;
Skyler Greyda16adf2023-03-05 10:22:12 +00001011 };
Skyler Grey75ea9172022-08-06 10:22:23 +01001012 };
pineafan6fb3e072022-05-20 19:27:23 +01001013 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001014 name: string;
1015 retainPrevious: boolean;
1016 nullable: boolean;
1017 track: string[];
1018 manageableBy: string[];
1019 }[];
pineafan6fb3e072022-05-20 19:27:23 +01001020 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001021 enabled: boolean;
1022 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +01001023 options: {
TheCodedProf4a088b12023-06-06 15:29:59 -04001024 enabled?: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +01001025 name: string;
TheCodedProf4a088b12023-06-06 15:29:59 -04001026 description?: string;
Skyler Grey75ea9172022-08-06 10:22:23 +01001027 min: number;
1028 max: number;
pineafan6fb3e072022-05-20 19:27:23 +01001029 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +01001030 name: string;
1031 description: string | null;
1032 role: string;
1033 }[];
1034 }[];
1035 };
1036 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +01001037}
pineafan4edb7762022-06-26 19:21:04 +01001038
1039export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001040 type: string;
1041 guild: string;
1042 user: string;
1043 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +01001044 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +01001045 occurredAt: Date;
1046 before: string | null;
1047 after: string | null;
1048 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +01001049}
1050
TheCodedProfc016f9f2023-04-23 16:01:38 -04001051export type FlagColors = "red" | "yellow" | "green" | "blue" | "purple" | "gray";
1052
pineafan4edb7762022-06-26 19:21:04 +01001053export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001054 guild: string;
1055 user: string;
pineafan3a02ea32022-08-11 21:35:04 +01001056 note: string | null;
TheCodedProfc016f9f2023-04-23 16:01:38 -04001057 flag: FlagColors | null;
pineafan4edb7762022-06-26 19:21:04 +01001058}
1059
pineafan73a7c4a2022-07-24 10:38:04 +01001060export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001061 user: string;
1062 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001063 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -05001064 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001065}