blob: 62aee7c095b0890d241b59dad1a87e32a0822b35 [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import {
2 ButtonStyle,
3 CommandInteraction,
4 ComponentType,
5 GuildMember,
6 Message,
7 MessageComponentInteraction
8} from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +01009import type Discord from "discord.js";
10import { Collection, MongoClient } from "mongodb";
pineafana2e39c72023-02-21 18:37:32 +000011import config from "../config/main.js";
TheCodedProf633866f2023-02-03 17:06:00 -050012import client from "../utils/client.js";
TheCodedProf088b1b22023-02-28 17:31:11 -050013import * as crypto from "crypto";
TheCodedProff8ef7942023-03-03 15:32:32 -050014import _ from "lodash";
Skyler Greyda16adf2023-03-05 10:22:12 +000015import defaultData from "../config/default.js";
TheCodedProf75276572023-03-04 13:49:16 -050016
pineafan6de4da52023-03-07 20:43:44 +000017let username, password;
18
19// @ts-expect-error
20if (Object.keys(config.mongoOptions).includes("username")) username = encodeURIComponent(config.mongoOptions.username);
21// @ts-expect-error
22if (Object.keys(config.mongoOptions).includes("password")) password = encodeURIComponent(config.mongoOptions.password);
Samuel Shuertd66098b2023-03-04 14:05:26 -050023
Skyler Greyda16adf2023-03-05 10:22:12 +000024const mongoClient = new MongoClient(
25 username
pineafan6de4da52023-03-07 20:43:44 +000026 ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT&authSource=${config.mongoOptions.authSource}`
27 : `mongodb://${config.mongoOptions.host}`
Skyler Greyda16adf2023-03-05 10:22:12 +000028);
pineafan63fc5e22022-08-04 22:04:10 +010029await mongoClient.connect();
TheCodedProf8d577fa2023-03-01 13:06:40 -050030const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010031
TheCodedProf78b90332023-03-04 14:02:21 -050032const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" };
TheCodedProf75c51be2023-03-03 17:18:18 -050033const getIV = () => crypto.randomBytes(16);
TheCodedProffaae5332023-03-01 18:16:05 -050034
pineafan4edb7762022-06-26 19:21:04 +010035export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010036 guilds: Collection<GuildConfig>;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050037 oldGuilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050038 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010039
40 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010041 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050042 this.defaultData = defaultData;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050043 this.oldGuilds = database.collection<GuildConfig>("oldGuilds");
44 }
45
46 async readOld(guild: string): Promise<Partial<GuildConfig>> {
47 // console.log("Guild read")
48 const entry = await this.oldGuilds.findOne({ id: guild });
49 return entry ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010050 }
51
TheCodedProfb7a7b992023-03-05 16:11:59 -050052 async updateAllGuilds() {
53 const guilds = await this.guilds.find().toArray();
54 for (const guild of guilds) {
55 let guildObj;
56 try {
57 guildObj = await client.guilds.fetch(guild.id);
58 } catch (e) {
59 guildObj = null;
60 }
Skyler Grey67691762023-03-06 09:58:19 +000061 if (!guildObj) await this.delete(guild.id);
TheCodedProfb7a7b992023-03-05 16:11:59 -050062 }
63 }
64
Skyler Greyad002172022-08-16 18:48:26 +010065 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050066 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010067 const entry = await this.guilds.findOne({ id: guild });
TheCodedProf9f4cf9f2023-03-04 14:18:19 -050068 const data = _.cloneDeep(this.defaultData);
TheCodedProff8ef7942023-03-03 15:32:32 -050069 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010070 }
71
Skyler Grey11236ba2022-08-08 21:13:33 +010072 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050073 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010074 // eslint-disable-next-line @typescript-eslint/no-explicit-any
75 const uo: Record<string, any> = {};
76 if (!Array.isArray(unset)) unset = [unset];
77 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010078 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010079 }
Skyler Grey75ea9172022-08-06 10:22:23 +010080 const out = { $set: {}, $unset: {} };
81 if (set) out.$set = set;
82 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010083 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010084 }
85
pineafan63fc5e22022-08-04 22:04:10 +010086 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010087 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050088 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010089 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010090 await this.guilds.updateOne(
91 { id: guild },
92 {
93 $addToSet: { [key]: { $each: value } }
94 },
95 { upsert: true }
96 );
pineafan6702cef2022-06-13 17:52:37 +010097 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010098 await this.guilds.updateOne(
99 { id: guild },
100 {
101 $addToSet: { [key]: value }
102 },
103 { upsert: true }
104 );
pineafan6702cef2022-06-13 17:52:37 +0100105 }
106 }
107
Skyler Grey75ea9172022-08-06 10:22:23 +0100108 async remove(
109 guild: string,
110 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +0100111 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +0100112 value: any,
113 innerKey?: string | null
114 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500115 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +0100116 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100117 await this.guilds.updateOne(
118 { id: guild },
119 {
120 $pull: { [key]: { [innerKey]: { $eq: value } } }
121 },
122 { upsert: true }
123 );
pineafan0bc04162022-07-25 17:22:26 +0100124 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100125 await this.guilds.updateOne(
126 { id: guild },
127 {
128 $pullAll: { [key]: value }
129 },
130 { upsert: true }
131 );
pineafan6702cef2022-06-13 17:52:37 +0100132 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100133 await this.guilds.updateOne(
134 { id: guild },
135 {
136 $pullAll: { [key]: [value] }
137 },
138 { upsert: true }
139 );
pineafan6702cef2022-06-13 17:52:37 +0100140 }
pineafan6fb3e072022-05-20 19:27:23 +0100141 }
pineafane23c4ec2022-07-27 21:56:27 +0100142
143 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500144 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100145 await this.guilds.deleteOne({ id: guild });
146 }
pineafan6fb3e072022-05-20 19:27:23 +0100147}
148
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500149interface TranscriptEmbed {
150 title?: string;
151 description?: string;
152 fields?: {
153 name: string;
154 value: string;
155 inline: boolean;
156 }[];
157 footer?: {
158 text: string;
159 iconURL?: string;
160 };
TheCodedProffaae5332023-03-01 18:16:05 -0500161 color?: number;
162 timestamp?: string;
163 author?: {
164 name: string;
165 iconURL?: string;
166 url?: string;
167 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500168}
169
170interface TranscriptComponent {
171 type: number;
172 style?: ButtonStyle;
173 label?: string;
174 description?: string;
175 placeholder?: string;
176 emojiURL?: string;
177}
178
179interface TranscriptAuthor {
180 username: string;
181 discriminator: number;
182 nickname?: string;
183 id: string;
184 iconURL?: string;
185 topRole: {
186 color: number;
187 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500188 };
189 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500190}
191
192interface TranscriptAttachment {
193 url: string;
194 filename: string;
195 size: number;
196 log?: string;
197}
198
199interface TranscriptMessage {
200 id: string;
201 author: TranscriptAuthor;
202 content?: string;
203 embeds?: TranscriptEmbed[];
204 components?: TranscriptComponent[][];
205 editedTimestamp?: number;
206 createdTimestamp: number;
207 flags?: string[];
208 attachments?: TranscriptAttachment[];
209 stickerURLs?: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000210 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500211}
212
213interface TranscriptSchema {
214 code: string;
215 for: TranscriptAuthor;
Skyler Greyda16adf2023-03-05 10:22:12 +0000216 type: "ticket" | "purge";
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500217 guild: string;
218 channel: string;
219 messages: TranscriptMessage[];
220 createdTimestamp: number;
221 createdBy: TranscriptAuthor;
222}
223
Skyler Greyda16adf2023-03-05 10:22:12 +0000224interface findDocSchema {
225 channelID: string;
226 messageID: string;
pineafan6de4da52023-03-07 20:43:44 +0000227 transcript: string;
Skyler Greyda16adf2023-03-05 10:22:12 +0000228}
TheCodedProf003160f2023-03-04 17:09:40 -0500229
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500230export class Transcript {
231 transcripts: Collection<TranscriptSchema>;
TheCodedProf003160f2023-03-04 17:09:40 -0500232 messageToTranscript: Collection<findDocSchema>;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500233
234 constructor() {
235 this.transcripts = database.collection<TranscriptSchema>("transcripts");
TheCodedProf003160f2023-03-04 17:09:40 -0500236 this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
237 }
238
239 async upload(data: findDocSchema) {
240 // console.log("Transcript upload")
241 await this.messageToTranscript.insertOne(data);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500242 }
243
244 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500245 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500246 let code;
247 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500248 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500249 } while (await this.transcripts.findOne({ code: code }));
Skyler Greyda16adf2023-03-05 10:22:12 +0000250 const key = crypto
251 .randomBytes(32 ** 2)
252 .toString("base64")
253 .replace(/=/g, "")
254 .replace(/\//g, "_")
255 .replace(/\+/g, "-")
256 .substring(0, 32);
Skyler Grey67691762023-03-06 09:58:19 +0000257 const iv = getIV()
258 .toString("base64")
259 .substring(0, 16)
260 .replace(/=/g, "")
261 .replace(/\//g, "_")
262 .replace(/\+/g, "-");
263 console.log(iv);
Skyler Greyda16adf2023-03-05 10:22:12 +0000264 for (const message of transcript.messages) {
265 if (message.content) {
TheCodedProf75c51be2023-03-03 17:18:18 -0500266 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
267 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
268 }
269 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500270
TheCodedProffaae5332023-03-01 18:16:05 -0500271 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Skyler Greyda16adf2023-03-05 10:22:12 +0000272 if (doc.acknowledged) {
273 client.database.eventScheduler.schedule(
274 "deleteTranscript",
275 (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
276 { guild: transcript.guild, code: code, iv: iv, key: key }
277 );
TheCodedProf003160f2023-03-04 17:09:40 -0500278 return [code, key, iv];
Skyler Greyda16adf2023-03-05 10:22:12 +0000279 } else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500280 }
281
TheCodedProf003160f2023-03-04 17:09:40 -0500282 async delete(code: string) {
283 // console.log("Transcript delete")
284 await this.transcripts.deleteOne({ code: code });
TheCodedProf75c51be2023-03-03 17:18:18 -0500285 }
286
287 async deleteAll(guild: string) {
288 // console.log("Transcript delete")
289 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
290 for (const doc of filteredDocs) {
291 await this.transcripts.deleteOne({ code: doc.code });
292 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500293 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500294
TheCodedProf003160f2023-03-04 17:09:40 -0500295 async readEncrypted(code: string) {
296 // console.log("Transcript read")
297 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
298 let findDoc: findDocSchema | null = null;
pineafan6de4da52023-03-07 20:43:44 +0000299 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000300 if (findDoc) {
301 const message = await (
302 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
303 )?.messages.fetch(findDoc.messageID);
304 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500305 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000306 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500307 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000308 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500309 const reader = transcript.getReader();
310 let data: Uint8Array | null = null;
311 let allPacketsReceived = false;
312 while (!allPacketsReceived) {
313 const { value, done } = await reader.read();
Skyler Greyda16adf2023-03-05 10:22:12 +0000314 if (done) {
315 allPacketsReceived = true;
316 continue;
317 }
318 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500319 data = value;
320 } else {
321 data = new Uint8Array(Buffer.concat([data, value]));
322 }
323 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000324 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000325 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500326 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000327 if (!doc) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500328 return doc;
329 }
330
331 async read(code: string, key: string, iv: string) {
Skyler Grey67691762023-03-06 09:58:19 +0000332 console.log("Transcript read");
TheCodedProf003160f2023-03-04 17:09:40 -0500333 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
334 let findDoc: findDocSchema | null = null;
Skyler Grey67691762023-03-06 09:58:19 +0000335 console.log(doc);
pineafan6de4da52023-03-07 20:43:44 +0000336 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000337 if (findDoc) {
338 const message = await (
339 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
340 )?.messages.fetch(findDoc.messageID);
341 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500342 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000343 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500344 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000345 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500346 const reader = transcript.getReader();
347 let data: Uint8Array | null = null;
348 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
Skyler Greyda16adf2023-03-05 10:22:12 +0000349 while (true) {
TheCodedProf003160f2023-03-04 17:09:40 -0500350 const { value, done } = await reader.read();
351 if (done) break;
Skyler Greyda16adf2023-03-05 10:22:12 +0000352 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500353 data = value;
354 } else {
355 data = new Uint8Array(Buffer.concat([data, value]));
356 }
357 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000358 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000359 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500360 }
Skyler Grey67691762023-03-06 09:58:19 +0000361 console.log(doc);
Skyler Greyda16adf2023-03-05 10:22:12 +0000362 if (!doc) return null;
363 for (const message of doc.messages) {
364 if (message.content) {
TheCodedProf003160f2023-03-04 17:09:40 -0500365 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
366 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
367 }
368 }
369 return doc;
370 }
371
Skyler Greyda16adf2023-03-05 10:22:12 +0000372 async createTranscript(
Skyler Greye0c511b2023-03-06 10:30:17 +0000373 type: "ticket" | "purge",
Skyler Greyda16adf2023-03-05 10:22:12 +0000374 messages: Message[],
375 interaction: MessageComponentInteraction | CommandInteraction,
376 member: GuildMember
377 ) {
378 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500379 const newOut: Omit<TranscriptSchema, "code"> = {
Skyler Greye0c511b2023-03-06 10:30:17 +0000380 type: type,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500381 for: {
382 username: member!.user.username,
383 discriminator: parseInt(member!.user.discriminator),
384 id: member!.user.id,
385 topRole: {
386 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500387 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000388 iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500389 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500390 },
391 guild: interaction.guild!.id,
392 channel: interaction.channel!.id,
393 messages: [],
394 createdTimestamp: Date.now(),
395 createdBy: {
396 username: interaction.user.username,
397 discriminator: parseInt(interaction.user.discriminator),
398 id: interaction.user.id,
399 topRole: {
400 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500401 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000402 iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500403 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500404 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000405 };
406 if (member.nickname) newOut.for.nickname = member.nickname;
407 if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500408 messages.reverse().forEach((message) => {
409 const msg: TranscriptMessage = {
410 id: message.id,
411 author: {
412 username: message.author.username,
413 discriminator: parseInt(message.author.discriminator),
414 id: message.author.id,
415 topRole: {
Skyler Greya0c70242023-03-06 09:56:21 +0000416 color: message.member ? message.member.roles.highest.color : 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500417 },
TheCodedProfe92b9b52023-03-06 17:07:34 -0500418 iconURL: (message.member?.user ?? message.author).displayAvatarURL({ forceStatic: true }),
Skyler Grey67691762023-03-06 09:58:19 +0000419 bot: message.author.bot || false
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500420 },
421 createdTimestamp: message.createdTimestamp
422 };
Skyler Greyda16adf2023-03-05 10:22:12 +0000423 if (message.member?.nickname) msg.author.nickname = message.member.nickname;
Skyler Greya0c70242023-03-06 09:56:21 +0000424 if (message.member?.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500425 if (message.content) msg.content = message.content;
Skyler Greyda16adf2023-03-05 10:22:12 +0000426 if (message.embeds.length > 0)
427 msg.embeds = message.embeds.map((embed) => {
428 const obj: TranscriptEmbed = {};
429 if (embed.title) obj.title = embed.title;
430 if (embed.description) obj.description = embed.description;
431 if (embed.fields.length > 0)
432 obj.fields = embed.fields.map((field) => {
433 return {
434 name: field.name,
435 value: field.value,
436 inline: field.inline ?? false
437 };
438 });
439 if (embed.color) obj.color = embed.color;
440 if (embed.timestamp) obj.timestamp = embed.timestamp;
441 if (embed.footer)
442 obj.footer = {
443 text: embed.footer.text
444 };
445 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
446 if (embed.author)
447 obj.author = {
448 name: embed.author.name
449 };
450 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
451 if (embed.author?.url) obj.author!.url = embed.author.url;
452 return obj;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500453 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000454 if (message.components.length > 0)
455 msg.components = message.components.map((component) =>
456 component.components.map((child) => {
457 const obj: TranscriptComponent = {
458 type: child.type
459 };
460 if (child.type === ComponentType.Button) {
461 obj.style = child.style;
462 obj.label = child.label ?? "";
463 } else if (child.type > 2) {
464 obj.placeholder = child.placeholder ?? "";
465 }
466 return obj;
467 })
468 );
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500469 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
470 msg.flags = message.flags.toArray();
471
Skyler Greyda16adf2023-03-05 10:22:12 +0000472 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
473 if (message.reference)
474 msg.referencedMessage = [
475 message.reference.guildId ?? "",
476 message.reference.channelId,
477 message.reference.messageId ?? ""
478 ];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500479 newOut.messages.push(msg);
480 });
481 return newOut;
482 }
483
484 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
485 let out = "";
486 for (const message of transcript.messages) {
487 if (message.referencedMessage) {
488 if (Array.isArray(message.referencedMessage)) {
489 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
Skyler Greyda16adf2023-03-05 10:22:12 +0000490 } else out += `> [Reply To] ${message.referencedMessage}\n`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500491 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000492 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
493 message.author.id
494 }) (${message.id})`;
TheCodedProff8ef7942023-03-03 15:32:32 -0500495 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
496 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500497 out += "\n";
498 if (message.content) out += `[Content]\n${message.content}\n\n`;
499 if (message.embeds) {
500 for (const embed of message.embeds) {
501 out += `[Embed]\n`;
502 if (embed.title) out += `| Title: ${embed.title}\n`;
503 if (embed.description) out += `| Description: ${embed.description}\n`;
504 if (embed.fields) {
505 for (const field of embed.fields) {
506 out += `| Field: ${field.name} - ${field.value}\n`;
507 }
508 }
509 if (embed.footer) {
510 out += `|Footer: ${embed.footer.text}\n`;
511 }
512 out += "\n";
513 }
514 }
515 if (message.components) {
516 for (const component of message.components) {
517 out += `[Component]\n`;
518 for (const button of component) {
519 out += `| Button: ${button.label ?? button.description}\n`;
520 }
521 out += "\n";
522 }
523 }
524 if (message.attachments) {
525 for (const attachment of message.attachments) {
526 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
527 }
528 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000529 out += "\n\n";
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500530 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000531 return out;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500532 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500533}
534
pineafan4edb7762022-06-26 19:21:04 +0100535export class History {
536 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100537
pineafan3a02ea32022-08-11 21:35:04 +0100538 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100539 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100540 }
541
Skyler Grey75ea9172022-08-06 10:22:23 +0100542 async create(
543 type: string,
544 guild: string,
545 user: Discord.User,
546 moderator: Discord.User | null,
547 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100548 before?: string | null,
549 after?: string | null,
550 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100551 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500552 // console.log("History create");
Skyler Greyda16adf2023-03-05 10:22:12 +0000553 await this.histories.insertOne(
554 {
555 type: type,
556 guild: guild,
557 user: user.id,
558 moderator: moderator ? moderator.id : null,
559 reason: reason,
560 occurredAt: new Date(),
561 before: before ?? null,
562 after: after ?? null,
563 amount: amount ?? null
564 },
565 collectionOptions
566 );
pineafan4edb7762022-06-26 19:21:04 +0100567 }
568
569 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500570 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100571 const entry = (await this.histories
572 .find({
573 guild: guild,
574 user: user,
575 occurredAt: {
576 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
577 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
578 }
579 })
580 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100581 return entry;
582 }
pineafane23c4ec2022-07-27 21:56:27 +0100583
584 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500585 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100586 await this.histories.deleteMany({ guild: guild });
587 }
pineafan4edb7762022-06-26 19:21:04 +0100588}
589
TheCodedProfb5e9d552023-01-29 15:43:26 -0500590interface ScanCacheSchema {
591 addedAt: Date;
592 hash: string;
593 data: boolean;
594 tags: string[];
595}
596
597export class ScanCache {
598 scanCache: Collection<ScanCacheSchema>;
599
600 constructor() {
601 this.scanCache = database.collection<ScanCacheSchema>("scanCache");
602 }
603
604 async read(hash: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500605 // console.log("ScanCache read");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500606 return await this.scanCache.findOne({ hash: hash });
607 }
608
609 async write(hash: string, data: boolean, tags?: string[]) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500610 // console.log("ScanCache write");
Skyler Greyda16adf2023-03-05 10:22:12 +0000611 await this.scanCache.insertOne(
612 { hash: hash, data: data, tags: tags ?? [], addedAt: new Date() },
613 collectionOptions
614 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500615 }
616
617 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500618 // console.log("ScanCache cleanup");
Skyler Greyda16adf2023-03-05 10:22:12 +0000619 await this.scanCache.deleteMany({
620 addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
621 hash: { $not$text: "http" }
622 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500623 }
624}
625
PineaFan538d3752023-01-12 21:48:23 +0000626export class PerformanceTest {
627 performanceData: Collection<PerformanceDataSchema>;
628
629 constructor() {
630 this.performanceData = database.collection<PerformanceDataSchema>("performance");
631 }
632
633 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500634 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000635 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500636 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000637 }
638 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500639 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000640 return await this.performanceData.find({}).toArray();
641 }
642}
643
644export interface PerformanceDataSchema {
645 timestamp?: Date;
646 discord: number;
647 databaseRead: number;
648 resources: {
649 cpu: number;
650 memory: number;
651 temperature: number;
Skyler Greyda16adf2023-03-05 10:22:12 +0000652 };
PineaFan538d3752023-01-12 21:48:23 +0000653}
654
pineafan4edb7762022-06-26 19:21:04 +0100655export class ModNotes {
656 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100657
pineafan3a02ea32022-08-11 21:35:04 +0100658 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100659 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100660 }
661
662 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500663 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100664 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100665 }
666
667 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500668 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100669 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100670 return entry?.note ?? null;
671 }
TheCodedProf267563a2023-01-21 17:00:57 -0500672
673 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500674 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500675 await this.modNotes.deleteMany({ guild: guild });
676 }
pineafan4edb7762022-06-26 19:21:04 +0100677}
678
pineafan73a7c4a2022-07-24 10:38:04 +0100679export class Premium {
680 premium: Collection<PremiumSchema>;
Skyler Greyda16adf2023-03-05 10:22:12 +0000681 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
682 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100683
pineafan3a02ea32022-08-11 21:35:04 +0100684 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100685 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500686 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100687 }
688
TheCodedProf633866f2023-02-03 17:06:00 -0500689 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500690 // console.log("Premium updateUser");
Skyler Greyda16adf2023-03-05 10:22:12 +0000691 if (!(await this.userExists(user))) await this.createUser(user, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500692 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
693 }
694
695 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500696 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500697 const entry = await this.premium.findOne({ user: user });
698 return entry ? true : false;
699 }
TheCodedProf633866f2023-02-03 17:06:00 -0500700 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500701 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500702 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500703 }
704
TheCodedProfaa3fe992023-02-25 21:53:09 -0500705 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500706 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500707 // [Has premium, user giving premium, level, is mod: if given automatically]
708 const cached = this.cache.get(guild);
709 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500710 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000711 const members = (await client.guilds.fetch(guild)).members.cache;
712 for (const { user } of entries) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500713 const member = members.get(user);
Skyler Greyda16adf2023-03-05 10:22:12 +0000714 if (member) {
715 //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 -0500716 const modPerms = //TODO: Create list in config for perms
Skyler Greyda16adf2023-03-05 10:22:12 +0000717 member.permissions.has("Administrator") ||
718 member.permissions.has("ManageChannels") ||
719 member.permissions.has("ManageRoles") ||
720 member.permissions.has("ManageEmojisAndStickers") ||
721 member.permissions.has("ManageWebhooks") ||
722 member.permissions.has("ManageGuild") ||
723 member.permissions.has("KickMembers") ||
724 member.permissions.has("BanMembers") ||
725 member.permissions.has("ManageEvents") ||
726 member.permissions.has("ManageMessages") ||
727 member.permissions.has("ManageThreads");
728 const entry = entries.find((e) => e.user === member.id);
729 if (entry && entry.level === 3 && modPerms) {
730 this.cache.set(guild, [
731 true,
732 member.id,
733 entry.level,
734 true,
735 new Date(Date.now() + this.cacheTimeout)
736 ]);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500737 return [true, member.id, entry.level, true];
738 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500739 }
740 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100741 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500742 appliesTo: {
743 $elemMatch: {
744 $eq: guild
745 }
746 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100747 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000748 this.cache.set(guild, [
749 entry ? true : false,
750 entry?.user ?? "",
751 entry?.level ?? 0,
752 false,
753 new Date(Date.now() + this.cacheTimeout)
754 ]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500755 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500756 }
757
TheCodedProf633866f2023-02-03 17:06:00 -0500758 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500759 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500760 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500761 if (!entry) return null;
762 return entry;
763 }
764
TheCodedProf94ff6de2023-02-22 17:47:26 -0500765 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500766 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500767 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000768 if (member) {
769 const entry = entries.find((e) => e.user === member.id);
770 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500771 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000772 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500773 }
774 const roles = member.roles;
775 let level = 0;
776 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500777 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500778 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500779 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500780 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500781 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500782 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500783 level = 3;
784 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500785 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500786 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000787 await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
TheCodedProf633866f2023-02-03 17:06:00 -0500788 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000789 await this.premium.updateOne(
790 { user: member.id },
791 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
792 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500793 }
794 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000795 const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
796 for (const { roles, id } of members.values()) {
797 const entry = entries.find((e) => e.user === id);
798 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500799 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000800 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500801 }
802 let level: number = 0;
803 if (roles.cache.has("1066468879309750313")) {
804 level = 99;
805 } else if (roles.cache.has("1066465491713003520")) {
806 level = 1;
807 } else if (roles.cache.has("1066439526496604194")) {
808 level = 2;
809 } else if (roles.cache.has("1066464134322978912")) {
810 level = 3;
811 }
812 await this.updateUser(id, level);
813 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000814 await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
TheCodedProf94ff6de2023-02-22 17:47:26 -0500815 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000816 await this.premium.updateOne(
817 { user: id },
818 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
819 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500820 }
TheCodedProf633866f2023-02-03 17:06:00 -0500821 }
822 }
TheCodedProf267563a2023-01-21 17:00:57 -0500823 }
824
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500825 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500826 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500827 const { level } = (await this.fetchUser(user))!;
828 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500829 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100830 }
TheCodedProffc420b72023-01-24 17:14:38 -0500831
TheCodedProf48865eb2023-03-05 15:25:25 -0500832 async removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500833 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500834 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf48865eb2023-03-05 15:25:25 -0500835 return await this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
TheCodedProffc420b72023-01-24 17:14:38 -0500836 }
pineafan4edb7762022-06-26 19:21:04 +0100837}
838
pineafan6de4da52023-03-07 20:43:44 +0000839export class Plugins {
840
841}
842
pineafan6fb3e072022-05-20 19:27:23 +0100843export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100844 id: string;
845 version: number;
PineaFan100df682023-01-02 13:26:08 +0000846 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100847 filters: {
848 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100849 NSFW: boolean;
850 size: boolean;
851 };
852 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100853 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100854 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100855 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100856 strict: string[];
857 loose: string[];
858 };
pineafan6fb3e072022-05-20 19:27:23 +0100859 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100860 users: string[];
861 roles: string[];
862 channels: string[];
863 };
864 };
pineafan6fb3e072022-05-20 19:27:23 +0100865 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100866 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000867 allowed: {
868 channels: string[];
869 roles: string[];
870 users: string[];
871 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100872 };
pineafan6fb3e072022-05-20 19:27:23 +0100873 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100874 mass: number;
875 everyone: boolean;
876 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100877 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100878 roles: string[];
879 rolesToMention: string[];
880 users: string[];
881 channels: string[];
882 };
883 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500884 clean: {
885 channels: string[];
886 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500887 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500888 roles: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000889 };
890 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100891 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500892 autoPublish: {
893 enabled: boolean;
894 channels: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000895 };
pineafan6fb3e072022-05-20 19:27:23 +0100896 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100897 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100898 role: string | null;
899 ping: string | null;
900 channel: string | null;
901 message: string | null;
902 };
903 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100904 logging: {
905 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100906 enabled: boolean;
907 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100908 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100909 };
pineafan6fb3e072022-05-20 19:27:23 +0100910 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100911 channel: string | null;
912 };
pineafan73a7c4a2022-07-24 10:38:04 +0100913 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100914 channel: string | null;
915 saved: Record<string, string>;
916 };
917 };
pineafan6fb3e072022-05-20 19:27:23 +0100918 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000919 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100920 role: string | null;
921 };
pineafan6fb3e072022-05-20 19:27:23 +0100922 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100923 enabled: boolean;
924 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100925 types: string;
926 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100927 useCustom: boolean;
928 supportRole: string | null;
929 maxTickets: number;
930 };
pineafan6fb3e072022-05-20 19:27:23 +0100931 moderation: {
932 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100933 timeout: boolean;
934 role: string | null;
935 text: string | null;
936 link: string | null;
937 };
pineafan6fb3e072022-05-20 19:27:23 +0100938 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100939 text: string | null;
940 link: string | null;
941 };
pineafan6fb3e072022-05-20 19:27:23 +0100942 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100943 text: string | null;
944 link: string | null;
945 };
pineafan6fb3e072022-05-20 19:27:23 +0100946 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100947 text: string | null;
948 link: string | null;
949 };
pineafan6fb3e072022-05-20 19:27:23 +0100950 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100951 text: string | null;
952 link: string | null;
953 };
pineafan6fb3e072022-05-20 19:27:23 +0100954 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100955 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500956 text: null;
957 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100958 };
PineaFane6ba7882023-01-18 20:41:16 +0000959 nick: {
960 text: string | null;
961 link: string | null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000962 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100963 };
pineafan6fb3e072022-05-20 19:27:23 +0100964 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100965 name: string;
966 retainPrevious: boolean;
967 nullable: boolean;
968 track: string[];
969 manageableBy: string[];
970 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100971 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100972 enabled: boolean;
973 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100974 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100975 name: string;
976 description: string;
977 min: number;
978 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100979 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100980 name: string;
981 description: string | null;
982 role: string;
983 }[];
984 }[];
985 };
986 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100987}
pineafan4edb7762022-06-26 19:21:04 +0100988
989export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100990 type: string;
991 guild: string;
992 user: string;
993 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100994 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100995 occurredAt: Date;
996 before: string | null;
997 after: string | null;
998 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100999}
1000
1001export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001002 guild: string;
1003 user: string;
pineafan3a02ea32022-08-11 21:35:04 +01001004 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +01001005}
1006
pineafan73a7c4a2022-07-24 10:38:04 +01001007export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001008 user: string;
1009 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001010 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -05001011 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001012}