blob: 140dbccc9ac303dbf61e988485633bb9f3334e8a [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 Grey75ea9172022-08-06 10:22:23 +010020 if (dataToReturn === null)
21 return Reflect.get(target, prop, receiver);
22 if (
23 typeof dataToReturn === "object" &&
24 !Array.isArray(dataToReturn)
25 )
26 dataToReturn = new Proxy(
27 Reflect.get(target, prop, receiver),
28 Entry(dataToReturn)
29 );
pineafan6fb3e072022-05-20 19:27:23 +010030 return dataToReturn ?? Reflect.get(target, prop, receiver);
31 }
pineafan63fc5e22022-08-04 22:04:10 +010032 };
33};
pineafan6fb3e072022-05-20 19:27:23 +010034
pineafan4edb7762022-06-26 19:21:04 +010035export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010036 guilds: Collection<GuildConfig>;
pineafan63fc5e22022-08-04 22:04:10 +010037 defaultData: GuildConfig | null;
38
39 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010040 this.guilds = database.collection<GuildConfig>("guilds");
pineafan63fc5e22022-08-04 22:04:10 +010041 this.defaultData = null;
pineafan6fb3e072022-05-20 19:27:23 +010042 return this;
43 }
44
pineafan63fc5e22022-08-04 22:04:10 +010045 async setup() {
Skyler Grey75ea9172022-08-06 10:22:23 +010046 this.defaultData = (
47 await import("../config/default.json", { assert: { type: "json" } })
48 ).default as unknown as GuildConfig;
pineafan63fc5e22022-08-04 22:04:10 +010049 }
50
pineafan6fb3e072022-05-20 19:27:23 +010051 async read(guild: string) {
pineafan63fc5e22022-08-04 22:04:10 +010052 const entry = await this.guilds.findOne({ id: guild });
Skyler Grey75ea9172022-08-06 10:22:23 +010053 return new Proxy(
54 structuredClone(this.defaultData),
55 Entry(entry)
56 ) as unknown as GuildConfig;
pineafan6fb3e072022-05-20 19:27:23 +010057 }
58
Skyler Grey75ea9172022-08-06 10:22:23 +010059 async write(
60 guild: string,
61 set: object | null,
62 unset: string[] | string = []
63 ) {
pineafan63fc5e22022-08-04 22:04:10 +010064 // eslint-disable-next-line @typescript-eslint/no-explicit-any
65 const uo: Record<string, any> = {};
66 if (!Array.isArray(unset)) unset = [unset];
67 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010068 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010069 }
Skyler Grey75ea9172022-08-06 10:22:23 +010070 const out = { $set: {}, $unset: {} };
71 if (set) out.$set = set;
72 if (unset.length) out.$unset = uo;
pineafan0bc04162022-07-25 17:22:26 +010073 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010074 }
75
pineafan63fc5e22022-08-04 22:04:10 +010076 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010077 async append(guild: string, key: string, value: any) {
78 if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +010079 await this.guilds.updateOne(
80 { id: guild },
81 {
82 $addToSet: { [key]: { $each: value } }
83 },
84 { upsert: true }
85 );
pineafan6702cef2022-06-13 17:52:37 +010086 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +010087 await this.guilds.updateOne(
88 { id: guild },
89 {
90 $addToSet: { [key]: value }
91 },
92 { upsert: true }
93 );
pineafan6702cef2022-06-13 17:52:37 +010094 }
95 }
96
Skyler Grey75ea9172022-08-06 10:22:23 +010097 async remove(
98 guild: string,
99 key: string,
Skyler Greyc634e2b2022-08-06 17:50:48 +0100100 // eslint-disable-next-line @typescript-eslint/no-explicit-any
Skyler Grey75ea9172022-08-06 10:22:23 +0100101 value: any,
102 innerKey?: string | null
103 ) {
pineafan63fc5e22022-08-04 22:04:10 +0100104 console.log(Array.isArray(value));
pineafan02ba0232022-07-24 22:16:15 +0100105 if (innerKey) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 await this.guilds.updateOne(
107 { id: guild },
108 {
109 $pull: { [key]: { [innerKey]: { $eq: value } } }
110 },
111 { upsert: true }
112 );
pineafan0bc04162022-07-25 17:22:26 +0100113 } else if (Array.isArray(value)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100114 await this.guilds.updateOne(
115 { id: guild },
116 {
117 $pullAll: { [key]: value }
118 },
119 { upsert: true }
120 );
pineafan6702cef2022-06-13 17:52:37 +0100121 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 await this.guilds.updateOne(
123 { id: guild },
124 {
125 $pullAll: { [key]: [value] }
126 },
127 { upsert: true }
128 );
pineafan6702cef2022-06-13 17:52:37 +0100129 }
pineafan6fb3e072022-05-20 19:27:23 +0100130 }
pineafane23c4ec2022-07-27 21:56:27 +0100131
132 async delete(guild: string) {
133 await this.guilds.deleteOne({ id: guild });
134 }
pineafan6fb3e072022-05-20 19:27:23 +0100135}
136
pineafan4edb7762022-06-26 19:21:04 +0100137export class History {
138 histories: Collection<HistorySchema>;
139 defaultData: GuildConfig;
140
141 async setup() {
142 this.histories = database.collection<HistorySchema>("history");
143 return this;
144 }
145
Skyler Grey75ea9172022-08-06 10:22:23 +0100146 async create(
147 type: string,
148 guild: string,
149 user: Discord.User,
150 moderator: Discord.User | null,
151 reason: string | null,
152 before?: null,
153 after?: null,
154 amount?: null
155 ) {
pineafan4edb7762022-06-26 19:21:04 +0100156 await this.histories.insertOne({
157 type: type,
158 guild: guild,
159 user: user.id,
160 moderator: moderator.id,
161 reason: reason,
162 occurredAt: new Date(),
163 before: before,
164 after: after,
165 amount: amount
166 });
167 }
168
169 async read(guild: string, user: string, year: number) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100170 const entry = (await this.histories
171 .find({
172 guild: guild,
173 user: user,
174 occurredAt: {
175 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
176 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
177 }
178 })
179 .toArray()) as HistorySchema[];
pineafan4edb7762022-06-26 19:21:04 +0100180 return entry;
181 }
pineafane23c4ec2022-07-27 21:56:27 +0100182
183 async delete(guild: string) {
184 await this.histories.deleteMany({ guild: guild });
185 }
pineafan4edb7762022-06-26 19:21:04 +0100186}
187
188export class ModNotes {
189 modNotes: Collection<ModNoteSchema>;
190 defaultData: GuildConfig;
191
192 async setup() {
193 this.modNotes = database.collection<ModNoteSchema>("modNotes");
194 return this;
195 }
196
197 async create(guild: string, user: string, note: string | null) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100198 await this.modNotes.updateOne(
199 { guild: guild, user: user },
200 { $set: { note: note } },
201 { upsert: true }
202 );
pineafan4edb7762022-06-26 19:21:04 +0100203 }
204
205 async read(guild: string, user: string) {
pineafan63fc5e22022-08-04 22:04:10 +0100206 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100207 return entry?.note ?? null;
208 }
209}
210
pineafan73a7c4a2022-07-24 10:38:04 +0100211export class Premium {
212 premium: Collection<PremiumSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100213
214 async setup() {
pineafan73a7c4a2022-07-24 10:38:04 +0100215 this.premium = database.collection<PremiumSchema>("premium");
pineafan4edb7762022-06-26 19:21:04 +0100216 return this;
217 }
218
pineafan73a7c4a2022-07-24 10:38:04 +0100219 async hasPremium(guild: string) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100220 const entry = await this.premium.findOne({
221 appliesTo: { $in: [guild] }
222 });
pineafane23c4ec2022-07-27 21:56:27 +0100223 return entry !== null;
pineafan4edb7762022-06-26 19:21:04 +0100224 }
225}
226
pineafan6fb3e072022-05-20 19:27:23 +0100227export interface GuildConfig {
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 id: string;
229 version: number;
pineafan6fb3e072022-05-20 19:27:23 +0100230 singleEventNotifications: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100231 statsChannelDeleted: boolean;
232 };
pineafan6fb3e072022-05-20 19:27:23 +0100233 filters: {
234 images: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100235 NSFW: boolean;
236 size: boolean;
237 };
238 malware: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100239 wordFilter: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100240 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100241 words: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100242 strict: string[];
243 loose: string[];
244 };
pineafan6fb3e072022-05-20 19:27:23 +0100245 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100246 users: string[];
247 roles: string[];
248 channels: string[];
249 };
250 };
pineafan6fb3e072022-05-20 19:27:23 +0100251 invite: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100252 enabled: boolean;
253 channels: string[];
254 };
pineafan6fb3e072022-05-20 19:27:23 +0100255 pings: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100256 mass: number;
257 everyone: boolean;
258 roles: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100259 allowed: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100260 roles: string[];
261 rolesToMention: string[];
262 users: string[];
263 channels: string[];
264 };
265 };
266 };
pineafan6fb3e072022-05-20 19:27:23 +0100267 welcome: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 enabled: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100269 verificationRequired: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100270 message: boolean;
271 role: string | null;
272 };
273 role: string | null;
274 ping: string | null;
275 channel: string | null;
276 message: string | null;
277 };
278 stats: Record<string, { name: string; enabled: boolean }>;
pineafan6fb3e072022-05-20 19:27:23 +0100279 logging: {
280 logs: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100281 enabled: boolean;
282 channel: string | null;
283 toLog: string | null;
284 };
pineafan6fb3e072022-05-20 19:27:23 +0100285 staff: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100286 channel: string | null;
287 };
pineafan73a7c4a2022-07-24 10:38:04 +0100288 attachments: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100289 channel: string | null;
290 saved: Record<string, string>;
291 };
292 };
pineafan6fb3e072022-05-20 19:27:23 +0100293 verify: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100294 enabled: boolean;
295 role: string | null;
296 };
pineafan6fb3e072022-05-20 19:27:23 +0100297 tickets: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100298 enabled: boolean;
299 category: string | null;
300 types: string | null;
301 customTypes: string[];
302 useCustom: boolean;
303 supportRole: string | null;
304 maxTickets: number;
305 };
pineafan6fb3e072022-05-20 19:27:23 +0100306 moderation: {
307 mute: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100308 timeout: boolean;
309 role: string | null;
310 text: string | null;
311 link: string | null;
312 };
pineafan6fb3e072022-05-20 19:27:23 +0100313 kick: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100314 text: string | null;
315 link: string | null;
316 };
pineafan6fb3e072022-05-20 19:27:23 +0100317 ban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100318 text: string | null;
319 link: string | null;
320 };
pineafan6fb3e072022-05-20 19:27:23 +0100321 softban: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100322 text: string | null;
323 link: string | null;
324 };
pineafan6fb3e072022-05-20 19:27:23 +0100325 warn: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100326 text: string | null;
327 link: string | null;
328 };
pineafan6fb3e072022-05-20 19:27:23 +0100329 role: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100330 role: string | null;
331 };
332 };
pineafan6fb3e072022-05-20 19:27:23 +0100333 tracks: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100334 name: string;
335 retainPrevious: boolean;
336 nullable: boolean;
337 track: string[];
338 manageableBy: string[];
339 }[];
pineafan6fb3e072022-05-20 19:27:23 +0100340 roleMenu: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100341 enabled: boolean;
342 allowWebUI: boolean;
pineafan6fb3e072022-05-20 19:27:23 +0100343 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100344 name: string;
345 description: string;
346 min: number;
347 max: number;
pineafan6fb3e072022-05-20 19:27:23 +0100348 options: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100349 name: string;
350 description: string | null;
351 role: string;
352 }[];
353 }[];
354 };
355 tags: Record<string, string>;
pineafan63fc5e22022-08-04 22:04:10 +0100356}
pineafan4edb7762022-06-26 19:21:04 +0100357
358export interface HistorySchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100359 type: string;
360 guild: string;
361 user: string;
362 moderator: string | null;
363 reason: string;
364 occurredAt: Date;
365 before: string | null;
366 after: string | null;
367 amount: string | null;
pineafan4edb7762022-06-26 19:21:04 +0100368}
369
370export interface ModNoteSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100371 guild: string;
372 user: string;
373 note: string;
pineafan4edb7762022-06-26 19:21:04 +0100374}
375
pineafan73a7c4a2022-07-24 10:38:04 +0100376export interface PremiumSchema {
Skyler Grey75ea9172022-08-06 10:22:23 +0100377 user: string;
378 level: number;
379 expires: Date;
380 appliesTo: string[];
381}