blob: a107d06dee2c213f8bbc93817b86988cfb3f0195 [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import {
2 ButtonStyle,
3 CommandInteraction,
4 ComponentType,
5 GuildMember,
6 Message,
7 MessageComponentInteraction
8} from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +01009import type Discord from "discord.js";
10import { Collection, MongoClient } from "mongodb";
pineafana2e39c72023-02-21 18:37:32 +000011import config from "../config/main.js";
TheCodedProf633866f2023-02-03 17:06:00 -050012import client from "../utils/client.js";
TheCodedProf088b1b22023-02-28 17:31:11 -050013import * as crypto from "crypto";
TheCodedProff8ef7942023-03-03 15:32:32 -050014import _ from "lodash";
Skyler Greyda16adf2023-03-05 10:22:12 +000015import defaultData from "../config/default.js";
TheCodedProf75276572023-03-04 13:49:16 -050016
pineafan6de4da52023-03-07 20:43:44 +000017let username, password;
18
Skyler Grey5b78b422023-03-07 22:36:20 +000019if ("username" in config.mongoOptions) username = encodeURIComponent(config.mongoOptions.username as string);
20if ("password" in config.mongoOptions) password = encodeURIComponent(config.mongoOptions.password as string);
Samuel Shuertd66098b2023-03-04 14:05:26 -050021
Skyler Greyda16adf2023-03-05 10:22:12 +000022const mongoClient = new MongoClient(
23 username
pineafan6de4da52023-03-07 20:43:44 +000024 ? `mongodb://${username}:${password}@${config.mongoOptions.host}?authMechanism=DEFAULT&authSource=${config.mongoOptions.authSource}`
25 : `mongodb://${config.mongoOptions.host}`
Skyler Greyda16adf2023-03-05 10:22:12 +000026);
pineafan63fc5e22022-08-04 22:04:10 +010027await mongoClient.connect();
TheCodedProf8d577fa2023-03-01 13:06:40 -050028const database = mongoClient.db();
pineafan6fb3e072022-05-20 19:27:23 +010029
TheCodedProf78b90332023-03-04 14:02:21 -050030const collectionOptions = { authdb: config.mongoOptions.authSource, w: "majority" };
TheCodedProf75c51be2023-03-03 17:18:18 -050031const getIV = () => crypto.randomBytes(16);
TheCodedProffaae5332023-03-01 18:16:05 -050032
pineafan4edb7762022-06-26 19:21:04 +010033export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010034 guilds: Collection<GuildConfig>;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050035 oldGuilds: Collection<GuildConfig>;
TheCodedProff8ef7942023-03-03 15:32:32 -050036 defaultData: GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010037
38 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010039 this.guilds = database.collection<GuildConfig>("guilds");
TheCodedProff8ef7942023-03-03 15:32:32 -050040 this.defaultData = defaultData;
TheCodedProf8a2d7cd2023-03-05 14:53:59 -050041 this.oldGuilds = database.collection<GuildConfig>("oldGuilds");
42 }
43
44 async readOld(guild: string): Promise<Partial<GuildConfig>> {
45 // console.log("Guild read")
46 const entry = await this.oldGuilds.findOne({ id: guild });
47 return entry ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010048 }
49
TheCodedProfb7a7b992023-03-05 16:11:59 -050050 async updateAllGuilds() {
51 const guilds = await this.guilds.find().toArray();
52 for (const guild of guilds) {
53 let guildObj;
54 try {
55 guildObj = await client.guilds.fetch(guild.id);
56 } catch (e) {
57 guildObj = null;
58 }
Skyler Grey67691762023-03-06 09:58:19 +000059 if (!guildObj) await this.delete(guild.id);
TheCodedProfb7a7b992023-03-05 16:11:59 -050060 }
61 }
62
Skyler Greyad002172022-08-16 18:48:26 +010063 async read(guild: string): Promise<GuildConfig> {
TheCodedProff8ef7942023-03-03 15:32:32 -050064 // console.log("Guild read")
pineafan63fc5e22022-08-04 22:04:10 +010065 const entry = await this.guilds.findOne({ id: guild });
TheCodedProf9f4cf9f2023-03-04 14:18:19 -050066 const data = _.cloneDeep(this.defaultData);
TheCodedProff8ef7942023-03-03 15:32:32 -050067 return _.merge(data, entry ?? {});
pineafan6fb3e072022-05-20 19:27:23 +010068 }
69
Skyler Grey11236ba2022-08-08 21:13:33 +010070 async write(guild: string, set: object | null, unset: string[] | string = []) {
TheCodedProff8ef7942023-03-03 15:32:32 -050071 // console.log("Guild write")
pineafan63fc5e22022-08-04 22:04:10 +010072 // eslint-disable-next-line @typescript-eslint/no-explicit-any
73 const uo: Record<string, any> = {};
74 if (!Array.isArray(unset)) unset = [unset];
75 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010076 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010077 }
Skyler Grey75ea9172022-08-06 10:22:23 +010078 const out = { $set: {}, $unset: {} };
79 if (set) out.$set = set;
80 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010081 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010082 }
83
pineafan63fc5e22022-08-04 22:04:10 +010084 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010085 async append(guild: string, key: string, value: any) {
TheCodedProff8ef7942023-03-03 15:32:32 -050086 // console.log("Guild append")
pineafan6702cef2022-06-13 17:52:37 +010087 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010088 await this.guilds.updateOne(
89 { id: guild },
90 {
91 $addToSet: { [key]: { $each: value } }
92 },
93 { upsert: true }
94 );
pineafan6702cef2022-06-13 17:52:37 +010095 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010096 await this.guilds.updateOne(
97 { id: guild },
98 {
99 $addToSet: { [key]: value }
100 },
101 { upsert: true }
102 );
pineafan6702cef2022-06-13 17:52:37 +0100103 }
104 }
105
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 async remove(
107 guild: string,
108 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +0100109 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +0100110 value: any,
111 innerKey?: string | null
112 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500113 // console.log("Guild remove")
pineafan02ba0232022-07-24 22:16:15 +0100114 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100115 await this.guilds.updateOne(
116 { id: guild },
117 {
118 $pull: { [key]: { [innerKey]: { $eq: value } } }
119 },
120 { upsert: true }
121 );
pineafan0bc04162022-07-25 17:22:26 +0100122 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100123 await this.guilds.updateOne(
124 { id: guild },
125 {
126 $pullAll: { [key]: value }
127 },
128 { upsert: true }
129 );
pineafan6702cef2022-06-13 17:52:37 +0100130 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100131 await this.guilds.updateOne(
132 { id: guild },
133 {
134 $pullAll: { [key]: [value] }
135 },
136 { upsert: true }
137 );
pineafan6702cef2022-06-13 17:52:37 +0100138 }
pineafan6fb3e072022-05-20 19:27:23 +0100139 }
pineafane23c4ec2022-07-27 21:56:27 +0100140
141 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500142 // console.log("Guild delete")
pineafane23c4ec2022-07-27 21:56:27 +0100143 await this.guilds.deleteOne({ id: guild });
144 }
pineafan6fb3e072022-05-20 19:27:23 +0100145}
146
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500147interface TranscriptEmbed {
148 title?: string;
149 description?: string;
150 fields?: {
151 name: string;
152 value: string;
153 inline: boolean;
154 }[];
155 footer?: {
156 text: string;
157 iconURL?: string;
158 };
TheCodedProffaae5332023-03-01 18:16:05 -0500159 color?: number;
160 timestamp?: string;
161 author?: {
162 name: string;
163 iconURL?: string;
164 url?: string;
165 };
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500166}
167
168interface TranscriptComponent {
169 type: number;
170 style?: ButtonStyle;
171 label?: string;
172 description?: string;
173 placeholder?: string;
174 emojiURL?: string;
175}
176
177interface TranscriptAuthor {
178 username: string;
179 discriminator: number;
180 nickname?: string;
181 id: string;
182 iconURL?: string;
183 topRole: {
184 color: number;
185 badgeURL?: string;
TheCodedProf088b1b22023-02-28 17:31:11 -0500186 };
187 bot: boolean;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500188}
189
190interface TranscriptAttachment {
191 url: string;
192 filename: string;
193 size: number;
194 log?: string;
195}
196
197interface TranscriptMessage {
198 id: string;
199 author: TranscriptAuthor;
200 content?: string;
201 embeds?: TranscriptEmbed[];
202 components?: TranscriptComponent[][];
203 editedTimestamp?: number;
204 createdTimestamp: number;
205 flags?: string[];
206 attachments?: TranscriptAttachment[];
207 stickerURLs?: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000208 referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500209}
210
211interface TranscriptSchema {
212 code: string;
213 for: TranscriptAuthor;
Skyler Greyda16adf2023-03-05 10:22:12 +0000214 type: "ticket" | "purge";
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500215 guild: string;
216 channel: string;
217 messages: TranscriptMessage[];
218 createdTimestamp: number;
219 createdBy: TranscriptAuthor;
220}
221
Skyler Greyda16adf2023-03-05 10:22:12 +0000222interface findDocSchema {
223 channelID: string;
224 messageID: string;
Skyler Grey5b78b422023-03-07 22:36:20 +0000225 code: string;
Skyler Greyda16adf2023-03-05 10:22:12 +0000226}
TheCodedProf003160f2023-03-04 17:09:40 -0500227
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500228export class Transcript {
229 transcripts: Collection<TranscriptSchema>;
TheCodedProf003160f2023-03-04 17:09:40 -0500230 messageToTranscript: Collection<findDocSchema>;
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500231
232 constructor() {
233 this.transcripts = database.collection<TranscriptSchema>("transcripts");
TheCodedProf003160f2023-03-04 17:09:40 -0500234 this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
235 }
236
237 async upload(data: findDocSchema) {
238 // console.log("Transcript upload")
239 await this.messageToTranscript.insertOne(data);
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500240 }
241
242 async create(transcript: Omit<TranscriptSchema, "code">) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500243 // console.log("Transcript create")
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500244 let code;
245 do {
TheCodedProf088b1b22023-02-28 17:31:11 -0500246 code = crypto.randomBytes(64).toString("base64").replace(/=/g, "").replace(/\//g, "_").replace(/\+/g, "-");
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500247 } while (await this.transcripts.findOne({ code: code }));
Skyler Greyda16adf2023-03-05 10:22:12 +0000248 const key = crypto
249 .randomBytes(32 ** 2)
250 .toString("base64")
251 .replace(/=/g, "")
252 .replace(/\//g, "_")
253 .replace(/\+/g, "-")
254 .substring(0, 32);
Skyler Grey67691762023-03-06 09:58:19 +0000255 const iv = getIV()
256 .toString("base64")
257 .substring(0, 16)
258 .replace(/=/g, "")
259 .replace(/\//g, "_")
260 .replace(/\+/g, "-");
261 console.log(iv);
Skyler Greyda16adf2023-03-05 10:22:12 +0000262 for (const message of transcript.messages) {
263 if (message.content) {
TheCodedProf75c51be2023-03-03 17:18:18 -0500264 const encCipher = crypto.createCipheriv("AES-256-CBC", key, iv);
265 message.content = encCipher.update(message.content, "utf8", "base64") + encCipher.final("base64");
266 }
267 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500268
TheCodedProffaae5332023-03-01 18:16:05 -0500269 const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
Skyler Greyda16adf2023-03-05 10:22:12 +0000270 if (doc.acknowledged) {
271 client.database.eventScheduler.schedule(
272 "deleteTranscript",
273 (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(),
274 { guild: transcript.guild, code: code, iv: iv, key: key }
275 );
TheCodedProf003160f2023-03-04 17:09:40 -0500276 return [code, key, iv];
Skyler Greyda16adf2023-03-05 10:22:12 +0000277 } else return [null, null, null];
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500278 }
279
TheCodedProf003160f2023-03-04 17:09:40 -0500280 async delete(code: string) {
281 // console.log("Transcript delete")
282 await this.transcripts.deleteOne({ code: code });
TheCodedProf75c51be2023-03-03 17:18:18 -0500283 }
284
285 async deleteAll(guild: string) {
286 // console.log("Transcript delete")
287 const filteredDocs = await this.transcripts.find({ guild: guild }).toArray();
288 for (const doc of filteredDocs) {
289 await this.transcripts.deleteOne({ code: doc.code });
290 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500291 }
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500292
TheCodedProf003160f2023-03-04 17:09:40 -0500293 async readEncrypted(code: string) {
294 // console.log("Transcript read")
295 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
296 let findDoc: findDocSchema | null = null;
pineafan6de4da52023-03-07 20:43:44 +0000297 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000298 if (findDoc) {
299 const message = await (
300 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
301 )?.messages.fetch(findDoc.messageID);
302 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500303 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000304 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500305 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000306 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500307 const reader = transcript.getReader();
308 let data: Uint8Array | null = null;
309 let allPacketsReceived = false;
310 while (!allPacketsReceived) {
311 const { value, done } = await reader.read();
Skyler Greyda16adf2023-03-05 10:22:12 +0000312 if (done) {
313 allPacketsReceived = true;
314 continue;
315 }
316 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500317 data = value;
318 } else {
319 data = new Uint8Array(Buffer.concat([data, value]));
320 }
321 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000322 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000323 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500324 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000325 if (!doc) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500326 return doc;
327 }
328
329 async read(code: string, key: string, iv: string) {
Skyler Grey67691762023-03-06 09:58:19 +0000330 console.log("Transcript read");
TheCodedProf003160f2023-03-04 17:09:40 -0500331 let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
332 let findDoc: findDocSchema | null = null;
Skyler Grey67691762023-03-06 09:58:19 +0000333 console.log(doc);
pineafan6de4da52023-03-07 20:43:44 +0000334 if (!doc) findDoc = await this.messageToTranscript.findOne({ transcript: code });
Skyler Greyda16adf2023-03-05 10:22:12 +0000335 if (findDoc) {
336 const message = await (
337 client.channels.cache.get(findDoc.channelID) as Discord.TextBasedChannel | null
338 )?.messages.fetch(findDoc.messageID);
339 if (!message) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500340 const attachment = message.attachments.first();
Skyler Greyda16adf2023-03-05 10:22:12 +0000341 if (!attachment) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500342 const transcript = (await fetch(attachment.url)).body;
Skyler Greyda16adf2023-03-05 10:22:12 +0000343 if (!transcript) return null;
TheCodedProf003160f2023-03-04 17:09:40 -0500344 const reader = transcript.getReader();
345 let data: Uint8Array | null = null;
346 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
Skyler Greyda16adf2023-03-05 10:22:12 +0000347 while (true) {
TheCodedProf003160f2023-03-04 17:09:40 -0500348 const { value, done } = await reader.read();
349 if (done) break;
Skyler Greyda16adf2023-03-05 10:22:12 +0000350 if (!data) {
TheCodedProf003160f2023-03-04 17:09:40 -0500351 data = value;
352 } else {
353 data = new Uint8Array(Buffer.concat([data, value]));
354 }
355 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000356 if (!data) return null;
Skyler Greycf771402023-03-05 07:06:37 +0000357 doc = JSON.parse(Buffer.from(data).toString()) as TranscriptSchema;
TheCodedProf003160f2023-03-04 17:09:40 -0500358 }
Skyler Grey67691762023-03-06 09:58:19 +0000359 console.log(doc);
Skyler Greyda16adf2023-03-05 10:22:12 +0000360 if (!doc) return null;
361 for (const message of doc.messages) {
362 if (message.content) {
TheCodedProf003160f2023-03-04 17:09:40 -0500363 const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
364 message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
365 }
366 }
367 return doc;
368 }
369
Skyler Greyda16adf2023-03-05 10:22:12 +0000370 async createTranscript(
Skyler Greye0c511b2023-03-06 10:30:17 +0000371 type: "ticket" | "purge",
Skyler Greyda16adf2023-03-05 10:22:12 +0000372 messages: Message[],
373 interaction: MessageComponentInteraction | CommandInteraction,
374 member: GuildMember
375 ) {
376 const interactionMember = await interaction.guild?.members.fetch(interaction.user.id);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500377 const newOut: Omit<TranscriptSchema, "code"> = {
Skyler Greye0c511b2023-03-06 10:30:17 +0000378 type: type,
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500379 for: {
380 username: member!.user.username,
381 discriminator: parseInt(member!.user.discriminator),
382 id: member!.user.id,
383 topRole: {
384 color: member!.roles.highest.color
TheCodedProf088b1b22023-02-28 17:31:11 -0500385 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000386 iconURL: member!.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500387 bot: member!.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500388 },
389 guild: interaction.guild!.id,
390 channel: interaction.channel!.id,
391 messages: [],
392 createdTimestamp: Date.now(),
393 createdBy: {
394 username: interaction.user.username,
395 discriminator: parseInt(interaction.user.discriminator),
396 id: interaction.user.id,
397 topRole: {
398 color: interactionMember?.roles.highest.color ?? 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500399 },
Skyler Greyda16adf2023-03-05 10:22:12 +0000400 iconURL: interaction.user.displayAvatarURL({ forceStatic: true }),
TheCodedProf088b1b22023-02-28 17:31:11 -0500401 bot: interaction.user.bot
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500402 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000403 };
404 if (member.nickname) newOut.for.nickname = member.nickname;
405 if (interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500406 messages.reverse().forEach((message) => {
407 const msg: TranscriptMessage = {
408 id: message.id,
409 author: {
410 username: message.author.username,
411 discriminator: parseInt(message.author.discriminator),
412 id: message.author.id,
413 topRole: {
Skyler Greya0c70242023-03-06 09:56:21 +0000414 color: message.member ? message.member.roles.highest.color : 0x000000
TheCodedProf088b1b22023-02-28 17:31:11 -0500415 },
TheCodedProfe92b9b52023-03-06 17:07:34 -0500416 iconURL: (message.member?.user ?? message.author).displayAvatarURL({ forceStatic: true }),
Skyler Grey67691762023-03-06 09:58:19 +0000417 bot: message.author.bot || false
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500418 },
419 createdTimestamp: message.createdTimestamp
420 };
Skyler Greyda16adf2023-03-05 10:22:12 +0000421 if (message.member?.nickname) msg.author.nickname = message.member.nickname;
Skyler Greya0c70242023-03-06 09:56:21 +0000422 if (message.member?.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500423 if (message.content) msg.content = message.content;
Skyler Greyda16adf2023-03-05 10:22:12 +0000424 if (message.embeds.length > 0)
425 msg.embeds = message.embeds.map((embed) => {
426 const obj: TranscriptEmbed = {};
427 if (embed.title) obj.title = embed.title;
428 if (embed.description) obj.description = embed.description;
429 if (embed.fields.length > 0)
430 obj.fields = embed.fields.map((field) => {
431 return {
432 name: field.name,
433 value: field.value,
434 inline: field.inline ?? false
435 };
436 });
437 if (embed.color) obj.color = embed.color;
438 if (embed.timestamp) obj.timestamp = embed.timestamp;
439 if (embed.footer)
440 obj.footer = {
441 text: embed.footer.text
442 };
443 if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL;
444 if (embed.author)
445 obj.author = {
446 name: embed.author.name
447 };
448 if (embed.author?.iconURL) obj.author!.iconURL = embed.author.iconURL;
449 if (embed.author?.url) obj.author!.url = embed.author.url;
450 return obj;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500451 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000452 if (message.components.length > 0)
453 msg.components = message.components.map((component) =>
454 component.components.map((child) => {
455 const obj: TranscriptComponent = {
456 type: child.type
457 };
458 if (child.type === ComponentType.Button) {
459 obj.style = child.style;
460 obj.label = child.label ?? "";
461 } else if (child.type > 2) {
462 obj.placeholder = child.placeholder ?? "";
463 }
464 return obj;
465 })
466 );
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500467 if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp;
468 msg.flags = message.flags.toArray();
469
Skyler Greyda16adf2023-03-05 10:22:12 +0000470 if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map((sticker) => sticker.url);
471 if (message.reference)
472 msg.referencedMessage = [
473 message.reference.guildId ?? "",
474 message.reference.channelId,
475 message.reference.messageId ?? ""
476 ];
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500477 newOut.messages.push(msg);
478 });
479 return newOut;
480 }
481
482 toHumanReadable(transcript: Omit<TranscriptSchema, "code">): string {
483 let out = "";
484 for (const message of transcript.messages) {
485 if (message.referencedMessage) {
486 if (Array.isArray(message.referencedMessage)) {
487 out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`;
Skyler Greyda16adf2023-03-05 10:22:12 +0000488 } else out += `> [Reply To] ${message.referencedMessage}\n`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500489 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000490 out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${
491 message.author.id
492 }) (${message.id})`;
TheCodedProff8ef7942023-03-03 15:32:32 -0500493 out += ` [${new Date(message.createdTimestamp).toISOString()}]`;
494 if (message.editedTimestamp) out += ` [Edited: ${new Date(message.editedTimestamp).toISOString()}]`;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500495 out += "\n";
496 if (message.content) out += `[Content]\n${message.content}\n\n`;
497 if (message.embeds) {
498 for (const embed of message.embeds) {
499 out += `[Embed]\n`;
500 if (embed.title) out += `| Title: ${embed.title}\n`;
501 if (embed.description) out += `| Description: ${embed.description}\n`;
502 if (embed.fields) {
503 for (const field of embed.fields) {
504 out += `| Field: ${field.name} - ${field.value}\n`;
505 }
506 }
507 if (embed.footer) {
508 out += `|Footer: ${embed.footer.text}\n`;
509 }
510 out += "\n";
511 }
512 }
513 if (message.components) {
514 for (const component of message.components) {
515 out += `[Component]\n`;
516 for (const button of component) {
517 out += `| Button: ${button.label ?? button.description}\n`;
518 }
519 out += "\n";
520 }
521 }
522 if (message.attachments) {
523 for (const attachment of message.attachments) {
524 out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`;
525 }
526 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000527 out += "\n\n";
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500528 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000529 return out;
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500530 }
TheCodedProfcfe8e9a2023-02-26 17:28:09 -0500531}
532
pineafan4edb7762022-06-26 19:21:04 +0100533export class History {
534 histories: Collection<HistorySchema>;
pineafan4edb7762022-06-26 19:21:04 +0100535
pineafan3a02ea32022-08-11 21:35:04 +0100536 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100537 this.histories = database.collection<HistorySchema>("history");
pineafan4edb7762022-06-26 19:21:04 +0100538 }
539
Skyler Grey75ea9172022-08-06 10:22:23 +0100540 async create(
541 type: string,
542 guild: string,
543 user: Discord.User,
544 moderator: Discord.User | null,
545 reason: string | null,
pineafan3a02ea32022-08-11 21:35:04 +0100546 before?: string | null,
547 after?: string | null,
548 amount?: string | null
Skyler Grey75ea9172022-08-06 10:22:23 +0100549 ) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500550 // console.log("History create");
Skyler Greyda16adf2023-03-05 10:22:12 +0000551 await this.histories.insertOne(
552 {
553 type: type,
554 guild: guild,
555 user: user.id,
556 moderator: moderator ? moderator.id : null,
557 reason: reason,
558 occurredAt: new Date(),
559 before: before ?? null,
560 after: after ?? null,
561 amount: amount ?? null
562 },
563 collectionOptions
564 );
pineafan4edb7762022-06-26 19:21:04 +0100565 }
566
567 async read(guild: string, user: string, year: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500568 // console.log("History read");
Skyler Grey75ea9172022-08-06 10:22:23 +0100569 const entry = (await this.histories
570 .find({
571 guild: guild,
572 user: user,
573 occurredAt: {
574 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
575 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
576 }
577 })
578 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100579 return entry;
580 }
pineafane23c4ec2022-07-27 21:56:27 +0100581
582 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500583 // console.log("History delete");
pineafane23c4ec2022-07-27 21:56:27 +0100584 await this.histories.deleteMany({ guild: guild });
585 }
pineafan4edb7762022-06-26 19:21:04 +0100586}
587
TheCodedProfb5e9d552023-01-29 15:43:26 -0500588interface ScanCacheSchema {
589 addedAt: Date;
590 hash: string;
Skyler Grey14432712023-03-07 23:40:50 +0000591 nsfw?: boolean;
592 malware?: boolean;
593 bad_link?: boolean;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500594 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) {
605 return await this.scanCache.findOne({ hash: hash });
606 }
607
Skyler Grey14432712023-03-07 23:40:50 +0000608 async write(hash: string, type: "nsfw" | "malware" | "bad_link", data: boolean, tags?: string[]) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000609 await this.scanCache.insertOne(
Skyler Grey14432712023-03-07 23:40:50 +0000610 { hash: hash, [type]: data, tags: tags ?? [], addedAt: new Date() },
Skyler Greyda16adf2023-03-05 10:22:12 +0000611 collectionOptions
612 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500613 }
614
615 async cleanup() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500616 // console.log("ScanCache cleanup");
Skyler Greyda16adf2023-03-05 10:22:12 +0000617 await this.scanCache.deleteMany({
618 addedAt: { $lt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 31) },
619 hash: { $not$text: "http" }
620 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500621 }
622}
623
PineaFan538d3752023-01-12 21:48:23 +0000624export class PerformanceTest {
625 performanceData: Collection<PerformanceDataSchema>;
626
627 constructor() {
628 this.performanceData = database.collection<PerformanceDataSchema>("performance");
629 }
630
631 async record(data: PerformanceDataSchema) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500632 // console.log("PerformanceTest record");
PineaFan538d3752023-01-12 21:48:23 +0000633 data.timestamp = new Date();
TheCodedProffaae5332023-03-01 18:16:05 -0500634 await this.performanceData.insertOne(data, collectionOptions);
PineaFan538d3752023-01-12 21:48:23 +0000635 }
636 async read() {
TheCodedProff8ef7942023-03-03 15:32:32 -0500637 // console.log("PerformanceTest read");
PineaFan538d3752023-01-12 21:48:23 +0000638 return await this.performanceData.find({}).toArray();
639 }
640}
641
642export interface PerformanceDataSchema {
643 timestamp?: Date;
644 discord: number;
645 databaseRead: number;
646 resources: {
647 cpu: number;
648 memory: number;
649 temperature: number;
Skyler Greyda16adf2023-03-05 10:22:12 +0000650 };
PineaFan538d3752023-01-12 21:48:23 +0000651}
652
pineafan4edb7762022-06-26 19:21:04 +0100653export class ModNotes {
654 modNotes: Collection<ModNoteSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100655
pineafan3a02ea32022-08-11 21:35:04 +0100656 constructor() {
pineafan4edb7762022-06-26 19:21:04 +0100657 this.modNotes = database.collection<ModNoteSchema>("modNotes");
pineafan4edb7762022-06-26 19:21:04 +0100658 }
659
660 async create(guild: string, user: string, note: string | null) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500661 // console.log("ModNotes create");
Skyler Grey11236ba2022-08-08 21:13:33 +0100662 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100663 }
664
665 async read(guild: string, user: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500666 // console.log("ModNotes read");
pineafan63fc5e22022-08-04 22:04:10 +0100667 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100668 return entry?.note ?? null;
669 }
TheCodedProf267563a2023-01-21 17:00:57 -0500670
671 async delete(guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500672 // console.log("ModNotes delete");
TheCodedProf267563a2023-01-21 17:00:57 -0500673 await this.modNotes.deleteMany({ guild: guild });
674 }
pineafan4edb7762022-06-26 19:21:04 +0100675}
676
pineafan73a7c4a2022-07-24 10:38:04 +0100677export class Premium {
678 premium: Collection<PremiumSchema>;
Skyler Greyda16adf2023-03-05 10:22:12 +0000679 cache: Map<string, [boolean, string, number, boolean, Date]>; // Date indicates the time one hour after it was created
680 cacheTimeout = 1000 * 60 * 60; // 1 hour
pineafan4edb7762022-06-26 19:21:04 +0100681
pineafan3a02ea32022-08-11 21:35:04 +0100682 constructor() {
pineafan73a7c4a2022-07-24 10:38:04 +0100683 this.premium = database.collection<PremiumSchema>("premium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500684 this.cache = new Map<string, [boolean, string, number, boolean, Date]>();
pineafan4edb7762022-06-26 19:21:04 +0100685 }
686
TheCodedProf633866f2023-02-03 17:06:00 -0500687 async updateUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500688 // console.log("Premium updateUser");
Skyler Greyda16adf2023-03-05 10:22:12 +0000689 if (!(await this.userExists(user))) await this.createUser(user, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500690 await this.premium.updateOne({ user: user }, { $set: { level: level } }, { upsert: true });
691 }
692
693 async userExists(user: string): Promise<boolean> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500694 // console.log("Premium userExists");
TheCodedProf633866f2023-02-03 17:06:00 -0500695 const entry = await this.premium.findOne({ user: user });
696 return entry ? true : false;
697 }
TheCodedProf633866f2023-02-03 17:06:00 -0500698 async createUser(user: string, level: number) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500699 // console.log("Premium createUser");
TheCodedProffaae5332023-03-01 18:16:05 -0500700 await this.premium.insertOne({ user: user, appliesTo: [], level: level }, collectionOptions);
TheCodedProf633866f2023-02-03 17:06:00 -0500701 }
702
TheCodedProfaa3fe992023-02-25 21:53:09 -0500703 async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500704 // console.log("Premium hasPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500705 // [Has premium, user giving premium, level, is mod: if given automatically]
706 const cached = this.cache.get(guild);
707 if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]];
TheCodedProf94ff6de2023-02-22 17:47:26 -0500708 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000709 const members = (await client.guilds.fetch(guild)).members.cache;
710 for (const { user } of entries) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500711 const member = members.get(user);
Skyler Greyda16adf2023-03-05 10:22:12 +0000712 if (member) {
713 //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 -0500714 const modPerms = //TODO: Create list in config for perms
Skyler Greyda16adf2023-03-05 10:22:12 +0000715 member.permissions.has("Administrator") ||
716 member.permissions.has("ManageChannels") ||
717 member.permissions.has("ManageRoles") ||
718 member.permissions.has("ManageEmojisAndStickers") ||
719 member.permissions.has("ManageWebhooks") ||
720 member.permissions.has("ManageGuild") ||
721 member.permissions.has("KickMembers") ||
722 member.permissions.has("BanMembers") ||
723 member.permissions.has("ManageEvents") ||
724 member.permissions.has("ManageMessages") ||
725 member.permissions.has("ManageThreads");
726 const entry = entries.find((e) => e.user === member.id);
727 if (entry && entry.level === 3 && modPerms) {
728 this.cache.set(guild, [
729 true,
730 member.id,
731 entry.level,
732 true,
733 new Date(Date.now() + this.cacheTimeout)
734 ]);
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500735 return [true, member.id, entry.level, true];
736 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500737 }
738 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100739 const entry = await this.premium.findOne({
TheCodedProf94ff6de2023-02-22 17:47:26 -0500740 appliesTo: {
741 $elemMatch: {
742 $eq: guild
743 }
744 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100745 });
Skyler Greyda16adf2023-03-05 10:22:12 +0000746 this.cache.set(guild, [
747 entry ? true : false,
748 entry?.user ?? "",
749 entry?.level ?? 0,
750 false,
751 new Date(Date.now() + this.cacheTimeout)
752 ]);
TheCodedProfaa3fe992023-02-25 21:53:09 -0500753 return entry ? [true, entry.user, entry.level, false] : null;
TheCodedProf267563a2023-01-21 17:00:57 -0500754 }
755
TheCodedProf633866f2023-02-03 17:06:00 -0500756 async fetchUser(user: string): Promise<PremiumSchema | null> {
TheCodedProff8ef7942023-03-03 15:32:32 -0500757 // console.log("Premium fetchUser");
TheCodedProf267563a2023-01-21 17:00:57 -0500758 const entry = await this.premium.findOne({ user: user });
TheCodedProf633866f2023-02-03 17:06:00 -0500759 if (!entry) return null;
760 return entry;
761 }
762
TheCodedProf94ff6de2023-02-22 17:47:26 -0500763 async checkAllPremium(member?: GuildMember) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500764 // console.log("Premium checkAllPremium");
TheCodedProf633866f2023-02-03 17:06:00 -0500765 const entries = await this.premium.find({}).toArray();
Skyler Greyda16adf2023-03-05 10:22:12 +0000766 if (member) {
767 const entry = entries.find((e) => e.user === member.id);
768 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500769 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000770 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: member.id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500771 }
772 const roles = member.roles;
773 let level = 0;
774 if (roles.cache.has("1066468879309750313")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500775 level = 99;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500776 } else if (roles.cache.has("1066465491713003520")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500777 level = 1;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500778 } else if (roles.cache.has("1066439526496604194")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500779 level = 2;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500780 } else if (roles.cache.has("1066464134322978912")) {
TheCodedProf633866f2023-02-03 17:06:00 -0500781 level = 3;
782 }
TheCodedProf94ff6de2023-02-22 17:47:26 -0500783 await this.updateUser(member.id, level);
TheCodedProf633866f2023-02-03 17:06:00 -0500784 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000785 await this.premium.updateOne({ user: member.id }, { $unset: { expiresAt: "" } });
TheCodedProf633866f2023-02-03 17:06:00 -0500786 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000787 await this.premium.updateOne(
788 { user: member.id },
789 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
790 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500791 }
792 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000793 const members = await (await client.guilds.fetch("684492926528651336")).members.fetch();
794 for (const { roles, id } of members.values()) {
795 const entry = entries.find((e) => e.user === id);
796 if (entry) {
TheCodedProf94ff6de2023-02-22 17:47:26 -0500797 const expiresAt = entry.expiresAt;
Skyler Greyda16adf2023-03-05 10:22:12 +0000798 if (expiresAt) expiresAt < Date.now() ? await this.premium.deleteOne({ user: id }) : null;
TheCodedProf94ff6de2023-02-22 17:47:26 -0500799 }
800 let level: number = 0;
801 if (roles.cache.has("1066468879309750313")) {
802 level = 99;
803 } else if (roles.cache.has("1066465491713003520")) {
804 level = 1;
805 } else if (roles.cache.has("1066439526496604194")) {
806 level = 2;
807 } else if (roles.cache.has("1066464134322978912")) {
808 level = 3;
809 }
810 await this.updateUser(id, level);
811 if (level > 0) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000812 await this.premium.updateOne({ user: id }, { $unset: { expiresAt: "" } });
TheCodedProf94ff6de2023-02-22 17:47:26 -0500813 } else {
Skyler Greyda16adf2023-03-05 10:22:12 +0000814 await this.premium.updateOne(
815 { user: id },
816 { $set: { expiresAt: Date.now() + 1000 * 60 * 60 * 24 * 3 } }
817 );
TheCodedProf94ff6de2023-02-22 17:47:26 -0500818 }
TheCodedProf633866f2023-02-03 17:06:00 -0500819 }
820 }
TheCodedProf267563a2023-01-21 17:00:57 -0500821 }
822
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500823 async addPremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500824 // console.log("Premium addPremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500825 const { level } = (await this.fetchUser(user))!;
826 this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf267563a2023-01-21 17:00:57 -0500827 return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100828 }
TheCodedProffc420b72023-01-24 17:14:38 -0500829
TheCodedProf48865eb2023-03-05 15:25:25 -0500830 async removePremium(user: string, guild: string) {
TheCodedProff8ef7942023-03-03 15:32:32 -0500831 // console.log("Premium removePremium");
TheCodedProf9c51a7e2023-02-27 17:11:13 -0500832 this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]);
TheCodedProf48865eb2023-03-05 15:25:25 -0500833 return await this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } });
TheCodedProffc420b72023-01-24 17:14:38 -0500834 }
pineafan4edb7762022-06-26 19:21:04 +0100835}
836
pineafan1e462ab2023-03-07 21:34:06 +0000837// export class Plugins {}
pineafan6de4da52023-03-07 20:43:44 +0000838
pineafan6fb3e072022-05-20 19:27:23 +0100839export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100840 id: string;
841 version: number;
PineaFan100df682023-01-02 13:26:08 +0000842 singleEventNotifications: Record<string, boolean>;
pineafan6fb3e072022-05-20 19:27:23 +0100843 filters: {
844 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100845 NSFW: boolean;
846 size: boolean;
847 };
848 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100849 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100850 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100851 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100852 strict: string[];
853 loose: string[];
854 };
pineafan6fb3e072022-05-20 19:27:23 +0100855 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100856 users: string[];
857 roles: string[];
858 channels: string[];
859 };
860 };
pineafan6fb3e072022-05-20 19:27:23 +0100861 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100862 enabled: boolean;
PineaFan538d3752023-01-12 21:48:23 +0000863 allowed: {
864 channels: string[];
865 roles: string[];
866 users: string[];
867 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100868 };
pineafan6fb3e072022-05-20 19:27:23 +0100869 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100870 mass: number;
871 everyone: boolean;
872 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100873 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100874 roles: string[];
875 rolesToMention: string[];
876 users: string[];
877 channels: string[];
878 };
879 };
TheCodedProfad0b8202023-02-14 14:27:09 -0500880 clean: {
881 channels: string[];
882 allowed: {
TheCodedProff8ef7942023-03-03 15:32:32 -0500883 users: string[];
TheCodedProfad0b8202023-02-14 14:27:09 -0500884 roles: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000885 };
886 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100887 };
TheCodedProfbaee2c12023-02-18 16:11:06 -0500888 autoPublish: {
889 enabled: boolean;
890 channels: string[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000891 };
pineafan6fb3e072022-05-20 19:27:23 +0100892 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100893 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100894 role: string | null;
895 ping: string | null;
896 channel: string | null;
897 message: string | null;
898 };
899 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100900 logging: {
901 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100902 enabled: boolean;
903 channel: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100904 toLog: string;
Skyler Grey75ea9172022-08-06 10:22:23 +0100905 };
pineafan6fb3e072022-05-20 19:27:23 +0100906 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100907 channel: string | null;
908 };
pineafan73a7c4a2022-07-24 10:38:04 +0100909 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100910 channel: string | null;
911 saved: Record<string, string>;
912 };
913 };
pineafan6fb3e072022-05-20 19:27:23 +0100914 verify: {
PineaFandf4996f2023-01-01 14:20:06 +0000915 enabled: boolean;
Skyler Grey75ea9172022-08-06 10:22:23 +0100916 role: string | null;
917 };
pineafan6fb3e072022-05-20 19:27:23 +0100918 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100919 enabled: boolean;
920 category: string | null;
Skyler Greyad002172022-08-16 18:48:26 +0100921 types: string;
922 customTypes: string[] | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100923 useCustom: boolean;
924 supportRole: string | null;
925 maxTickets: number;
926 };
pineafan6fb3e072022-05-20 19:27:23 +0100927 moderation: {
928 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100929 timeout: boolean;
930 role: string | null;
931 text: string | null;
932 link: string | null;
933 };
pineafan6fb3e072022-05-20 19:27:23 +0100934 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100935 text: string | null;
936 link: string | null;
937 };
pineafan6fb3e072022-05-20 19:27:23 +0100938 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100939 text: string | null;
940 link: string | null;
941 };
pineafan6fb3e072022-05-20 19:27:23 +0100942 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100943 text: string | null;
944 link: string | null;
945 };
pineafan6fb3e072022-05-20 19:27:23 +0100946 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100947 text: string | null;
948 link: string | null;
949 };
pineafan6fb3e072022-05-20 19:27:23 +0100950 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100951 role: string | null;
TheCodedProfd9636e82023-01-17 22:13:06 -0500952 text: null;
953 link: null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100954 };
PineaFane6ba7882023-01-18 20:41:16 +0000955 nick: {
956 text: string | null;
957 link: string | null;
Skyler Greyda16adf2023-03-05 10:22:12 +0000958 };
Skyler Grey75ea9172022-08-06 10:22:23 +0100959 };
pineafan6fb3e072022-05-20 19:27:23 +0100960 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100961 name: string;
962 retainPrevious: boolean;
963 nullable: boolean;
964 track: string[];
965 manageableBy: string[];
966 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100967 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100968 enabled: boolean;
969 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100970 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100971 name: string;
972 description: string;
973 min: number;
974 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100975 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100976 name: string;
977 description: string | null;
978 role: string;
979 }[];
980 }[];
981 };
982 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100983}
pineafan4edb7762022-06-26 19:21:04 +0100984
985export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100986 type: string;
987 guild: string;
988 user: string;
989 moderator: string | null;
pineafan3a02ea32022-08-11 21:35:04 +0100990 reason: string | null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100991 occurredAt: Date;
992 before: string | null;
993 after: string | null;
994 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100995}
996
997export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100998 guild: string;
999 user: string;
pineafan3a02ea32022-08-11 21:35:04 +01001000 note: string | null;
pineafan4edb7762022-06-26 19:21:04 +01001001}
1002
pineafan73a7c4a2022-07-24 10:38:04 +01001003export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +01001004 user: string;
1005 level: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001006 appliesTo: string[];
TheCodedProf633866f2023-02-03 17:06:00 -05001007 expiresAt?: number;
Skyler Grey75ea9172022-08-06 10:22:23 +01001008}