blob: 3592c9514981fb16c0694fb06599a0eb9581d290 [file] [log] [blame]
pineafan63fc5e22022-08-04 22:04:10 +01001import type Discord from "discord.js";
2import { Collection, MongoClient } from "mongodb";
3// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Skyler Grey75ea9172022-08-06 10:22:23 +01004// @ts-expect-error
pineafan63fc5e22022-08-04 22:04:10 +01005import structuredClone from "@ungap/structured-clone";
Skyler Grey75ea9172022-08-06 10:22:23 +01006import config from "../config/main.json" assert { type: "json" };
pineafan4edb7762022-06-26 19:21:04 +01007
8const mongoClient = new MongoClient(config.mongoUrl);
pineafan63fc5e22022-08-04 22:04:10 +01009await mongoClient.connect();
pineafan4edb7762022-06-26 19:21:04 +010010const database = mongoClient.db("Nucleus");
pineafan6fb3e072022-05-20 19:27:23 +010011
pineafan63fc5e22022-08-04 22:04:10 +010012// eslint-disable-next-line @typescript-eslint/no-explicit-any
13export const Entry = (data: any) => {
pineafan6fb3e072022-05-20 19:27:23 +010014 data = data ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010015 // eslint-disable-next-line @typescript-eslint/no-explicit-any
16 data.getKey = (key: any) => data[key];
pineafan6fb3e072022-05-20 19:27:23 +010017 return {
pineafan63fc5e22022-08-04 22:04:10 +010018 // eslint-disable-next-line @typescript-eslint/no-explicit-any
19 get(target: Record<string, any>, prop: string, receiver: any) {
20 let dataToReturn = data[prop];
Skyler Grey75ea9172022-08-06 10:22:23 +010021 if (dataToReturn === null)
22 return Reflect.get(target, prop, receiver);
23 if (
24 typeof dataToReturn === "object" &&
25 !Array.isArray(dataToReturn)
26 )
27 dataToReturn = new Proxy(
28 Reflect.get(target, prop, receiver),
29 Entry(dataToReturn)
30 );
pineafan6fb3e072022-05-20 19:27:23 +010031 return dataToReturn ?? Reflect.get(target, prop, receiver);
32 }
pineafan63fc5e22022-08-04 22:04:10 +010033 };
34};
pineafan6fb3e072022-05-20 19:27:23 +010035
pineafan4edb7762022-06-26 19:21:04 +010036export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010037 guilds: Collection<GuildConfig>;
pineafan63fc5e22022-08-04 22:04:10 +010038 defaultData: GuildConfig | null;
39
40 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010041 this.guilds = database.collection<GuildConfig>("guilds");
pineafan63fc5e22022-08-04 22:04:10 +010042 this.defaultData = null;
pineafan6fb3e072022-05-20 19:27:23 +010043 return this;
44 }
45
pineafan63fc5e22022-08-04 22:04:10 +010046 async setup() {
Skyler Grey75ea9172022-08-06 10:22:23 +010047 this.defaultData = (
48 await import("../config/default.json", { assert: { type: "json" } })
49 ).default as unknown as GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010050 }
51
pineafan6fb3e072022-05-20 19:27:23 +010052 async read(guild: string) {
pineafan63fc5e22022-08-04 22:04:10 +010053 const entry = await this.guilds.findOne({ id: guild });
Skyler Grey75ea9172022-08-06 10:22:23 +010054 return new Proxy(
55 structuredClone(this.defaultData),
56 Entry(entry)
57 ) as unknown as GuildConfig;
pineafan6fb3e072022-05-20 19:27:23 +010058 }
59
Skyler Grey75ea9172022-08-06 10:22:23 +010060 async write(
61 guild: string,
62 set: object | null,
63 unset: string[] | string = []
64 ) {
pineafan63fc5e22022-08-04 22:04:10 +010065 // eslint-disable-next-line @typescript-eslint/no-explicit-any
66 const uo: Record<string, any> = {};
67 if (!Array.isArray(unset)) unset = [unset];
68 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010069 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010070 }
Skyler Grey75ea9172022-08-06 10:22:23 +010071 const out = { $set: {}, $unset: {} };
72 if (set) out.$set = set;
73 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010074 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010075 }
76
pineafan63fc5e22022-08-04 22:04:10 +010077 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010078 async append(guild: string, key: string, value: any) {
79 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010080 await this.guilds.updateOne(
81 { id: guild },
82 {
83 $addToSet: { [key]: { $each: value } }
84 },
85 { upsert: true }
86 );
pineafan6702cef2022-06-13 17:52:37 +010087 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010088 await this.guilds.updateOne(
89 { id: guild },
90 {
91 $addToSet: { [key]: value }
92 },
93 { upsert: true }
94 );
pineafan6702cef2022-06-13 17:52:37 +010095 }
96 }
97
pineafan63fc5e22022-08-04 22:04:10 +010098 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +010099 async remove(
100 guild: string,
101 key: string,
102 value: any,
103 innerKey?: string | null
104 ) {
pineafan63fc5e22022-08-04 22:04:10 +0100105 console.log(Array.isArray(value));
pineafan02ba0232022-07-24 22:16:15 +0100106 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100107 await this.guilds.updateOne(
108 { id: guild },
109 {
110 $pull: { [key]: { [innerKey]: { $eq: value } } }
111 },
112 { upsert: true }
113 );
pineafan0bc04162022-07-25 17:22:26 +0100114 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100115 await this.guilds.updateOne(
116 { id: guild },
117 {
118 $pullAll: { [key]: value }
119 },
120 { upsert: true }
121 );
pineafan6702cef2022-06-13 17:52:37 +0100122 } else {
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 }
pineafan6fb3e072022-05-20 19:27:23 +0100131 }
pineafane23c4ec2022-07-27 21:56:27 +0100132
133 async delete(guild: string) {
134 await this.guilds.deleteOne({ id: guild });
135 }
pineafan6fb3e072022-05-20 19:27:23 +0100136}
137
pineafan4edb7762022-06-26 19:21:04 +0100138export class History {
139 histories: Collection<HistorySchema>;
140 defaultData: GuildConfig;
141
142 async setup() {
143 this.histories = database.collection<HistorySchema>("history");
144 return this;
145 }
146
Skyler Grey75ea9172022-08-06 10:22:23 +0100147 async create(
148 type: string,
149 guild: string,
150 user: Discord.User,
151 moderator: Discord.User | null,
152 reason: string | null,
153 before?: null,
154 after?: null,
155 amount?: null
156 ) {
pineafan4edb7762022-06-26 19:21:04 +0100157 await this.histories.insertOne({
158 type: type,
159 guild: guild,
160 user: user.id,
161 moderator: moderator.id,
162 reason: reason,
163 occurredAt: new Date(),
164 before: before,
165 after: after,
166 amount: amount
167 });
168 }
169
170 async read(guild: string, user: string, year: number) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100171 const entry = (await this.histories
172 .find({
173 guild: guild,
174 user: user,
175 occurredAt: {
176 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
177 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
178 }
179 })
180 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100181 return entry;
182 }
pineafane23c4ec2022-07-27 21:56:27 +0100183
184 async delete(guild: string) {
185 await this.histories.deleteMany({ guild: guild });
186 }
pineafan4edb7762022-06-26 19:21:04 +0100187}
188
189export class ModNotes {
190 modNotes: Collection<ModNoteSchema>;
191 defaultData: GuildConfig;
192
193 async setup() {
194 this.modNotes = database.collection<ModNoteSchema>("modNotes");
195 return this;
196 }
197
198 async create(guild: string, user: string, note: string | null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100199 await this.modNotes.updateOne(
200 { guild: guild, user: user },
201 { $set: { note: note } },
202 { upsert: true }
203 );
pineafan4edb7762022-06-26 19:21:04 +0100204 }
205
206 async read(guild: string, user: string) {
pineafan63fc5e22022-08-04 22:04:10 +0100207 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100208 return entry?.note ?? null;
209 }
210}
211
pineafan73a7c4a2022-07-24 10:38:04 +0100212export class Premium {
213 premium: Collection<PremiumSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100214
215 async setup() {
pineafan73a7c4a2022-07-24 10:38:04 +0100216 this.premium = database.collection<PremiumSchema>("premium");
pineafan4edb7762022-06-26 19:21:04 +0100217 return this;
218 }
219
pineafan73a7c4a2022-07-24 10:38:04 +0100220 async hasPremium(guild: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100221 const entry = await this.premium.findOne({
222 appliesTo: { $in: [guild] }
223 });
pineafane23c4ec2022-07-27 21:56:27 +0100224 return entry !== null;
pineafan4edb7762022-06-26 19:21:04 +0100225 }
226}
227
pineafan6fb3e072022-05-20 19:27:23 +0100228export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100229 id: string;
230 version: number;
pineafan6fb3e072022-05-20 19:27:23 +0100231 singleEventNotifications: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100232 statsChannelDeleted: boolean;
233 };
pineafan6fb3e072022-05-20 19:27:23 +0100234 filters: {
235 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100236 NSFW: boolean;
237 size: boolean;
238 };
239 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100240 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100241 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100242 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100243 strict: string[];
244 loose: string[];
245 };
pineafan6fb3e072022-05-20 19:27:23 +0100246 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100247 users: string[];
248 roles: string[];
249 channels: string[];
250 };
251 };
pineafan6fb3e072022-05-20 19:27:23 +0100252 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 enabled: boolean;
254 channels: string[];
255 };
pineafan6fb3e072022-05-20 19:27:23 +0100256 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100257 mass: number;
258 everyone: boolean;
259 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100260 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100261 roles: string[];
262 rolesToMention: string[];
263 users: string[];
264 channels: string[];
265 };
266 };
267 };
pineafan6fb3e072022-05-20 19:27:23 +0100268 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100269 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100270 verificationRequired: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100271 message: boolean;
272 role: string | null;
273 };
274 role: string | null;
275 ping: string | null;
276 channel: string | null;
277 message: string | null;
278 };
279 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100280 logging: {
281 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100282 enabled: boolean;
283 channel: string | null;
284 toLog: string | null;
285 };
pineafan6fb3e072022-05-20 19:27:23 +0100286 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100287 channel: string | null;
288 };
pineafan73a7c4a2022-07-24 10:38:04 +0100289 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100290 channel: string | null;
291 saved: Record<string, string>;
292 };
293 };
pineafan6fb3e072022-05-20 19:27:23 +0100294 verify: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100295 enabled: boolean;
296 role: string | null;
297 };
pineafan6fb3e072022-05-20 19:27:23 +0100298 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100299 enabled: boolean;
300 category: string | null;
301 types: string | null;
302 customTypes: string[];
303 useCustom: boolean;
304 supportRole: string | null;
305 maxTickets: number;
306 };
pineafan6fb3e072022-05-20 19:27:23 +0100307 moderation: {
308 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100309 timeout: boolean;
310 role: string | null;
311 text: string | null;
312 link: string | null;
313 };
pineafan6fb3e072022-05-20 19:27:23 +0100314 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100315 text: string | null;
316 link: string | null;
317 };
pineafan6fb3e072022-05-20 19:27:23 +0100318 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100319 text: string | null;
320 link: string | null;
321 };
pineafan6fb3e072022-05-20 19:27:23 +0100322 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100323 text: string | null;
324 link: string | null;
325 };
pineafan6fb3e072022-05-20 19:27:23 +0100326 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100327 text: string | null;
328 link: string | null;
329 };
pineafan6fb3e072022-05-20 19:27:23 +0100330 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100331 role: string | null;
332 };
333 };
pineafan6fb3e072022-05-20 19:27:23 +0100334 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100335 name: string;
336 retainPrevious: boolean;
337 nullable: boolean;
338 track: string[];
339 manageableBy: string[];
340 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100341 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100342 enabled: boolean;
343 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100344 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100345 name: string;
346 description: string;
347 min: number;
348 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100349 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100350 name: string;
351 description: string | null;
352 role: string;
353 }[];
354 }[];
355 };
356 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100357}
pineafan4edb7762022-06-26 19:21:04 +0100358
359export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100360 type: string;
361 guild: string;
362 user: string;
363 moderator: string | null;
364 reason: string;
365 occurredAt: Date;
366 before: string | null;
367 after: string | null;
368 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100369}
370
371export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100372 guild: string;
373 user: string;
374 note: string;
pineafan4edb7762022-06-26 19:21:04 +0100375}
376
pineafan73a7c4a2022-07-24 10:38:04 +0100377export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100378 user: string;
379 level: number;
380 expires: Date;
381 appliesTo: string[];
382}