blob: 04c9e246ef47c742e8d8cd854c4e319240bf37a5 [file] [log] [blame]
pineafan63fc5e22022-08-04 22:04:10 +01001import type Discord from "discord.js";
2import { Collection, MongoClient } from "mongodb";
Skyler Grey75ea9172022-08-06 10:22:23 +01003// @ts-expect-error
pineafan63fc5e22022-08-04 22:04:10 +01004import structuredClone from "@ungap/structured-clone";
Skyler Grey75ea9172022-08-06 10:22:23 +01005import config from "../config/main.json" assert { type: "json" };
pineafan4edb7762022-06-26 19:21:04 +01006
7const mongoClient = new MongoClient(config.mongoUrl);
pineafan63fc5e22022-08-04 22:04:10 +01008await mongoClient.connect();
pineafan4edb7762022-06-26 19:21:04 +01009const database = mongoClient.db("Nucleus");
pineafan6fb3e072022-05-20 19:27:23 +010010
pineafan63fc5e22022-08-04 22:04:10 +010011// eslint-disable-next-line @typescript-eslint/no-explicit-any
12export const Entry = (data: any) => {
pineafan6fb3e072022-05-20 19:27:23 +010013 data = data ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010014 // eslint-disable-next-line @typescript-eslint/no-explicit-any
15 data.getKey = (key: any) => data[key];
pineafan6fb3e072022-05-20 19:27:23 +010016 return {
pineafan63fc5e22022-08-04 22:04:10 +010017 // eslint-disable-next-line @typescript-eslint/no-explicit-any
18 get(target: Record<string, any>, prop: string, receiver: any) {
19 let dataToReturn = data[prop];
Skyler Grey11236ba2022-08-08 21:13:33 +010020 if (dataToReturn === null) return Reflect.get(target, prop, receiver);
21 if (typeof dataToReturn === "object" && !Array.isArray(dataToReturn))
22 dataToReturn = new Proxy(Reflect.get(target, prop, receiver), Entry(dataToReturn));
pineafan6fb3e072022-05-20 19:27:23 +010023 return dataToReturn ?? Reflect.get(target, prop, receiver);
24 }
pineafan63fc5e22022-08-04 22:04:10 +010025 };
26};
pineafan6fb3e072022-05-20 19:27:23 +010027
pineafan4edb7762022-06-26 19:21:04 +010028export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010029 guilds: Collection<GuildConfig>;
pineafan63fc5e22022-08-04 22:04:10 +010030 defaultData: GuildConfig | null;
31
32 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010033 this.guilds = database.collection<GuildConfig>("guilds");
pineafan63fc5e22022-08-04 22:04:10 +010034 this.defaultData = null;
pineafan6fb3e072022-05-20 19:27:23 +010035 return this;
36 }
37
pineafan63fc5e22022-08-04 22:04:10 +010038 async setup() {
Skyler Grey11236ba2022-08-08 21:13:33 +010039 this.defaultData = (await import("../config/default.json", { assert: { type: "json" } }))
40 .default as unknown as GuildConfig;
41 return this;
pineafan63fc5e22022-08-04 22:04:10 +010042 }
43
pineafan6fb3e072022-05-20 19:27:23 +010044 async read(guild: string) {
pineafan63fc5e22022-08-04 22:04:10 +010045 const entry = await this.guilds.findOne({ id: guild });
Skyler Grey11236ba2022-08-08 21:13:33 +010046 return new Proxy(structuredClone(this.defaultData), Entry(entry)) as unknown as GuildConfig;
pineafan6fb3e072022-05-20 19:27:23 +010047 }
48
Skyler Grey11236ba2022-08-08 21:13:33 +010049 async write(guild: string, set: object | null, unset: string[] | string = []) {
pineafan63fc5e22022-08-04 22:04:10 +010050 // eslint-disable-next-line @typescript-eslint/no-explicit-any
51 const uo: Record<string, any> = {};
52 if (!Array.isArray(unset)) unset = [unset];
53 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010054 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010055 }
Skyler Grey75ea9172022-08-06 10:22:23 +010056 const out = { $set: {}, $unset: {} };
57 if (set) out.$set = set;
58 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010059 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010060 }
61
pineafan63fc5e22022-08-04 22:04:10 +010062 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010063 async append(guild: string, key: string, value: any) {
64 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010065 await this.guilds.updateOne(
66 { id: guild },
67 {
68 $addToSet: { [key]: { $each: value } }
69 },
70 { upsert: true }
71 );
pineafan6702cef2022-06-13 17:52:37 +010072 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010073 await this.guilds.updateOne(
74 { id: guild },
75 {
76 $addToSet: { [key]: value }
77 },
78 { upsert: true }
79 );
pineafan6702cef2022-06-13 17:52:37 +010080 }
81 }
82
Skyler Grey75ea9172022-08-06 10:22:23 +010083 async remove(
84 guild: string,
85 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +010086 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010087 value: any,
88 innerKey?: string | null
89 ) {
pineafan63fc5e22022-08-04 22:04:10 +010090 console.log(Array.isArray(value));
pineafan02ba0232022-07-24 22:16:15 +010091 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +010092 await this.guilds.updateOne(
93 { id: guild },
94 {
95 $pull: { [key]: { [innerKey]: { $eq: value } } }
96 },
97 { upsert: true }
98 );
pineafan0bc04162022-07-25 17:22:26 +010099 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100100 await this.guilds.updateOne(
101 { id: guild },
102 {
103 $pullAll: { [key]: value }
104 },
105 { upsert: true }
106 );
pineafan6702cef2022-06-13 17:52:37 +0100107 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100108 await this.guilds.updateOne(
109 { id: guild },
110 {
111 $pullAll: { [key]: [value] }
112 },
113 { upsert: true }
114 );
pineafan6702cef2022-06-13 17:52:37 +0100115 }
pineafan6fb3e072022-05-20 19:27:23 +0100116 }
pineafane23c4ec2022-07-27 21:56:27 +0100117
118 async delete(guild: string) {
119 await this.guilds.deleteOne({ id: guild });
120 }
pineafan6fb3e072022-05-20 19:27:23 +0100121}
122
pineafan4edb7762022-06-26 19:21:04 +0100123export class History {
124 histories: Collection<HistorySchema>;
125 defaultData: GuildConfig;
126
127 async setup() {
128 this.histories = database.collection<HistorySchema>("history");
129 return this;
130 }
131
Skyler Grey75ea9172022-08-06 10:22:23 +0100132 async create(
133 type: string,
134 guild: string,
135 user: Discord.User,
136 moderator: Discord.User | null,
137 reason: string | null,
138 before?: null,
139 after?: null,
140 amount?: null
141 ) {
pineafan4edb7762022-06-26 19:21:04 +0100142 await this.histories.insertOne({
143 type: type,
144 guild: guild,
145 user: user.id,
146 moderator: moderator.id,
147 reason: reason,
148 occurredAt: new Date(),
149 before: before,
150 after: after,
151 amount: amount
152 });
153 }
154
155 async read(guild: string, user: string, year: number) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100156 const entry = (await this.histories
157 .find({
158 guild: guild,
159 user: user,
160 occurredAt: {
161 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
162 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
163 }
164 })
165 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100166 return entry;
167 }
pineafane23c4ec2022-07-27 21:56:27 +0100168
169 async delete(guild: string) {
170 await this.histories.deleteMany({ guild: guild });
171 }
pineafan4edb7762022-06-26 19:21:04 +0100172}
173
174export class ModNotes {
175 modNotes: Collection<ModNoteSchema>;
176 defaultData: GuildConfig;
177
178 async setup() {
179 this.modNotes = database.collection<ModNoteSchema>("modNotes");
180 return this;
181 }
182
183 async create(guild: string, user: string, note: string | null) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100184 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note } }, { upsert: true });
pineafan4edb7762022-06-26 19:21:04 +0100185 }
186
187 async read(guild: string, user: string) {
pineafan63fc5e22022-08-04 22:04:10 +0100188 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100189 return entry?.note ?? null;
190 }
191}
192
pineafan73a7c4a2022-07-24 10:38:04 +0100193export class Premium {
194 premium: Collection<PremiumSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100195
196 async setup() {
pineafan73a7c4a2022-07-24 10:38:04 +0100197 this.premium = database.collection<PremiumSchema>("premium");
pineafan4edb7762022-06-26 19:21:04 +0100198 return this;
199 }
200
pineafan73a7c4a2022-07-24 10:38:04 +0100201 async hasPremium(guild: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100202 const entry = await this.premium.findOne({
203 appliesTo: { $in: [guild] }
204 });
pineafane23c4ec2022-07-27 21:56:27 +0100205 return entry !== null;
pineafan4edb7762022-06-26 19:21:04 +0100206 }
207}
208
pineafan6fb3e072022-05-20 19:27:23 +0100209export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100210 id: string;
211 version: number;
pineafan6fb3e072022-05-20 19:27:23 +0100212 singleEventNotifications: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100213 statsChannelDeleted: boolean;
214 };
pineafan6fb3e072022-05-20 19:27:23 +0100215 filters: {
216 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100217 NSFW: boolean;
218 size: boolean;
219 };
220 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100221 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100222 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100223 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100224 strict: string[];
225 loose: string[];
226 };
pineafan6fb3e072022-05-20 19:27:23 +0100227 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 users: string[];
229 roles: string[];
230 channels: string[];
231 };
232 };
pineafan6fb3e072022-05-20 19:27:23 +0100233 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100234 enabled: boolean;
235 channels: string[];
236 };
pineafan6fb3e072022-05-20 19:27:23 +0100237 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100238 mass: number;
239 everyone: boolean;
240 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100241 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100242 roles: string[];
243 rolesToMention: string[];
244 users: string[];
245 channels: string[];
246 };
247 };
248 };
pineafan6fb3e072022-05-20 19:27:23 +0100249 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100250 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100251 verificationRequired: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100252 message: boolean;
253 role: string | null;
254 };
255 role: string | null;
256 ping: string | null;
257 channel: string | null;
258 message: string | null;
259 };
260 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100261 logging: {
262 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100263 enabled: boolean;
264 channel: string | null;
265 toLog: string | null;
266 };
pineafan6fb3e072022-05-20 19:27:23 +0100267 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 channel: string | null;
269 };
pineafan73a7c4a2022-07-24 10:38:04 +0100270 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100271 channel: string | null;
272 saved: Record<string, string>;
273 };
274 };
pineafan6fb3e072022-05-20 19:27:23 +0100275 verify: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100276 enabled: boolean;
277 role: string | null;
278 };
pineafan6fb3e072022-05-20 19:27:23 +0100279 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100280 enabled: boolean;
281 category: string | null;
282 types: string | null;
283 customTypes: string[];
284 useCustom: boolean;
285 supportRole: string | null;
286 maxTickets: number;
287 };
pineafan6fb3e072022-05-20 19:27:23 +0100288 moderation: {
289 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100290 timeout: boolean;
291 role: string | null;
292 text: string | null;
293 link: string | null;
294 };
pineafan6fb3e072022-05-20 19:27:23 +0100295 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100296 text: string | null;
297 link: string | null;
298 };
pineafan6fb3e072022-05-20 19:27:23 +0100299 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100300 text: string | null;
301 link: string | null;
302 };
pineafan6fb3e072022-05-20 19:27:23 +0100303 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100304 text: string | null;
305 link: string | null;
306 };
pineafan6fb3e072022-05-20 19:27:23 +0100307 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100308 text: string | null;
309 link: string | null;
310 };
pineafan6fb3e072022-05-20 19:27:23 +0100311 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100312 role: string | null;
313 };
314 };
pineafan6fb3e072022-05-20 19:27:23 +0100315 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100316 name: string;
317 retainPrevious: boolean;
318 nullable: boolean;
319 track: string[];
320 manageableBy: string[];
321 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100322 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100323 enabled: boolean;
324 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100325 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100326 name: string;
327 description: string;
328 min: number;
329 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100330 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100331 name: string;
332 description: string | null;
333 role: string;
334 }[];
335 }[];
336 };
337 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100338}
pineafan4edb7762022-06-26 19:21:04 +0100339
340export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100341 type: string;
342 guild: string;
343 user: string;
344 moderator: string | null;
345 reason: string;
346 occurredAt: Date;
347 before: string | null;
348 after: string | null;
349 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100350}
351
352export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100353 guild: string;
354 user: string;
355 note: string;
pineafan4edb7762022-06-26 19:21:04 +0100356}
357
pineafan73a7c4a2022-07-24 10:38:04 +0100358export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100359 user: string;
360 level: number;
361 expires: Date;
362 appliesTo: string[];
363}