blob: e867775013b6f8e222882a6fd6887b9e676d8623 [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>;
TheCodedProff8ef7942023-03-03 15:32:32 -050034 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010035
36 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010037 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050038 this.defaultData = defaultData;
pineafan63fc5e22022-08-04 22:04:10 +010039 }
40
Skyler Greyad002172022-08-16 18:48:26 +010041 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050042 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010043 const entry = await this.guilds.findOne({ id: guild });
TheCodedProf9f4cf9f2023-03-04 14:18:19 -050044 const data = _.cloneDeep(this.defaultData);
TheCodedProff8ef7942023-03-03 15:32:32 -050045 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010046 }
47
Skyler Grey11236ba2022-08-08 21:13:33 +010048 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050049 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010050 // eslint-disable-next-line @typescript-eslint/no-explicit-any
51 const uo: Record<string, any> = {};
52 if (!Array.isArray(unset)) unset = [unset];
53 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010054 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010055 }
Skyler Grey75ea9172022-08-06 10:22:23 +010056 const out = { $set: {}, $unset: {} };
57 if (set) out.$set = set;
58 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010059 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010060 }
61
pineafan63fc5e22022-08-04 22:04:10 +010062 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010063 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050064 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010065 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010066 await this.guilds.updateOne(
67 { id: guild },
68 {
69 $addToSet: { [key]: { $each: value } }
70 },
71 { upsert: true }
72 );
pineafan6702cef2022-06-13 17:52:37 +010073 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010074 await this.guilds.updateOne(
75 { id: guild },
76 {
77 $addToSet: { [key]: value }
78 },
79 { upsert: true }
80 );
pineafan6702cef2022-06-13 17:52:37 +010081 }
82 }
83
Skyler Grey75ea9172022-08-06 10:22:23 +010084 async remove(
85 guild: string,
86 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010087 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010088 value: any,
89 innerKey?: string | null
90 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -050091 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +010092 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010093 await this.guilds.updateOne(
94 { id: guild },
95 {
96 $pull: { [key]: { [innerKey]: { $eq: value } } }
97 },
98 { upsert: true }
99 );
pineafan0bc04162022-07-25 17:22:26 +0100100 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100101 await this.guilds.updateOne(
102 { id: guild },
103 {
104 $pullAll: { [key]: value }
105 },
106 { upsert: true }
107 );
pineafan6702cef2022-06-13 17:52:37 +0100108 } else {
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 }
pineafan6fb3e072022-05-20 19:27:23 +0100117 }
pineafane23c4ec2022-07-27 21:56:27 +0100118
119 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500120 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100121 await this.guilds.deleteOne({ id: guild });
122 }
pineafan6fb3e072022-05-20 19:27:23 +0100123}
124
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500125interface TranscriptEmbed {
126 title?: string;
127 description?: string;
128 fields?: {
129 name: string;
130 value: string;
131 inline: boolean;
132 }[];
133 footer?: {
134 text: string;
135 iconURL?: string;
136 };
TheCodedProffaae5332023-03-01 18:16:05 -0500137 color?: number;
138 timestamp?: string;
139 author?: {
140 name: string;
141 iconURL?: string;
142 url?: string;
143 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500144}
145
146interface TranscriptComponent {
147 type: number;
148 style?: ButtonStyle;
149 label?: string;
150 description?: string;
151 placeholder?: string;
152 emojiURL?: string;
153}
154
155interface TranscriptAuthor {
156 username: string;
157 discriminator: number;
158 nickname?: string;
159 id: string;
160 iconURL?: string;
161 topRole: {
162 color: number;
163 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500164 };
165 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500166}
167
168interface TranscriptAttachment {
169 url: string;
170 filename: string;
171 size: number;
172 log?: string;
173}
174
175interface TranscriptMessage {
176 id: string;
177 author: TranscriptAuthor;
178 content?: string;
179 embeds?: TranscriptEmbed[];
180 components?: TranscriptComponent[][];
181 editedTimestamp?: number;
182 createdTimestamp: number;
183 flags?: string[];
184 attachments?: TranscriptAttachment[];
185 stickerURLs?: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000186 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500187}
188
189interface TranscriptSchema {
190 code: string;
191 for: TranscriptAuthor;
Skyler Greyda16adf2023-03-05 10:22:12 +0000192 type: "ticket" | "purge";
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500193 guild: string;
194 channel: string;
195 messages: TranscriptMessage[];
196 createdTimestamp: number;
197 createdBy: TranscriptAuthor;
198}
199
Skyler Greyda16adf2023-03-05 10:22:12 +0000200interface findDocSchema {
201 channelID: string;
202 messageID: string;
203 transcript: string;
204}
TheCodedProf003160f2023-03-04 17:09:40 -0500205
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500206export class Transcript {
207 transcripts: Collection<TranscriptSchema>;
TheCodedProf003160f2023-03-04 17:09:40 -0500208 messageToTranscript: Collection<findDocSchema>;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500209
210 constructor() {
211 this.transcripts = database.collection<TranscriptSchema>("transcripts");
TheCodedProf003160f2023-03-04 17:09:40 -0500212 this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
213 }
214
215 async upload(data: findDocSchema) {
216 // console.log("Transcript upload")
217 await this.messageToTranscript.insertOne(data);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500218 }
219
220 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500221 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500222 let code;
223 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500224 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500225 } while (await this.transcripts.findOne({ code: code }));
Skyler Greyda16adf2023-03-05 10:22:12 +0000226 const key = crypto
227 .randomBytes(32 ** 2)
228 .toString("base64")
229 .replace(/=/g, "")
230 .replace(/\//g, "_")
231 .replace(/\+/g, "-")
232 .substring(0, 32);
TheCodedProf75c51be2023-03-03 17:18:18 -0500233 const iv = getIV().toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
Skyler Greyda16adf2023-03-05 10:22:12 +0000234 for (const message of transcript.messages) {
235 if (message.content) {
TheCodedProf75c51be2023-03-03 17:18:18 -0500236 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
237 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
238 }
239 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500240
TheCodedProffaae5332023-03-01 18:16:05 -0500241 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Skyler Greyda16adf2023-03-05 10:22:12 +0000242 if (doc.acknowledged) {
243 client.database.eventScheduler.schedule(
244 "deleteTranscript",
245 (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
246 { guild: transcript.guild, code: code, iv: iv, key: key }
247 );
TheCodedProf003160f2023-03-04 17:09:40 -0500248 return [code, key, iv];
Skyler Greyda16adf2023-03-05 10:22:12 +0000249 } else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500250 }
251
TheCodedProf003160f2023-03-04 17:09:40 -0500252 async delete(code: string) {
253 // console.log("Transcript delete")
254 await this.transcripts.deleteOne({ code: code });
TheCodedProf75c51be2023-03-03 17:18:18 -0500255 }
256
257 async deleteAll(guild: string) {
258 // console.log("Transcript delete")
259 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
260 for (const doc of filteredDocs) {
261 await this.transcripts.deleteOne({ code: doc.code });
262 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500263 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500264
TheCodedProf003160f2023-03-04 17:09:40 -0500265 async readEncrypted(code: string) {
266 // console.log("Transcript read")
267 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
268 let findDoc: findDocSchema | null = null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000269 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
270 if (findDoc) {
271 const message = await (
272 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
273 )?.messages.fetch(findDoc.messageID);
274 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500275 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000276 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500277 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000278 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500279 const reader = transcript.getReader();
280 let data: Uint8Array | null = null;
281 let allPacketsReceived = false;
282 while (!allPacketsReceived) {
283 const { value, done } = await reader.read();
Skyler Greyda16adf2023-03-05 10:22:12 +0000284 if (done) {
285 allPacketsReceived = true;
286 continue;
287 }
288 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500289 data = value;
290 } else {
291 data = new Uint8Array(Buffer.concat([data, value]));
292 }
293 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000294 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000295 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500296 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000297 if (!doc) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500298 return doc;
299 }
300
301 async read(code: string, key: string, iv: string) {
302 // console.log("Transcript read")
303 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
304 let findDoc: findDocSchema | null = null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000305 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
306 if (findDoc) {
307 const message = await (
308 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
309 )?.messages.fetch(findDoc.messageID);
310 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500311 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000312 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500313 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000314 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500315 const reader = transcript.getReader();
316 let data: Uint8Array | null = null;
317 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
Skyler Greyda16adf2023-03-05 10:22:12 +0000318 while (true) {
TheCodedProf003160f2023-03-04 17:09:40 -0500319 const { value, done } = await reader.read();
320 if (done) break;
Skyler Greyda16adf2023-03-05 10:22:12 +0000321 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500322 data = value;
323 } else {
324 data = new Uint8Array(Buffer.concat([data, value]));
325 }
326 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000327 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000328 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500329 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000330 if (!doc) return null;
331 for (const message of doc.messages) {
332 if (message.content) {
TheCodedProf003160f2023-03-04 17:09:40 -0500333 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
334 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
335 }
336 }
337 return doc;
338 }
339
Skyler Greyda16adf2023-03-05 10:22:12 +0000340 async createTranscript(
341 messages: Message[],
342 interaction: MessageComponentInteraction | CommandInteraction,
343 member: GuildMember
344 ) {
345 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500346 const newOut: Omit<TranscriptSchema, "code"> = {
347 type: "ticket",
348 for: {
349 username: member!.user.username,
350 discriminator: parseInt(member!.user.discriminator),
351 id: member!.user.id,
352 topRole: {
353 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500354 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000355 iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500356 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500357 },
358 guild: interaction.guild!.id,
359 channel: interaction.channel!.id,
360 messages: [],
361 createdTimestamp: Date.now(),
362 createdBy: {
363 username: interaction.user.username,
364 discriminator: parseInt(interaction.user.discriminator),
365 id: interaction.user.id,
366 topRole: {
367 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500368 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000369 iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500370 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500371 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000372 };
373 if (member.nickname) newOut.for.nickname = member.nickname;
374 if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500375 messages.reverse().forEach((message) => {
376 const msg: TranscriptMessage = {
377 id: message.id,
378 author: {
379 username: message.author.username,
380 discriminator: parseInt(message.author.discriminator),
381 id: message.author.id,
382 topRole: {
383 color: message.member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500384 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000385 iconURL: message.member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500386 bot: message.author.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500387 },
388 createdTimestamp: message.createdTimestamp
389 };
Skyler Greyda16adf2023-03-05 10:22:12 +0000390 if (message.member?.nickname) msg.author.nickname = message.member.nickname;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500391 if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
392 if (message.content) msg.content = message.content;
Skyler Greyda16adf2023-03-05 10:22:12 +0000393 if (message.embeds.length > 0)
394 msg.embeds = message.embeds.map((embed) => {
395 const obj: TranscriptEmbed = {};
396 if (embed.title) obj.title = embed.title;
397 if (embed.description) obj.description = embed.description;
398 if (embed.fields.length > 0)
399 obj.fields = embed.fields.map((field) => {
400 return {
401 name: field.name,
402 value: field.value,
403 inline: field.inline ?? false
404 };
405 });
406 if (embed.color) obj.color = embed.color;
407 if (embed.timestamp) obj.timestamp = embed.timestamp;
408 if (embed.footer)
409 obj.footer = {
410 text: embed.footer.text
411 };
412 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
413 if (embed.author)
414 obj.author = {
415 name: embed.author.name
416 };
417 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
418 if (embed.author?.url) obj.author!.url = embed.author.url;
419 return obj;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500420 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000421 if (message.components.length > 0)
422 msg.components = message.components.map((component) =>
423 component.components.map((child) => {
424 const obj: TranscriptComponent = {
425 type: child.type
426 };
427 if (child.type === ComponentType.Button) {
428 obj.style = child.style;
429 obj.label = child.label ?? "";
430 } else if (child.type > 2) {
431 obj.placeholder = child.placeholder ?? "";
432 }
433 return obj;
434 })
435 );
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500436 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
437 msg.flags = message.flags.toArray();
438
Skyler Greyda16adf2023-03-05 10:22:12 +0000439 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
440 if (message.reference)
441 msg.referencedMessage = [
442 message.reference.guildId ?? "",
443 message.reference.channelId,
444 message.reference.messageId ?? ""
445 ];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500446 newOut.messages.push(msg);
447 });
448 return newOut;
449 }
450
451 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
452 let out = "";
453 for (const message of transcript.messages) {
454 if (message.referencedMessage) {
455 if (Array.isArray(message.referencedMessage)) {
456 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
Skyler Greyda16adf2023-03-05 10:22:12 +0000457 } else out += `> [Reply To] ${message.referencedMessage}\n`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500458 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000459 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
460 message.author.id
461 }) (${message.id})`;
TheCodedProff8ef7942023-03-03 15:32:32 -0500462 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
463 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500464 out += "\n";
465 if (message.content) out += `[Content]\n${message.content}\n\n`;
466 if (message.embeds) {
467 for (const embed of message.embeds) {
468 out += `[Embed]\n`;
469 if (embed.title) out += `| Title: ${embed.title}\n`;
470 if (embed.description) out += `| Description: ${embed.description}\n`;
471 if (embed.fields) {
472 for (const field of embed.fields) {
473 out += `| Field: ${field.name} - ${field.value}\n`;
474 }
475 }
476 if (embed.footer) {
477 out += `|Footer: ${embed.footer.text}\n`;
478 }
479 out += "\n";
480 }
481 }
482 if (message.components) {
483 for (const component of message.components) {
484 out += `[Component]\n`;
485 for (const button of component) {
486 out += `| Button: ${button.label ?? button.description}\n`;
487 }
488 out += "\n";
489 }
490 }
491 if (message.attachments) {
492 for (const attachment of message.attachments) {
493 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
494 }
495 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000496 out += "\n\n";
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500497 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000498 return out;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500499 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500500}
501
pineafan4edb7762022-06-26 19:21:04 +0100502export class History {
503 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100504
pineafan3a02ea32022-08-11 21:35:04 +0100505 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100506 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100507 }
508
Skyler Grey75ea9172022-08-06 10:22:23 +0100509 async create(
510 type: string,
511 guild: string,
512 user: Discord.User,
513 moderator: Discord.User | null,
514 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100515 before?: string | null,
516 after?: string | null,
517 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100518 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500519 // console.log("History create");
Skyler Greyda16adf2023-03-05 10:22:12 +0000520 await this.histories.insertOne(
521 {
522 type: type,
523 guild: guild,
524 user: user.id,
525 moderator: moderator ? moderator.id : null,
526 reason: reason,
527 occurredAt: new Date(),
528 before: before ?? null,
529 after: after ?? null,
530 amount: amount ?? null
531 },
532 collectionOptions
533 );
pineafan4edb7762022-06-26 19:21:04 +0100534 }
535
536 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500537 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100538 const entry = (await this.histories
539 .find({
540 guild: guild,
541 user: user,
542 occurredAt: {
543 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
544 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
545 }
546 })
547 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100548 return entry;
549 }
pineafane23c4ec2022-07-27 21:56:27 +0100550
551 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500552 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100553 await this.histories.deleteMany({ guild: guild });
554 }
pineafan4edb7762022-06-26 19:21:04 +0100555}
556
TheCodedProfb5e9d552023-01-29 15:43:26 -0500557interface ScanCacheSchema {
558 addedAt: Date;
559 hash: string;
560 data: boolean;
561 tags: string[];
562}
563
564export class ScanCache {
565 scanCache: Collection<ScanCacheSchema>;
566
567 constructor() {
568 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
569 }
570
571 async read(hash: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500572 // console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500573 return await this.scanCache.findOne({ hash: hash });
574 }
575
576 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500577 // console.log("ScanCache write");
Skyler Greyda16adf2023-03-05 10:22:12 +0000578 await this.scanCache.insertOne(
579 { hash: hash, data: data, tags: tags ?? [], addedAt: new Date() },
580 collectionOptions
581 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500582 }
583
584 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500585 // console.log("ScanCache cleanup");
Skyler Greyda16adf2023-03-05 10:22:12 +0000586 await this.scanCache.deleteMany({
587 addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
588 hash: { $not$text: "http" }
589 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500590 }
591}
592
PineaFan538d3752023-01-12 21:48:23 +0000593export class PerformanceTest {
594 performanceData: Collection<PerformanceDataSchema>;
595
596 constructor() {
597 this.performanceData = database.collection<PerformanceDataSchema>("performance");
598 }
599
600 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500601 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000602 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500603 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000604 }
605 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500606 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000607 return await this.performanceData.find({}).toArray();
608 }
609}
610
611export interface PerformanceDataSchema {
612 timestamp?: Date;
613 discord: number;
614 databaseRead: number;
615 resources: {
616 cpu: number;
617 memory: number;
618 temperature: number;
Skyler Greyda16adf2023-03-05 10:22:12 +0000619 };
PineaFan538d3752023-01-12 21:48:23 +0000620}
621
pineafan4edb7762022-06-26 19:21:04 +0100622export class ModNotes {
623 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100624
pineafan3a02ea32022-08-11 21:35:04 +0100625 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100626 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100627 }
628
629 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500630 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100631 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100632 }
633
634 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500635 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100636 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100637 return entry?.note ?? null;
638 }
TheCodedProf267563a2023-01-21 17:00:57 -0500639
640 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500641 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500642 await this.modNotes.deleteMany({ guild: guild });
643 }
pineafan4edb7762022-06-26 19:21:04 +0100644}
645
pineafan73a7c4a2022-07-24 10:38:04 +0100646export class Premium {
647 premium: Collection<PremiumSchema>;
Skyler Greyda16adf2023-03-05 10:22:12 +0000648 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
649 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100650
pineafan3a02ea32022-08-11 21:35:04 +0100651 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100652 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500653 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100654 }
655
TheCodedProf633866f2023-02-03 17:06:00 -0500656 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500657 // console.log("Premium updateUser");
Skyler Greyda16adf2023-03-05 10:22:12 +0000658 if (!(await this.userExists(user))) await this.createUser(user, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500659 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
660 }
661
662 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500663 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500664 const entry = await this.premium.findOne({ user: user });
665 return entry ? true : false;
666 }
TheCodedProf633866f2023-02-03 17:06:00 -0500667 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500668 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500669 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500670 }
671
TheCodedProfaa3fe992023-02-25 21:53:09 -0500672 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500673 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500674 // [Has premium, user giving premium, level, is mod: if given automatically]
675 const cached = this.cache.get(guild);
676 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500677 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000678 const members = (await client.guilds.fetch(guild)).members.cache;
679 for (const { user } of entries) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500680 const member = members.get(user);
Skyler Greyda16adf2023-03-05 10:22:12 +0000681 if (member) {
682 //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 -0500683 const modPerms = //TODO: Create list in config for perms
Skyler Greyda16adf2023-03-05 10:22:12 +0000684 member.permissions.has("Administrator") ||
685 member.permissions.has("ManageChannels") ||
686 member.permissions.has("ManageRoles") ||
687 member.permissions.has("ManageEmojisAndStickers") ||
688 member.permissions.has("ManageWebhooks") ||
689 member.permissions.has("ManageGuild") ||
690 member.permissions.has("KickMembers") ||
691 member.permissions.has("BanMembers") ||
692 member.permissions.has("ManageEvents") ||
693 member.permissions.has("ManageMessages") ||
694 member.permissions.has("ManageThreads");
695 const entry = entries.find((e) => e.user === member.id);
696 if (entry && entry.level === 3 && modPerms) {
697 this.cache.set(guild, [
698 true,
699 member.id,
700 entry.level,
701 true,
702 new Date(Date.now() + this.cacheTimeout)
703 ]);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500704 return [true, member.id, entry.level, true];
705 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500706 }
707 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100708 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500709 appliesTo: {
710 $elemMatch: {
711 $eq: guild
712 }
713 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100714 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000715 this.cache.set(guild, [
716 entry ? true : false,
717 entry?.user ?? "",
718 entry?.level ?? 0,
719 false,
720 new Date(Date.now() + this.cacheTimeout)
721 ]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500722 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500723 }
724
TheCodedProf633866f2023-02-03 17:06:00 -0500725 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500726 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500727 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500728 if (!entry) return null;
729 return entry;
730 }
731
TheCodedProf94ff6de2023-02-22 17:47:26 -0500732 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500733 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500734 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000735 if (member) {
736 const entry = entries.find((e) => e.user === member.id);
737 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500738 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000739 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500740 }
741 const roles = member.roles;
742 let level = 0;
743 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500744 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500745 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500746 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500747 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500748 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500749 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500750 level = 3;
751 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500752 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500753 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000754 await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
TheCodedProf633866f2023-02-03 17:06:00 -0500755 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000756 await this.premium.updateOne(
757 { user: member.id },
758 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
759 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500760 }
761 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000762 const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
763 for (const { roles, id } of members.values()) {
764 const entry = entries.find((e) => e.user === id);
765 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500766 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000767 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500768 }
769 let level: number = 0;
770 if (roles.cache.has("1066468879309750313")) {
771 level = 99;
772 } else if (roles.cache.has("1066465491713003520")) {
773 level = 1;
774 } else if (roles.cache.has("1066439526496604194")) {
775 level = 2;
776 } else if (roles.cache.has("1066464134322978912")) {
777 level = 3;
778 }
779 await this.updateUser(id, level);
780 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000781 await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
TheCodedProf94ff6de2023-02-22 17:47:26 -0500782 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000783 await this.premium.updateOne(
784 { user: id },
785 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
786 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500787 }
TheCodedProf633866f2023-02-03 17:06:00 -0500788 }
789 }
TheCodedProf267563a2023-01-21 17:00:57 -0500790 }
791
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500792 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500793 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500794 const { level } = (await this.fetchUser(user))!;
795 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500796 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100797 }
TheCodedProffc420b72023-01-24 17:14:38 -0500798
799 removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500800 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500801 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProffc420b72023-01-24 17:14:38 -0500802 return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
803 }
pineafan4edb7762022-06-26 19:21:04 +0100804}
805
pineafan6fb3e072022-05-20 19:27:23 +0100806export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100807 id: string;
808 version: number;
PineaFan100df682023-01-02 13:26:08 +0000809 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100810 filters: {
811 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100812 NSFW: boolean;
813 size: boolean;
814 };
815 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100816 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100817 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100818 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100819 strict: string[];
820 loose: string[];
821 };
pineafan6fb3e072022-05-20 19:27:23 +0100822 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100823 users: string[];
824 roles: string[];
825 channels: string[];
826 };
827 };
pineafan6fb3e072022-05-20 19:27:23 +0100828 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100829 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000830 allowed: {
831 channels: string[];
832 roles: string[];
833 users: string[];
834 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100835 };
pineafan6fb3e072022-05-20 19:27:23 +0100836 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100837 mass: number;
838 everyone: boolean;
839 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100840 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100841 roles: string[];
842 rolesToMention: string[];
843 users: string[];
844 channels: string[];
845 };
846 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500847 clean: {
848 channels: string[];
849 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500850 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500851 roles: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000852 };
853 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100854 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500855 autoPublish: {
856 enabled: boolean;
857 channels: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000858 };
pineafan6fb3e072022-05-20 19:27:23 +0100859 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100860 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100861 role: string | null;
862 ping: string | null;
863 channel: string | null;
864 message: string | null;
865 };
866 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100867 logging: {
868 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100869 enabled: boolean;
870 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100871 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100872 };
pineafan6fb3e072022-05-20 19:27:23 +0100873 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100874 channel: string | null;
875 };
pineafan73a7c4a2022-07-24 10:38:04 +0100876 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100877 channel: string | null;
878 saved: Record<string, string>;
879 };
880 };
pineafan6fb3e072022-05-20 19:27:23 +0100881 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000882 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100883 role: string | null;
884 };
pineafan6fb3e072022-05-20 19:27:23 +0100885 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100886 enabled: boolean;
887 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100888 types: string;
889 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100890 useCustom: boolean;
891 supportRole: string | null;
892 maxTickets: number;
893 };
pineafan6fb3e072022-05-20 19:27:23 +0100894 moderation: {
895 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100896 timeout: boolean;
897 role: string | null;
898 text: string | null;
899 link: string | null;
900 };
pineafan6fb3e072022-05-20 19:27:23 +0100901 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100902 text: string | null;
903 link: string | null;
904 };
pineafan6fb3e072022-05-20 19:27:23 +0100905 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100906 text: string | null;
907 link: string | null;
908 };
pineafan6fb3e072022-05-20 19:27:23 +0100909 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100910 text: string | null;
911 link: string | null;
912 };
pineafan6fb3e072022-05-20 19:27:23 +0100913 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100914 text: string | null;
915 link: string | null;
916 };
pineafan6fb3e072022-05-20 19:27:23 +0100917 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100918 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500919 text: null;
920 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100921 };
PineaFane6ba7882023-01-18 20:41:16 +0000922 nick: {
923 text: string | null;
924 link: string | null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000925 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100926 };
pineafan6fb3e072022-05-20 19:27:23 +0100927 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100928 name: string;
929 retainPrevious: boolean;
930 nullable: boolean;
931 track: string[];
932 manageableBy: string[];
933 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100934 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100935 enabled: boolean;
936 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100937 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100938 name: string;
939 description: string;
940 min: number;
941 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100942 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100943 name: string;
944 description: string | null;
945 role: string;
946 }[];
947 }[];
948 };
949 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100950}
pineafan4edb7762022-06-26 19:21:04 +0100951
952export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100953 type: string;
954 guild: string;
955 user: string;
956 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100957 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100958 occurredAt: Date;
959 before: string | null;
960 after: string | null;
961 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100962}
963
964export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100965 guild: string;
966 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100967 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100968}
969
pineafan73a7c4a2022-07-24 10:38:04 +0100970export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100971 user: string;
972 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100973 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -0500974 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +0100975}