blob: b6864b87892bac1b06a4796b1e3eb3ffb95c8349 [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
TheCodedProffaae5332023-03-01 18:16:05 -050017const username = encodeURIComponent(config.mongoOptions.username);
18const password = encodeURIComponent(config.mongoOptions.password);
Samuel Shuertd66098b2023-03-04 14:05:26 -050019
Skyler Greyda16adf2023-03-05 10:22:12 +000020const mongoClient = new MongoClient(
21 username
22 ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT`
23 : `mongodb://${config.mongoOptions.host}`,
24 { authSource: config.mongoOptions.authSource }
25);
pineafan63fc5e22022-08-04 22:04:10 +010026await mongoClient.connect();
TheCodedProf8d577fa2023-03-01 13:06:40 -050027const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010028
TheCodedProf78b90332023-03-04 14:02:21 -050029const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" };
TheCodedProf75c51be2023-03-03 17:18:18 -050030const getIV = () => crypto.randomBytes(16);
TheCodedProffaae5332023-03-01 18:16:05 -050031
pineafan4edb7762022-06-26 19:21:04 +010032export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010033 guilds: Collection<GuildConfig>;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050034 oldGuilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050035 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010036
37 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010038 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050039 this.defaultData = defaultData;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050040 this.oldGuilds = database.collection<GuildConfig>("oldGuilds");
41 }
42
43 async readOld(guild: string): Promise<Partial<GuildConfig>> {
44 // console.log("Guild read")
45 const entry = await this.oldGuilds.findOne({ id: guild });
46 return entry ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010047 }
48
Skyler Greyad002172022-08-16 18:48:26 +010049 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050050 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010051 const entry = await this.guilds.findOne({ id: guild });
TheCodedProf9f4cf9f2023-03-04 14:18:19 -050052 const data = _.cloneDeep(this.defaultData);
TheCodedProff8ef7942023-03-03 15:32:32 -050053 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010054 }
55
Skyler Grey11236ba2022-08-08 21:13:33 +010056 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050057 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010058 // eslint-disable-next-line @typescript-eslint/no-explicit-any
59 const uo: Record<string, any> = {};
60 if (!Array.isArray(unset)) unset = [unset];
61 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010062 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010063 }
Skyler Grey75ea9172022-08-06 10:22:23 +010064 const out = { $set: {}, $unset: {} };
65 if (set) out.$set = set;
66 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010067 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010068 }
69
pineafan63fc5e22022-08-04 22:04:10 +010070 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010071 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050072 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010073 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010074 await this.guilds.updateOne(
75 { id: guild },
76 {
77 $addToSet: { [key]: { $each: value } }
78 },
79 { upsert: true }
80 );
pineafan6702cef2022-06-13 17:52:37 +010081 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010082 await this.guilds.updateOne(
83 { id: guild },
84 {
85 $addToSet: { [key]: value }
86 },
87 { upsert: true }
88 );
pineafan6702cef2022-06-13 17:52:37 +010089 }
90 }
91
Skyler Grey75ea9172022-08-06 10:22:23 +010092 async remove(
93 guild: string,
94 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010095 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010096 value: any,
97 innerKey?: string | null
98 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -050099 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +0100100 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100101 await this.guilds.updateOne(
102 { id: guild },
103 {
104 $pull: { [key]: { [innerKey]: { $eq: value } } }
105 },
106 { upsert: true }
107 );
pineafan0bc04162022-07-25 17:22:26 +0100108 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100109 await this.guilds.updateOne(
110 { id: guild },
111 {
112 $pullAll: { [key]: value }
113 },
114 { upsert: true }
115 );
pineafan6702cef2022-06-13 17:52:37 +0100116 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100117 await this.guilds.updateOne(
118 { id: guild },
119 {
120 $pullAll: { [key]: [value] }
121 },
122 { upsert: true }
123 );
pineafan6702cef2022-06-13 17:52:37 +0100124 }
pineafan6fb3e072022-05-20 19:27:23 +0100125 }
pineafane23c4ec2022-07-27 21:56:27 +0100126
127 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500128 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100129 await this.guilds.deleteOne({ id: guild });
130 }
pineafan6fb3e072022-05-20 19:27:23 +0100131}
132
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500133interface TranscriptEmbed {
134 title?: string;
135 description?: string;
136 fields?: {
137 name: string;
138 value: string;
139 inline: boolean;
140 }[];
141 footer?: {
142 text: string;
143 iconURL?: string;
144 };
TheCodedProffaae5332023-03-01 18:16:05 -0500145 color?: number;
146 timestamp?: string;
147 author?: {
148 name: string;
149 iconURL?: string;
150 url?: string;
151 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500152}
153
154interface TranscriptComponent {
155 type: number;
156 style?: ButtonStyle;
157 label?: string;
158 description?: string;
159 placeholder?: string;
160 emojiURL?: string;
161}
162
163interface TranscriptAuthor {
164 username: string;
165 discriminator: number;
166 nickname?: string;
167 id: string;
168 iconURL?: string;
169 topRole: {
170 color: number;
171 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500172 };
173 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500174}
175
176interface TranscriptAttachment {
177 url: string;
178 filename: string;
179 size: number;
180 log?: string;
181}
182
183interface TranscriptMessage {
184 id: string;
185 author: TranscriptAuthor;
186 content?: string;
187 embeds?: TranscriptEmbed[];
188 components?: TranscriptComponent[][];
189 editedTimestamp?: number;
190 createdTimestamp: number;
191 flags?: string[];
192 attachments?: TranscriptAttachment[];
193 stickerURLs?: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000194 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500195}
196
197interface TranscriptSchema {
198 code: string;
199 for: TranscriptAuthor;
Skyler Greyda16adf2023-03-05 10:22:12 +0000200 type: "ticket" | "purge";
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500201 guild: string;
202 channel: string;
203 messages: TranscriptMessage[];
204 createdTimestamp: number;
205 createdBy: TranscriptAuthor;
206}
207
Skyler Greyda16adf2023-03-05 10:22:12 +0000208interface findDocSchema {
209 channelID: string;
210 messageID: string;
211 transcript: string;
212}
TheCodedProf003160f2023-03-04 17:09:40 -0500213
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500214export class Transcript {
215 transcripts: Collection<TranscriptSchema>;
TheCodedProf003160f2023-03-04 17:09:40 -0500216 messageToTranscript: Collection<findDocSchema>;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500217
218 constructor() {
219 this.transcripts = database.collection<TranscriptSchema>("transcripts");
TheCodedProf003160f2023-03-04 17:09:40 -0500220 this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
221 }
222
223 async upload(data: findDocSchema) {
224 // console.log("Transcript upload")
225 await this.messageToTranscript.insertOne(data);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500226 }
227
228 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500229 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500230 let code;
231 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500232 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500233 } while (await this.transcripts.findOne({ code: code }));
Skyler Greyda16adf2023-03-05 10:22:12 +0000234 const key = crypto
235 .randomBytes(32 ** 2)
236 .toString("base64")
237 .replace(/=/g, "")
238 .replace(/\//g, "_")
239 .replace(/\+/g, "-")
240 .substring(0, 32);
TheCodedProf75c51be2023-03-03 17:18:18 -0500241 const iv = getIV().toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
Skyler Greyda16adf2023-03-05 10:22:12 +0000242 for (const message of transcript.messages) {
243 if (message.content) {
TheCodedProf75c51be2023-03-03 17:18:18 -0500244 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
245 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
246 }
247 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500248
TheCodedProffaae5332023-03-01 18:16:05 -0500249 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Skyler Greyda16adf2023-03-05 10:22:12 +0000250 if (doc.acknowledged) {
251 client.database.eventScheduler.schedule(
252 "deleteTranscript",
253 (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
254 { guild: transcript.guild, code: code, iv: iv, key: key }
255 );
TheCodedProf003160f2023-03-04 17:09:40 -0500256 return [code, key, iv];
Skyler Greyda16adf2023-03-05 10:22:12 +0000257 } else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500258 }
259
TheCodedProf003160f2023-03-04 17:09:40 -0500260 async delete(code: string) {
261 // console.log("Transcript delete")
262 await this.transcripts.deleteOne({ code: code });
TheCodedProf75c51be2023-03-03 17:18:18 -0500263 }
264
265 async deleteAll(guild: string) {
266 // console.log("Transcript delete")
267 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
268 for (const doc of filteredDocs) {
269 await this.transcripts.deleteOne({ code: doc.code });
270 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500271 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500272
TheCodedProf003160f2023-03-04 17:09:40 -0500273 async readEncrypted(code: string) {
274 // console.log("Transcript read")
275 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
276 let findDoc: findDocSchema | null = null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000277 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
278 if (findDoc) {
279 const message = await (
280 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
281 )?.messages.fetch(findDoc.messageID);
282 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500283 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000284 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500285 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000286 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500287 const reader = transcript.getReader();
288 let data: Uint8Array | null = null;
289 let allPacketsReceived = false;
290 while (!allPacketsReceived) {
291 const { value, done } = await reader.read();
Skyler Greyda16adf2023-03-05 10:22:12 +0000292 if (done) {
293 allPacketsReceived = true;
294 continue;
295 }
296 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500297 data = value;
298 } else {
299 data = new Uint8Array(Buffer.concat([data, value]));
300 }
301 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000302 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000303 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500304 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000305 if (!doc) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500306 return doc;
307 }
308
309 async read(code: string, key: string, iv: string) {
310 // console.log("Transcript read")
311 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
312 let findDoc: findDocSchema | null = null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000313 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
314 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 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
Skyler Greyda16adf2023-03-05 10:22:12 +0000326 while (true) {
TheCodedProf003160f2023-03-04 17:09:40 -0500327 const { value, done } = await reader.read();
328 if (done) break;
Skyler Greyda16adf2023-03-05 10:22:12 +0000329 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500330 data = value;
331 } else {
332 data = new Uint8Array(Buffer.concat([data, value]));
333 }
334 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000335 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000336 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500337 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000338 if (!doc) return null;
339 for (const message of doc.messages) {
340 if (message.content) {
TheCodedProf003160f2023-03-04 17:09:40 -0500341 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
342 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
343 }
344 }
345 return doc;
346 }
347
Skyler Greyda16adf2023-03-05 10:22:12 +0000348 async createTranscript(
349 messages: Message[],
350 interaction: MessageComponentInteraction | CommandInteraction,
351 member: GuildMember
352 ) {
353 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500354 const newOut: Omit<TranscriptSchema, "code"> = {
355 type: "ticket",
356 for: {
357 username: member!.user.username,
358 discriminator: parseInt(member!.user.discriminator),
359 id: member!.user.id,
360 topRole: {
361 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500362 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000363 iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500364 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500365 },
366 guild: interaction.guild!.id,
367 channel: interaction.channel!.id,
368 messages: [],
369 createdTimestamp: Date.now(),
370 createdBy: {
371 username: interaction.user.username,
372 discriminator: parseInt(interaction.user.discriminator),
373 id: interaction.user.id,
374 topRole: {
375 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500376 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000377 iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500378 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500379 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000380 };
381 if (member.nickname) newOut.for.nickname = member.nickname;
382 if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500383 messages.reverse().forEach((message) => {
384 const msg: TranscriptMessage = {
385 id: message.id,
386 author: {
387 username: message.author.username,
388 discriminator: parseInt(message.author.discriminator),
389 id: message.author.id,
390 topRole: {
391 color: message.member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500392 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000393 iconURL: message.member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500394 bot: message.author.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500395 },
396 createdTimestamp: message.createdTimestamp
397 };
Skyler Greyda16adf2023-03-05 10:22:12 +0000398 if (message.member?.nickname) msg.author.nickname = message.member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500399 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
400 if (message.content) msg.content = message.content;
Skyler Greyda16adf2023-03-05 10:22:12 +0000401 if (message.embeds.length > 0)
402 msg.embeds = message.embeds.map((embed) => {
403 const obj: TranscriptEmbed = {};
404 if (embed.title) obj.title = embed.title;
405 if (embed.description) obj.description = embed.description;
406 if (embed.fields.length > 0)
407 obj.fields = embed.fields.map((field) => {
408 return {
409 name: field.name,
410 value: field.value,
411 inline: field.inline ?? false
412 };
413 });
414 if (embed.color) obj.color = embed.color;
415 if (embed.timestamp) obj.timestamp = embed.timestamp;
416 if (embed.footer)
417 obj.footer = {
418 text: embed.footer.text
419 };
420 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
421 if (embed.author)
422 obj.author = {
423 name: embed.author.name
424 };
425 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
426 if (embed.author?.url) obj.author!.url = embed.author.url;
427 return obj;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500428 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000429 if (message.components.length > 0)
430 msg.components = message.components.map((component) =>
431 component.components.map((child) => {
432 const obj: TranscriptComponent = {
433 type: child.type
434 };
435 if (child.type === ComponentType.Button) {
436 obj.style = child.style;
437 obj.label = child.label ?? "";
438 } else if (child.type > 2) {
439 obj.placeholder = child.placeholder ?? "";
440 }
441 return obj;
442 })
443 );
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500444 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
445 msg.flags = message.flags.toArray();
446
Skyler Greyda16adf2023-03-05 10:22:12 +0000447 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
448 if (message.reference)
449 msg.referencedMessage = [
450 message.reference.guildId ?? "",
451 message.reference.channelId,
452 message.reference.messageId ?? ""
453 ];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500454 newOut.messages.push(msg);
455 });
456 return newOut;
457 }
458
459 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
460 let out = "";
461 for (const message of transcript.messages) {
462 if (message.referencedMessage) {
463 if (Array.isArray(message.referencedMessage)) {
464 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
Skyler Greyda16adf2023-03-05 10:22:12 +0000465 } else out += `> [Reply To] ${message.referencedMessage}\n`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500466 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000467 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
468 message.author.id
469 }) (${message.id})`;
TheCodedProff8ef7942023-03-03 15:32:32 -0500470 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
471 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500472 out += "\n";
473 if (message.content) out += `[Content]\n${message.content}\n\n`;
474 if (message.embeds) {
475 for (const embed of message.embeds) {
476 out += `[Embed]\n`;
477 if (embed.title) out += `| Title: ${embed.title}\n`;
478 if (embed.description) out += `| Description: ${embed.description}\n`;
479 if (embed.fields) {
480 for (const field of embed.fields) {
481 out += `| Field: ${field.name} - ${field.value}\n`;
482 }
483 }
484 if (embed.footer) {
485 out += `|Footer: ${embed.footer.text}\n`;
486 }
487 out += "\n";
488 }
489 }
490 if (message.components) {
491 for (const component of message.components) {
492 out += `[Component]\n`;
493 for (const button of component) {
494 out += `| Button: ${button.label ?? button.description}\n`;
495 }
496 out += "\n";
497 }
498 }
499 if (message.attachments) {
500 for (const attachment of message.attachments) {
501 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
502 }
503 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000504 out += "\n\n";
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500505 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000506 return out;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500507 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500508}
509
pineafan4edb7762022-06-26 19:21:04 +0100510export class History {
511 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100512
pineafan3a02ea32022-08-11 21:35:04 +0100513 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100514 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100515 }
516
Skyler Grey75ea9172022-08-06 10:22:23 +0100517 async create(
518 type: string,
519 guild: string,
520 user: Discord.User,
521 moderator: Discord.User | null,
522 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100523 before?: string | null,
524 after?: string | null,
525 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100526 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500527 // console.log("History create");
Skyler Greyda16adf2023-03-05 10:22:12 +0000528 await this.histories.insertOne(
529 {
530 type: type,
531 guild: guild,
532 user: user.id,
533 moderator: moderator ? moderator.id : null,
534 reason: reason,
535 occurredAt: new Date(),
536 before: before ?? null,
537 after: after ?? null,
538 amount: amount ?? null
539 },
540 collectionOptions
541 );
pineafan4edb7762022-06-26 19:21:04 +0100542 }
543
544 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500545 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100546 const entry = (await this.histories
547 .find({
548 guild: guild,
549 user: user,
550 occurredAt: {
551 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
552 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
553 }
554 })
555 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100556 return entry;
557 }
pineafane23c4ec2022-07-27 21:56:27 +0100558
559 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500560 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100561 await this.histories.deleteMany({ guild: guild });
562 }
pineafan4edb7762022-06-26 19:21:04 +0100563}
564
TheCodedProfb5e9d552023-01-29 15:43:26 -0500565interface ScanCacheSchema {
566 addedAt: Date;
567 hash: string;
568 data: boolean;
569 tags: string[];
570}
571
572export class ScanCache {
573 scanCache: Collection<ScanCacheSchema>;
574
575 constructor() {
576 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
577 }
578
579 async read(hash: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500580 // console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500581 return await this.scanCache.findOne({ hash: hash });
582 }
583
584 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500585 // console.log("ScanCache write");
Skyler Greyda16adf2023-03-05 10:22:12 +0000586 await this.scanCache.insertOne(
587 { hash: hash, data: data, tags: tags ?? [], addedAt: new Date() },
588 collectionOptions
589 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500590 }
591
592 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500593 // console.log("ScanCache cleanup");
Skyler Greyda16adf2023-03-05 10:22:12 +0000594 await this.scanCache.deleteMany({
595 addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
596 hash: { $not$text: "http" }
597 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500598 }
599}
600
PineaFan538d3752023-01-12 21:48:23 +0000601export class PerformanceTest {
602 performanceData: Collection<PerformanceDataSchema>;
603
604 constructor() {
605 this.performanceData = database.collection<PerformanceDataSchema>("performance");
606 }
607
608 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500609 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000610 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500611 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000612 }
613 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500614 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000615 return await this.performanceData.find({}).toArray();
616 }
617}
618
619export interface PerformanceDataSchema {
620 timestamp?: Date;
621 discord: number;
622 databaseRead: number;
623 resources: {
624 cpu: number;
625 memory: number;
626 temperature: number;
Skyler Greyda16adf2023-03-05 10:22:12 +0000627 };
PineaFan538d3752023-01-12 21:48:23 +0000628}
629
pineafan4edb7762022-06-26 19:21:04 +0100630export class ModNotes {
631 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100632
pineafan3a02ea32022-08-11 21:35:04 +0100633 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100634 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100635 }
636
637 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500638 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100639 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100640 }
641
642 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500643 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100644 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100645 return entry?.note ?? null;
646 }
TheCodedProf267563a2023-01-21 17:00:57 -0500647
648 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500649 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500650 await this.modNotes.deleteMany({ guild: guild });
651 }
pineafan4edb7762022-06-26 19:21:04 +0100652}
653
pineafan73a7c4a2022-07-24 10:38:04 +0100654export class Premium {
655 premium: Collection<PremiumSchema>;
Skyler Greyda16adf2023-03-05 10:22:12 +0000656 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
657 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100658
pineafan3a02ea32022-08-11 21:35:04 +0100659 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100660 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500661 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100662 }
663
TheCodedProf633866f2023-02-03 17:06:00 -0500664 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500665 // console.log("Premium updateUser");
Skyler Greyda16adf2023-03-05 10:22:12 +0000666 if (!(await this.userExists(user))) await this.createUser(user, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500667 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
668 }
669
670 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500671 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500672 const entry = await this.premium.findOne({ user: user });
673 return entry ? true : false;
674 }
TheCodedProf633866f2023-02-03 17:06:00 -0500675 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500676 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500677 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500678 }
679
TheCodedProfaa3fe992023-02-25 21:53:09 -0500680 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500681 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500682 // [Has premium, user giving premium, level, is mod: if given automatically]
683 const cached = this.cache.get(guild);
684 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500685 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000686 const members = (await client.guilds.fetch(guild)).members.cache;
687 for (const { user } of entries) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500688 const member = members.get(user);
Skyler Greyda16adf2023-03-05 10:22:12 +0000689 if (member) {
690 //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 -0500691 const modPerms = //TODO: Create list in config for perms
Skyler Greyda16adf2023-03-05 10:22:12 +0000692 member.permissions.has("Administrator") ||
693 member.permissions.has("ManageChannels") ||
694 member.permissions.has("ManageRoles") ||
695 member.permissions.has("ManageEmojisAndStickers") ||
696 member.permissions.has("ManageWebhooks") ||
697 member.permissions.has("ManageGuild") ||
698 member.permissions.has("KickMembers") ||
699 member.permissions.has("BanMembers") ||
700 member.permissions.has("ManageEvents") ||
701 member.permissions.has("ManageMessages") ||
702 member.permissions.has("ManageThreads");
703 const entry = entries.find((e) => e.user === member.id);
704 if (entry && entry.level === 3 && modPerms) {
705 this.cache.set(guild, [
706 true,
707 member.id,
708 entry.level,
709 true,
710 new Date(Date.now() + this.cacheTimeout)
711 ]);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500712 return [true, member.id, entry.level, true];
713 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500714 }
715 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100716 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500717 appliesTo: {
718 $elemMatch: {
719 $eq: guild
720 }
721 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100722 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000723 this.cache.set(guild, [
724 entry ? true : false,
725 entry?.user ?? "",
726 entry?.level ?? 0,
727 false,
728 new Date(Date.now() + this.cacheTimeout)
729 ]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500730 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500731 }
732
TheCodedProf633866f2023-02-03 17:06:00 -0500733 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500734 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500735 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500736 if (!entry) return null;
737 return entry;
738 }
739
TheCodedProf94ff6de2023-02-22 17:47:26 -0500740 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500741 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500742 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000743 if (member) {
744 const entry = entries.find((e) => e.user === member.id);
745 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500746 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000747 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500748 }
749 const roles = member.roles;
750 let level = 0;
751 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500752 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500753 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500754 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500755 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500756 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500757 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500758 level = 3;
759 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500760 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500761 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000762 await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
TheCodedProf633866f2023-02-03 17:06:00 -0500763 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000764 await this.premium.updateOne(
765 { user: member.id },
766 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
767 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500768 }
769 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000770 const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
771 for (const { roles, id } of members.values()) {
772 const entry = entries.find((e) => e.user === id);
773 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500774 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000775 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500776 }
777 let level: number = 0;
778 if (roles.cache.has("1066468879309750313")) {
779 level = 99;
780 } else if (roles.cache.has("1066465491713003520")) {
781 level = 1;
782 } else if (roles.cache.has("1066439526496604194")) {
783 level = 2;
784 } else if (roles.cache.has("1066464134322978912")) {
785 level = 3;
786 }
787 await this.updateUser(id, level);
788 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000789 await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
TheCodedProf94ff6de2023-02-22 17:47:26 -0500790 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000791 await this.premium.updateOne(
792 { user: id },
793 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
794 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500795 }
TheCodedProf633866f2023-02-03 17:06:00 -0500796 }
797 }
TheCodedProf267563a2023-01-21 17:00:57 -0500798 }
799
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500800 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500801 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500802 const { level } = (await this.fetchUser(user))!;
803 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500804 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100805 }
TheCodedProffc420b72023-01-24 17:14:38 -0500806
TheCodedProf48865eb2023-03-05 15:25:25 -0500807 async removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500808 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500809 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf48865eb2023-03-05 15:25:25 -0500810 return await this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
TheCodedProffc420b72023-01-24 17:14:38 -0500811 }
pineafan4edb7762022-06-26 19:21:04 +0100812}
813
pineafan6fb3e072022-05-20 19:27:23 +0100814export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100815 id: string;
816 version: number;
PineaFan100df682023-01-02 13:26:08 +0000817 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100818 filters: {
819 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100820 NSFW: boolean;
821 size: boolean;
822 };
823 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100824 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100825 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100826 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100827 strict: string[];
828 loose: string[];
829 };
pineafan6fb3e072022-05-20 19:27:23 +0100830 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100831 users: string[];
832 roles: string[];
833 channels: string[];
834 };
835 };
pineafan6fb3e072022-05-20 19:27:23 +0100836 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100837 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000838 allowed: {
839 channels: string[];
840 roles: string[];
841 users: string[];
842 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100843 };
pineafan6fb3e072022-05-20 19:27:23 +0100844 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100845 mass: number;
846 everyone: boolean;
847 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100848 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100849 roles: string[];
850 rolesToMention: string[];
851 users: string[];
852 channels: string[];
853 };
854 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500855 clean: {
856 channels: string[];
857 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500858 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500859 roles: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000860 };
861 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100862 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500863 autoPublish: {
864 enabled: boolean;
865 channels: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000866 };
pineafan6fb3e072022-05-20 19:27:23 +0100867 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100868 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100869 role: string | null;
870 ping: string | null;
871 channel: string | null;
872 message: string | null;
873 };
874 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100875 logging: {
876 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100877 enabled: boolean;
878 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100879 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100880 };
pineafan6fb3e072022-05-20 19:27:23 +0100881 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100882 channel: string | null;
883 };
pineafan73a7c4a2022-07-24 10:38:04 +0100884 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100885 channel: string | null;
886 saved: Record<string, string>;
887 };
888 };
pineafan6fb3e072022-05-20 19:27:23 +0100889 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000890 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100891 role: string | null;
892 };
pineafan6fb3e072022-05-20 19:27:23 +0100893 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100894 enabled: boolean;
895 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100896 types: string;
897 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100898 useCustom: boolean;
899 supportRole: string | null;
900 maxTickets: number;
901 };
pineafan6fb3e072022-05-20 19:27:23 +0100902 moderation: {
903 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100904 timeout: boolean;
905 role: string | null;
906 text: string | null;
907 link: string | null;
908 };
pineafan6fb3e072022-05-20 19:27:23 +0100909 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100910 text: string | null;
911 link: string | null;
912 };
pineafan6fb3e072022-05-20 19:27:23 +0100913 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100914 text: string | null;
915 link: string | null;
916 };
pineafan6fb3e072022-05-20 19:27:23 +0100917 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100918 text: string | null;
919 link: string | null;
920 };
pineafan6fb3e072022-05-20 19:27:23 +0100921 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100922 text: string | null;
923 link: string | null;
924 };
pineafan6fb3e072022-05-20 19:27:23 +0100925 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100926 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500927 text: null;
928 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100929 };
PineaFane6ba7882023-01-18 20:41:16 +0000930 nick: {
931 text: string | null;
932 link: string | null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000933 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100934 };
pineafan6fb3e072022-05-20 19:27:23 +0100935 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100936 name: string;
937 retainPrevious: boolean;
938 nullable: boolean;
939 track: string[];
940 manageableBy: string[];
941 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100942 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100943 enabled: boolean;
944 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100945 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100946 name: string;
947 description: string;
948 min: number;
949 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100950 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100951 name: string;
952 description: string | null;
953 role: string;
954 }[];
955 }[];
956 };
957 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100958}
pineafan4edb7762022-06-26 19:21:04 +0100959
960export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100961 type: string;
962 guild: string;
963 user: string;
964 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100965 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100966 occurredAt: Date;
967 before: string | null;
968 after: string | null;
969 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100970}
971
972export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100973 guild: string;
974 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100975 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100976}
977
pineafan73a7c4a2022-07-24 10:38:04 +0100978export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100979 user: string;
980 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100981 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -0500982 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100983}