blob: a5d514901b7a21bd8b09f78dfe0dc5f572cfb83f [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
TheCodedProfb7a7b992023-03-05 16:11:59 -050049 async updateAllGuilds() {
50 const guilds = await this.guilds.find().toArray();
51 for (const guild of guilds) {
52 let guildObj;
53 try {
54 guildObj = await client.guilds.fetch(guild.id);
55 } catch (e) {
56 guildObj = null;
57 }
Skyler Grey67691762023-03-06 09:58:19 +000058 if (!guildObj) await this.delete(guild.id);
TheCodedProfb7a7b992023-03-05 16:11:59 -050059 }
60 }
61
Skyler Greyad002172022-08-16 18:48:26 +010062 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050063 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010064 const entry = await this.guilds.findOne({ id: guild });
TheCodedProf9f4cf9f2023-03-04 14:18:19 -050065 const data = _.cloneDeep(this.defaultData);
TheCodedProff8ef7942023-03-03 15:32:32 -050066 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010067 }
68
Skyler Grey11236ba2022-08-08 21:13:33 +010069 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050070 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010071 // eslint-disable-next-line @typescript-eslint/no-explicit-any
72 const uo: Record<string, any> = {};
73 if (!Array.isArray(unset)) unset = [unset];
74 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010075 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010076 }
Skyler Grey75ea9172022-08-06 10:22:23 +010077 const out = { $set: {}, $unset: {} };
78 if (set) out.$set = set;
79 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010080 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010081 }
82
pineafan63fc5e22022-08-04 22:04:10 +010083 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010084 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050085 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010086 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010087 await this.guilds.updateOne(
88 { id: guild },
89 {
90 $addToSet: { [key]: { $each: value } }
91 },
92 { upsert: true }
93 );
pineafan6702cef2022-06-13 17:52:37 +010094 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010095 await this.guilds.updateOne(
96 { id: guild },
97 {
98 $addToSet: { [key]: value }
99 },
100 { upsert: true }
101 );
pineafan6702cef2022-06-13 17:52:37 +0100102 }
103 }
104
Skyler Grey75ea9172022-08-06 10:22:23 +0100105 async remove(
106 guild: string,
107 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +0100108 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +0100109 value: any,
110 innerKey?: string | null
111 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500112 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +0100113 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100114 await this.guilds.updateOne(
115 { id: guild },
116 {
117 $pull: { [key]: { [innerKey]: { $eq: value } } }
118 },
119 { upsert: true }
120 );
pineafan0bc04162022-07-25 17:22:26 +0100121 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 await this.guilds.updateOne(
123 { id: guild },
124 {
125 $pullAll: { [key]: value }
126 },
127 { upsert: true }
128 );
pineafan6702cef2022-06-13 17:52:37 +0100129 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100130 await this.guilds.updateOne(
131 { id: guild },
132 {
133 $pullAll: { [key]: [value] }
134 },
135 { upsert: true }
136 );
pineafan6702cef2022-06-13 17:52:37 +0100137 }
pineafan6fb3e072022-05-20 19:27:23 +0100138 }
pineafane23c4ec2022-07-27 21:56:27 +0100139
140 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500141 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100142 await this.guilds.deleteOne({ id: guild });
143 }
pineafan6fb3e072022-05-20 19:27:23 +0100144}
145
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500146interface TranscriptEmbed {
147 title?: string;
148 description?: string;
149 fields?: {
150 name: string;
151 value: string;
152 inline: boolean;
153 }[];
154 footer?: {
155 text: string;
156 iconURL?: string;
157 };
TheCodedProffaae5332023-03-01 18:16:05 -0500158 color?: number;
159 timestamp?: string;
160 author?: {
161 name: string;
162 iconURL?: string;
163 url?: string;
164 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500165}
166
167interface TranscriptComponent {
168 type: number;
169 style?: ButtonStyle;
170 label?: string;
171 description?: string;
172 placeholder?: string;
173 emojiURL?: string;
174}
175
176interface TranscriptAuthor {
177 username: string;
178 discriminator: number;
179 nickname?: string;
180 id: string;
181 iconURL?: string;
182 topRole: {
183 color: number;
184 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500185 };
186 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500187}
188
189interface TranscriptAttachment {
190 url: string;
191 filename: string;
192 size: number;
193 log?: string;
194}
195
196interface TranscriptMessage {
197 id: string;
198 author: TranscriptAuthor;
199 content?: string;
200 embeds?: TranscriptEmbed[];
201 components?: TranscriptComponent[][];
202 editedTimestamp?: number;
203 createdTimestamp: number;
204 flags?: string[];
205 attachments?: TranscriptAttachment[];
206 stickerURLs?: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000207 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500208}
209
210interface TranscriptSchema {
211 code: string;
212 for: TranscriptAuthor;
Skyler Greyda16adf2023-03-05 10:22:12 +0000213 type: "ticket" | "purge";
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500214 guild: string;
215 channel: string;
216 messages: TranscriptMessage[];
217 createdTimestamp: number;
218 createdBy: TranscriptAuthor;
219}
220
Skyler Greyda16adf2023-03-05 10:22:12 +0000221interface findDocSchema {
222 channelID: string;
223 messageID: string;
224 transcript: string;
225}
TheCodedProf003160f2023-03-04 17:09:40 -0500226
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500227export class Transcript {
228 transcripts: Collection<TranscriptSchema>;
TheCodedProf003160f2023-03-04 17:09:40 -0500229 messageToTranscript: Collection<findDocSchema>;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500230
231 constructor() {
232 this.transcripts = database.collection<TranscriptSchema>("transcripts");
TheCodedProf003160f2023-03-04 17:09:40 -0500233 this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
234 }
235
236 async upload(data: findDocSchema) {
237 // console.log("Transcript upload")
238 await this.messageToTranscript.insertOne(data);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500239 }
240
241 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500242 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500243 let code;
244 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500245 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500246 } while (await this.transcripts.findOne({ code: code }));
Skyler Greyda16adf2023-03-05 10:22:12 +0000247 const key = crypto
248 .randomBytes(32 ** 2)
249 .toString("base64")
250 .replace(/=/g, "")
251 .replace(/\//g, "_")
252 .replace(/\+/g, "-")
253 .substring(0, 32);
Skyler Grey67691762023-03-06 09:58:19 +0000254 const iv = getIV()
255 .toString("base64")
256 .substring(0, 16)
257 .replace(/=/g, "")
258 .replace(/\//g, "_")
259 .replace(/\+/g, "-");
260 console.log(iv);
Skyler Greyda16adf2023-03-05 10:22:12 +0000261 for (const message of transcript.messages) {
262 if (message.content) {
TheCodedProf75c51be2023-03-03 17:18:18 -0500263 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
264 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
265 }
266 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500267
TheCodedProffaae5332023-03-01 18:16:05 -0500268 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Skyler Greyda16adf2023-03-05 10:22:12 +0000269 if (doc.acknowledged) {
270 client.database.eventScheduler.schedule(
271 "deleteTranscript",
272 (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
273 { guild: transcript.guild, code: code, iv: iv, key: key }
274 );
TheCodedProf003160f2023-03-04 17:09:40 -0500275 return [code, key, iv];
Skyler Greyda16adf2023-03-05 10:22:12 +0000276 } else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500277 }
278
TheCodedProf003160f2023-03-04 17:09:40 -0500279 async delete(code: string) {
280 // console.log("Transcript delete")
281 await this.transcripts.deleteOne({ code: code });
TheCodedProf75c51be2023-03-03 17:18:18 -0500282 }
283
284 async deleteAll(guild: string) {
285 // console.log("Transcript delete")
286 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
287 for (const doc of filteredDocs) {
288 await this.transcripts.deleteOne({ code: doc.code });
289 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500290 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500291
TheCodedProf003160f2023-03-04 17:09:40 -0500292 async readEncrypted(code: string) {
293 // console.log("Transcript read")
294 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
295 let findDoc: findDocSchema | null = null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000296 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
297 if (findDoc) {
298 const message = await (
299 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
300 )?.messages.fetch(findDoc.messageID);
301 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500302 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000303 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500304 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000305 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500306 const reader = transcript.getReader();
307 let data: Uint8Array | null = null;
308 let allPacketsReceived = false;
309 while (!allPacketsReceived) {
310 const { value, done } = await reader.read();
Skyler Greyda16adf2023-03-05 10:22:12 +0000311 if (done) {
312 allPacketsReceived = true;
313 continue;
314 }
315 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500316 data = value;
317 } else {
318 data = new Uint8Array(Buffer.concat([data, value]));
319 }
320 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000321 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000322 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500323 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000324 if (!doc) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500325 return doc;
326 }
327
328 async read(code: string, key: string, iv: string) {
Skyler Grey67691762023-03-06 09:58:19 +0000329 console.log("Transcript read");
TheCodedProf003160f2023-03-04 17:09:40 -0500330 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
331 let findDoc: findDocSchema | null = null;
Skyler Grey67691762023-03-06 09:58:19 +0000332 console.log(doc);
Skyler Greyda16adf2023-03-05 10:22:12 +0000333 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
334 if (findDoc) {
335 const message = await (
336 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
337 )?.messages.fetch(findDoc.messageID);
338 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500339 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000340 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500341 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000342 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500343 const reader = transcript.getReader();
344 let data: Uint8Array | null = null;
345 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
Skyler Greyda16adf2023-03-05 10:22:12 +0000346 while (true) {
TheCodedProf003160f2023-03-04 17:09:40 -0500347 const { value, done } = await reader.read();
348 if (done) break;
Skyler Greyda16adf2023-03-05 10:22:12 +0000349 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500350 data = value;
351 } else {
352 data = new Uint8Array(Buffer.concat([data, value]));
353 }
354 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000355 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000356 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500357 }
Skyler Grey67691762023-03-06 09:58:19 +0000358 console.log(doc);
Skyler Greyda16adf2023-03-05 10:22:12 +0000359 if (!doc) return null;
360 for (const message of doc.messages) {
361 if (message.content) {
TheCodedProf003160f2023-03-04 17:09:40 -0500362 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
363 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
364 }
365 }
366 return doc;
367 }
368
Skyler Greyda16adf2023-03-05 10:22:12 +0000369 async createTranscript(
Skyler Greye0c511b2023-03-06 10:30:17 +0000370 type: "ticket" | "purge",
Skyler Greyda16adf2023-03-05 10:22:12 +0000371 messages: Message[],
372 interaction: MessageComponentInteraction | CommandInteraction,
373 member: GuildMember
374 ) {
375 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500376 const newOut: Omit<TranscriptSchema, "code"> = {
Skyler Greye0c511b2023-03-06 10:30:17 +0000377 type: type,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500378 for: {
379 username: member!.user.username,
380 discriminator: parseInt(member!.user.discriminator),
381 id: member!.user.id,
382 topRole: {
383 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500384 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000385 iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500386 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500387 },
388 guild: interaction.guild!.id,
389 channel: interaction.channel!.id,
390 messages: [],
391 createdTimestamp: Date.now(),
392 createdBy: {
393 username: interaction.user.username,
394 discriminator: parseInt(interaction.user.discriminator),
395 id: interaction.user.id,
396 topRole: {
397 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500398 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000399 iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500400 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500401 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000402 };
403 if (member.nickname) newOut.for.nickname = member.nickname;
404 if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500405 messages.reverse().forEach((message) => {
406 const msg: TranscriptMessage = {
407 id: message.id,
408 author: {
409 username: message.author.username,
410 discriminator: parseInt(message.author.discriminator),
411 id: message.author.id,
412 topRole: {
Skyler Greya0c70242023-03-06 09:56:21 +0000413 color: message.member ? message.member.roles.highest.color : 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500414 },
Skyler Grey67691762023-03-06 09:58:19 +0000415 iconURL: (message.member?.user || message.author).displayAvatarURL({ forceStatic: true }),
416 bot: message.author.bot || false
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500417 },
418 createdTimestamp: message.createdTimestamp
419 };
Skyler Greyda16adf2023-03-05 10:22:12 +0000420 if (message.member?.nickname) msg.author.nickname = message.member.nickname;
Skyler Greya0c70242023-03-06 09:56:21 +0000421 if (message.member?.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500422 if (message.content) msg.content = message.content;
Skyler Greyda16adf2023-03-05 10:22:12 +0000423 if (message.embeds.length > 0)
424 msg.embeds = message.embeds.map((embed) => {
425 const obj: TranscriptEmbed = {};
426 if (embed.title) obj.title = embed.title;
427 if (embed.description) obj.description = embed.description;
428 if (embed.fields.length > 0)
429 obj.fields = embed.fields.map((field) => {
430 return {
431 name: field.name,
432 value: field.value,
433 inline: field.inline ?? false
434 };
435 });
436 if (embed.color) obj.color = embed.color;
437 if (embed.timestamp) obj.timestamp = embed.timestamp;
438 if (embed.footer)
439 obj.footer = {
440 text: embed.footer.text
441 };
442 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
443 if (embed.author)
444 obj.author = {
445 name: embed.author.name
446 };
447 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
448 if (embed.author?.url) obj.author!.url = embed.author.url;
449 return obj;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500450 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000451 if (message.components.length > 0)
452 msg.components = message.components.map((component) =>
453 component.components.map((child) => {
454 const obj: TranscriptComponent = {
455 type: child.type
456 };
457 if (child.type === ComponentType.Button) {
458 obj.style = child.style;
459 obj.label = child.label ?? "";
460 } else if (child.type > 2) {
461 obj.placeholder = child.placeholder ?? "";
462 }
463 return obj;
464 })
465 );
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500466 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
467 msg.flags = message.flags.toArray();
468
Skyler Greyda16adf2023-03-05 10:22:12 +0000469 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
470 if (message.reference)
471 msg.referencedMessage = [
472 message.reference.guildId ?? "",
473 message.reference.channelId,
474 message.reference.messageId ?? ""
475 ];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500476 newOut.messages.push(msg);
477 });
478 return newOut;
479 }
480
481 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
482 let out = "";
483 for (const message of transcript.messages) {
484 if (message.referencedMessage) {
485 if (Array.isArray(message.referencedMessage)) {
486 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
Skyler Greyda16adf2023-03-05 10:22:12 +0000487 } else out += `> [Reply To] ${message.referencedMessage}\n`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500488 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000489 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
490 message.author.id
491 }) (${message.id})`;
TheCodedProff8ef7942023-03-03 15:32:32 -0500492 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
493 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500494 out += "\n";
495 if (message.content) out += `[Content]\n${message.content}\n\n`;
496 if (message.embeds) {
497 for (const embed of message.embeds) {
498 out += `[Embed]\n`;
499 if (embed.title) out += `| Title: ${embed.title}\n`;
500 if (embed.description) out += `| Description: ${embed.description}\n`;
501 if (embed.fields) {
502 for (const field of embed.fields) {
503 out += `| Field: ${field.name} - ${field.value}\n`;
504 }
505 }
506 if (embed.footer) {
507 out += `|Footer: ${embed.footer.text}\n`;
508 }
509 out += "\n";
510 }
511 }
512 if (message.components) {
513 for (const component of message.components) {
514 out += `[Component]\n`;
515 for (const button of component) {
516 out += `| Button: ${button.label ?? button.description}\n`;
517 }
518 out += "\n";
519 }
520 }
521 if (message.attachments) {
522 for (const attachment of message.attachments) {
523 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
524 }
525 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000526 out += "\n\n";
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500527 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000528 return out;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500529 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500530}
531
pineafan4edb7762022-06-26 19:21:04 +0100532export class History {
533 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100534
pineafan3a02ea32022-08-11 21:35:04 +0100535 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100536 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100537 }
538
Skyler Grey75ea9172022-08-06 10:22:23 +0100539 async create(
540 type: string,
541 guild: string,
542 user: Discord.User,
543 moderator: Discord.User | null,
544 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100545 before?: string | null,
546 after?: string | null,
547 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100548 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500549 // console.log("History create");
Skyler Greyda16adf2023-03-05 10:22:12 +0000550 await this.histories.insertOne(
551 {
552 type: type,
553 guild: guild,
554 user: user.id,
555 moderator: moderator ? moderator.id : null,
556 reason: reason,
557 occurredAt: new Date(),
558 before: before ?? null,
559 after: after ?? null,
560 amount: amount ?? null
561 },
562 collectionOptions
563 );
pineafan4edb7762022-06-26 19:21:04 +0100564 }
565
566 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500567 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100568 const entry = (await this.histories
569 .find({
570 guild: guild,
571 user: user,
572 occurredAt: {
573 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
574 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
575 }
576 })
577 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100578 return entry;
579 }
pineafane23c4ec2022-07-27 21:56:27 +0100580
581 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500582 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100583 await this.histories.deleteMany({ guild: guild });
584 }
pineafan4edb7762022-06-26 19:21:04 +0100585}
586
TheCodedProfb5e9d552023-01-29 15:43:26 -0500587interface ScanCacheSchema {
588 addedAt: Date;
589 hash: string;
590 data: boolean;
591 tags: string[];
592}
593
594export class ScanCache {
595 scanCache: Collection<ScanCacheSchema>;
596
597 constructor() {
598 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
599 }
600
601 async read(hash: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500602 // console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500603 return await this.scanCache.findOne({ hash: hash });
604 }
605
606 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500607 // console.log("ScanCache write");
Skyler Greyda16adf2023-03-05 10:22:12 +0000608 await this.scanCache.insertOne(
609 { hash: hash, data: data, tags: tags ?? [], addedAt: new Date() },
610 collectionOptions
611 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500612 }
613
614 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500615 // console.log("ScanCache cleanup");
Skyler Greyda16adf2023-03-05 10:22:12 +0000616 await this.scanCache.deleteMany({
617 addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
618 hash: { $not$text: "http" }
619 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500620 }
621}
622
PineaFan538d3752023-01-12 21:48:23 +0000623export class PerformanceTest {
624 performanceData: Collection<PerformanceDataSchema>;
625
626 constructor() {
627 this.performanceData = database.collection<PerformanceDataSchema>("performance");
628 }
629
630 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500631 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000632 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500633 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000634 }
635 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500636 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000637 return await this.performanceData.find({}).toArray();
638 }
639}
640
641export interface PerformanceDataSchema {
642 timestamp?: Date;
643 discord: number;
644 databaseRead: number;
645 resources: {
646 cpu: number;
647 memory: number;
648 temperature: number;
Skyler Greyda16adf2023-03-05 10:22:12 +0000649 };
PineaFan538d3752023-01-12 21:48:23 +0000650}
651
pineafan4edb7762022-06-26 19:21:04 +0100652export class ModNotes {
653 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100654
pineafan3a02ea32022-08-11 21:35:04 +0100655 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100656 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100657 }
658
659 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500660 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100661 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100662 }
663
664 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500665 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100666 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100667 return entry?.note ?? null;
668 }
TheCodedProf267563a2023-01-21 17:00:57 -0500669
670 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500671 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500672 await this.modNotes.deleteMany({ guild: guild });
673 }
pineafan4edb7762022-06-26 19:21:04 +0100674}
675
pineafan73a7c4a2022-07-24 10:38:04 +0100676export class Premium {
677 premium: Collection<PremiumSchema>;
Skyler Greyda16adf2023-03-05 10:22:12 +0000678 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
679 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100680
pineafan3a02ea32022-08-11 21:35:04 +0100681 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100682 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500683 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100684 }
685
TheCodedProf633866f2023-02-03 17:06:00 -0500686 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500687 // console.log("Premium updateUser");
Skyler Greyda16adf2023-03-05 10:22:12 +0000688 if (!(await this.userExists(user))) await this.createUser(user, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500689 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
690 }
691
692 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500693 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500694 const entry = await this.premium.findOne({ user: user });
695 return entry ? true : false;
696 }
TheCodedProf633866f2023-02-03 17:06:00 -0500697 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500698 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500699 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500700 }
701
TheCodedProfaa3fe992023-02-25 21:53:09 -0500702 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500703 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500704 // [Has premium, user giving premium, level, is mod: if given automatically]
705 const cached = this.cache.get(guild);
706 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500707 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000708 const members = (await client.guilds.fetch(guild)).members.cache;
709 for (const { user } of entries) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500710 const member = members.get(user);
Skyler Greyda16adf2023-03-05 10:22:12 +0000711 if (member) {
712 //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 -0500713 const modPerms = //TODO: Create list in config for perms
Skyler Greyda16adf2023-03-05 10:22:12 +0000714 member.permissions.has("Administrator") ||
715 member.permissions.has("ManageChannels") ||
716 member.permissions.has("ManageRoles") ||
717 member.permissions.has("ManageEmojisAndStickers") ||
718 member.permissions.has("ManageWebhooks") ||
719 member.permissions.has("ManageGuild") ||
720 member.permissions.has("KickMembers") ||
721 member.permissions.has("BanMembers") ||
722 member.permissions.has("ManageEvents") ||
723 member.permissions.has("ManageMessages") ||
724 member.permissions.has("ManageThreads");
725 const entry = entries.find((e) => e.user === member.id);
726 if (entry && entry.level === 3 && modPerms) {
727 this.cache.set(guild, [
728 true,
729 member.id,
730 entry.level,
731 true,
732 new Date(Date.now() + this.cacheTimeout)
733 ]);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500734 return [true, member.id, entry.level, true];
735 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500736 }
737 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100738 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500739 appliesTo: {
740 $elemMatch: {
741 $eq: guild
742 }
743 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100744 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000745 this.cache.set(guild, [
746 entry ? true : false,
747 entry?.user ?? "",
748 entry?.level ?? 0,
749 false,
750 new Date(Date.now() + this.cacheTimeout)
751 ]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500752 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500753 }
754
TheCodedProf633866f2023-02-03 17:06:00 -0500755 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500756 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500757 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500758 if (!entry) return null;
759 return entry;
760 }
761
TheCodedProf94ff6de2023-02-22 17:47:26 -0500762 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500763 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500764 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000765 if (member) {
766 const entry = entries.find((e) => e.user === member.id);
767 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500768 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000769 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500770 }
771 const roles = member.roles;
772 let level = 0;
773 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500774 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500775 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500776 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500777 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500778 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500779 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500780 level = 3;
781 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500782 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500783 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000784 await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
TheCodedProf633866f2023-02-03 17:06:00 -0500785 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000786 await this.premium.updateOne(
787 { user: member.id },
788 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
789 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500790 }
791 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000792 const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
793 for (const { roles, id } of members.values()) {
794 const entry = entries.find((e) => e.user === id);
795 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500796 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000797 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500798 }
799 let level: number = 0;
800 if (roles.cache.has("1066468879309750313")) {
801 level = 99;
802 } else if (roles.cache.has("1066465491713003520")) {
803 level = 1;
804 } else if (roles.cache.has("1066439526496604194")) {
805 level = 2;
806 } else if (roles.cache.has("1066464134322978912")) {
807 level = 3;
808 }
809 await this.updateUser(id, level);
810 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000811 await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
TheCodedProf94ff6de2023-02-22 17:47:26 -0500812 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000813 await this.premium.updateOne(
814 { user: id },
815 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
816 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500817 }
TheCodedProf633866f2023-02-03 17:06:00 -0500818 }
819 }
TheCodedProf267563a2023-01-21 17:00:57 -0500820 }
821
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500822 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500823 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500824 const { level } = (await this.fetchUser(user))!;
825 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500826 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100827 }
TheCodedProffc420b72023-01-24 17:14:38 -0500828
TheCodedProf48865eb2023-03-05 15:25:25 -0500829 async removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500830 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500831 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf48865eb2023-03-05 15:25:25 -0500832 return await this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
TheCodedProffc420b72023-01-24 17:14:38 -0500833 }
pineafan4edb7762022-06-26 19:21:04 +0100834}
835
pineafan6fb3e072022-05-20 19:27:23 +0100836export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100837 id: string;
838 version: number;
PineaFan100df682023-01-02 13:26:08 +0000839 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100840 filters: {
841 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100842 NSFW: boolean;
843 size: boolean;
844 };
845 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100846 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100847 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100848 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100849 strict: string[];
850 loose: string[];
851 };
pineafan6fb3e072022-05-20 19:27:23 +0100852 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100853 users: string[];
854 roles: string[];
855 channels: string[];
856 };
857 };
pineafan6fb3e072022-05-20 19:27:23 +0100858 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100859 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000860 allowed: {
861 channels: string[];
862 roles: string[];
863 users: string[];
864 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100865 };
pineafan6fb3e072022-05-20 19:27:23 +0100866 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100867 mass: number;
868 everyone: boolean;
869 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100870 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100871 roles: string[];
872 rolesToMention: string[];
873 users: string[];
874 channels: string[];
875 };
876 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500877 clean: {
878 channels: string[];
879 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500880 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500881 roles: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000882 };
883 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100884 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500885 autoPublish: {
886 enabled: boolean;
887 channels: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000888 };
pineafan6fb3e072022-05-20 19:27:23 +0100889 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100890 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100891 role: string | null;
892 ping: string | null;
893 channel: string | null;
894 message: string | null;
895 };
896 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100897 logging: {
898 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100899 enabled: boolean;
900 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100901 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100902 };
pineafan6fb3e072022-05-20 19:27:23 +0100903 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100904 channel: string | null;
905 };
pineafan73a7c4a2022-07-24 10:38:04 +0100906 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100907 channel: string | null;
908 saved: Record<string, string>;
909 };
910 };
pineafan6fb3e072022-05-20 19:27:23 +0100911 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000912 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100913 role: string | null;
914 };
pineafan6fb3e072022-05-20 19:27:23 +0100915 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100916 enabled: boolean;
917 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100918 types: string;
919 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100920 useCustom: boolean;
921 supportRole: string | null;
922 maxTickets: number;
923 };
pineafan6fb3e072022-05-20 19:27:23 +0100924 moderation: {
925 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100926 timeout: boolean;
927 role: string | null;
928 text: string | null;
929 link: string | null;
930 };
pineafan6fb3e072022-05-20 19:27:23 +0100931 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100932 text: string | null;
933 link: string | null;
934 };
pineafan6fb3e072022-05-20 19:27:23 +0100935 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100936 text: string | null;
937 link: string | null;
938 };
pineafan6fb3e072022-05-20 19:27:23 +0100939 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100940 text: string | null;
941 link: string | null;
942 };
pineafan6fb3e072022-05-20 19:27:23 +0100943 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100944 text: string | null;
945 link: string | null;
946 };
pineafan6fb3e072022-05-20 19:27:23 +0100947 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100948 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500949 text: null;
950 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100951 };
PineaFane6ba7882023-01-18 20:41:16 +0000952 nick: {
953 text: string | null;
954 link: string | null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000955 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100956 };
pineafan6fb3e072022-05-20 19:27:23 +0100957 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100958 name: string;
959 retainPrevious: boolean;
960 nullable: boolean;
961 track: string[];
962 manageableBy: string[];
963 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100964 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100965 enabled: boolean;
966 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100967 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100968 name: string;
969 description: string;
970 min: number;
971 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100972 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100973 name: string;
974 description: string | null;
975 role: string;
976 }[];
977 }[];
978 };
979 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100980}
pineafan4edb7762022-06-26 19:21:04 +0100981
982export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100983 type: string;
984 guild: string;
985 user: string;
986 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100987 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100988 occurredAt: Date;
989 before: string | null;
990 after: string | null;
991 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100992}
993
994export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100995 guild: string;
996 user: string;
pineafan3a02ea32022-08-11 21:35:04 +0100997 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100998}
999
pineafan73a7c4a2022-07-24 10:38:04 +01001000export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001001 user: string;
1002 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001003 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -05001004 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001005}