blob: cffdf048193706a444686a0ac2421fa38deed24a [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
4// @ts-ignore
5import structuredClone from "@ungap/structured-clone";
6import config from "../config/main.json" assert {type: "json"};
pineafan4edb7762022-06-26 19:21:04 +01007
8
9const mongoClient = new MongoClient(config.mongoUrl);
pineafan63fc5e22022-08-04 22:04:10 +010010await mongoClient.connect();
pineafan4edb7762022-06-26 19:21:04 +010011const database = mongoClient.db("Nucleus");
pineafan6fb3e072022-05-20 19:27:23 +010012
pineafan63fc5e22022-08-04 22:04:10 +010013// eslint-disable-next-line @typescript-eslint/no-explicit-any
14export const Entry = (data: any) => {
pineafan6fb3e072022-05-20 19:27:23 +010015 data = data ?? {};
pineafan63fc5e22022-08-04 22:04:10 +010016 // eslint-disable-next-line @typescript-eslint/no-explicit-any
17 data.getKey = (key: any) => data[key];
pineafan6fb3e072022-05-20 19:27:23 +010018 return {
pineafan63fc5e22022-08-04 22:04:10 +010019 // eslint-disable-next-line @typescript-eslint/no-explicit-any
20 get(target: Record<string, any>, prop: string, receiver: any) {
21 let dataToReturn = data[prop];
pineafan6fb3e072022-05-20 19:27:23 +010022 if (dataToReturn === null ) return Reflect.get(target, prop, receiver);
23 if (typeof dataToReturn === "object" && !Array.isArray(dataToReturn)) dataToReturn = new Proxy(
24 Reflect.get(target, prop, receiver),
pineafan63fc5e22022-08-04 22:04:10 +010025 Entry(dataToReturn)
26 );
pineafan6fb3e072022-05-20 19:27:23 +010027 return dataToReturn ?? Reflect.get(target, prop, receiver);
28 }
pineafan63fc5e22022-08-04 22:04:10 +010029 };
30};
pineafan6fb3e072022-05-20 19:27:23 +010031
32
pineafan4edb7762022-06-26 19:21:04 +010033export class Guilds {
pineafan6fb3e072022-05-20 19:27:23 +010034 guilds: Collection<GuildConfig>;
pineafan63fc5e22022-08-04 22:04:10 +010035 defaultData: GuildConfig | null;
36
37 constructor() {
pineafan4edb7762022-06-26 19:21:04 +010038 this.guilds = database.collection<GuildConfig>("guilds");
pineafan63fc5e22022-08-04 22:04:10 +010039 this.defaultData = null;
pineafan6fb3e072022-05-20 19:27:23 +010040 return this;
41 }
42
pineafan63fc5e22022-08-04 22:04:10 +010043 async setup() {
44 this.defaultData = (await import("../config/default.json", { assert: { type: "json" }})).default as unknown as GuildConfig;
45 }
46
pineafan6fb3e072022-05-20 19:27:23 +010047 async read(guild: string) {
pineafan63fc5e22022-08-04 22:04:10 +010048 const entry = await this.guilds.findOne({ id: guild });
49 return new Proxy(structuredClone(this.defaultData), Entry(entry)) as unknown as GuildConfig;
pineafan6fb3e072022-05-20 19:27:23 +010050 }
51
pineafane23c4ec2022-07-27 21:56:27 +010052 async write(guild: string, set: object | null, unset: string[] | string = []) {
pineafan63fc5e22022-08-04 22:04:10 +010053 // eslint-disable-next-line @typescript-eslint/no-explicit-any
54 const uo: Record<string, any> = {};
55 if (!Array.isArray(unset)) unset = [unset];
56 for (const key of unset) {
pineafan0bc04162022-07-25 17:22:26 +010057 uo[key] = null;
pineafan6702cef2022-06-13 17:52:37 +010058 }
pineafan63fc5e22022-08-04 22:04:10 +010059 const out = {$set: {}, $unset: {}};
pineafan0bc04162022-07-25 17:22:26 +010060 if (set) out["$set"] = set;
61 if (unset.length) out["$unset"] = uo;
62 await this.guilds.updateOne({ id: guild }, out, { upsert: true });
pineafan6702cef2022-06-13 17:52:37 +010063 }
64
pineafan63fc5e22022-08-04 22:04:10 +010065 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan6702cef2022-06-13 17:52:37 +010066 async append(guild: string, key: string, value: any) {
67 if (Array.isArray(value)) {
68 await this.guilds.updateOne({ id: guild }, {
69 $addToSet: { [key]: { $each: value } }
70 }, { upsert: true });
71 } else {
72 await this.guilds.updateOne({ id: guild }, {
73 $addToSet: { [key]: value }
74 }, { upsert: true });
75 }
76 }
77
pineafan63fc5e22022-08-04 22:04:10 +010078 // eslint-disable-next-line @typescript-eslint/no-explicit-any
pineafan0bc04162022-07-25 17:22:26 +010079 async remove(guild: string, key: string, value: any, innerKey?: string | null) {
pineafan63fc5e22022-08-04 22:04:10 +010080 console.log(Array.isArray(value));
pineafan02ba0232022-07-24 22:16:15 +010081 if (innerKey) {
82 await this.guilds.updateOne({ id: guild }, {
83 $pull: { [key]: { [innerKey]: { $eq: value } } }
84 }, { upsert: true });
pineafan0bc04162022-07-25 17:22:26 +010085 } else if (Array.isArray(value)) {
pineafan6702cef2022-06-13 17:52:37 +010086 await this.guilds.updateOne({ id: guild }, {
87 $pullAll: { [key]: value }
88 }, { upsert: true });
89 } else {
90 await this.guilds.updateOne({ id: guild }, {
91 $pullAll: { [key]: [value] }
92 }, { upsert: true });
93 }
pineafan6fb3e072022-05-20 19:27:23 +010094 }
pineafane23c4ec2022-07-27 21:56:27 +010095
96 async delete(guild: string) {
97 await this.guilds.deleteOne({ id: guild });
98 }
pineafan6fb3e072022-05-20 19:27:23 +010099}
100
pineafan4edb7762022-06-26 19:21:04 +0100101
102export class History {
103 histories: Collection<HistorySchema>;
104 defaultData: GuildConfig;
105
106 async setup() {
107 this.histories = database.collection<HistorySchema>("history");
108 return this;
109 }
110
111 async create(type: string, guild: string, user: Discord.User, moderator: Discord.User | null, reason: string | null, before?: null, after?: null, amount?: null) {
112 await this.histories.insertOne({
113 type: type,
114 guild: guild,
115 user: user.id,
116 moderator: moderator.id,
117 reason: reason,
118 occurredAt: new Date(),
119 before: before,
120 after: after,
121 amount: amount
122 });
123 }
124
125 async read(guild: string, user: string, year: number) {
pineafan63fc5e22022-08-04 22:04:10 +0100126 const entry = (await this.histories.find({
pineafan4edb7762022-06-26 19:21:04 +0100127 guild: guild,
128 user: user,
129 occurredAt: {
130 $gte: new Date(year - 1, 11, 31, 23, 59, 59),
131 $lt: new Date(year + 1, 0, 1, 0, 0, 0)
132 }
133 }).toArray()) as HistorySchema[];
134 return entry;
135 }
pineafane23c4ec2022-07-27 21:56:27 +0100136
137 async delete(guild: string) {
138 await this.histories.deleteMany({ guild: guild });
139 }
pineafan4edb7762022-06-26 19:21:04 +0100140}
141
142export class ModNotes {
143 modNotes: Collection<ModNoteSchema>;
144 defaultData: GuildConfig;
145
146 async setup() {
147 this.modNotes = database.collection<ModNoteSchema>("modNotes");
148 return this;
149 }
150
151 async create(guild: string, user: string, note: string | null) {
152 await this.modNotes.updateOne({ guild: guild, user: user }, { $set: { note: note }}, { upsert: true });
153 }
154
155 async read(guild: string, user: string) {
pineafan63fc5e22022-08-04 22:04:10 +0100156 const entry = await this.modNotes.findOne({ guild: guild, user: user });
pineafan4edb7762022-06-26 19:21:04 +0100157 return entry?.note ?? null;
158 }
159}
160
pineafan73a7c4a2022-07-24 10:38:04 +0100161export class Premium {
162 premium: Collection<PremiumSchema>;
pineafan4edb7762022-06-26 19:21:04 +0100163
164 async setup() {
pineafan73a7c4a2022-07-24 10:38:04 +0100165 this.premium = database.collection<PremiumSchema>("premium");
pineafan4edb7762022-06-26 19:21:04 +0100166 return this;
167 }
168
pineafan73a7c4a2022-07-24 10:38:04 +0100169 async hasPremium(guild: string) {
pineafan63fc5e22022-08-04 22:04:10 +0100170 const entry = await this.premium.findOne({ appliesTo: { $in: [guild] } });
pineafane23c4ec2022-07-27 21:56:27 +0100171 return entry !== null;
pineafan4edb7762022-06-26 19:21:04 +0100172 }
173}
174
pineafan6fb3e072022-05-20 19:27:23 +0100175export interface GuildConfig {
176 id: string,
177 version: number,
178 singleEventNotifications: {
179 statsChannelDeleted: boolean
180 }
181 filters: {
182 images: {
183 NSFW: boolean,
184 size: boolean
185 },
186 malware: boolean,
187 wordFilter: {
188 enabled: boolean,
189 words: {
190 strict: string[],
191 loose: string[]
192 },
193 allowed: {
194 users: string[],
195 roles: string[],
196 channels: string[]
197 }
198 },
199 invite: {
200 enabled: boolean,
pineafan63fc5e22022-08-04 22:04:10 +0100201 channels: string[]
pineafan6fb3e072022-05-20 19:27:23 +0100202 },
203 pings: {
204 mass: number,
205 everyone: boolean,
206 roles: boolean,
207 allowed: {
208 roles: string[],
209 rolesToMention: string[],
210 users: string[],
211 channels: string[]
212 }
213 }
214 }
215 welcome: {
216 enabled: boolean,
217 verificationRequired: {
218 message: boolean,
pineafan6702cef2022-06-13 17:52:37 +0100219 role: string | null
pineafan6fb3e072022-05-20 19:27:23 +0100220 },
pineafan41d93562022-07-30 22:10:15 +0100221 role: string | null,
222 ping: string | null,
pineafan6702cef2022-06-13 17:52:37 +0100223 channel: string | null,
224 message: string | null,
pineafan6fb3e072022-05-20 19:27:23 +0100225 }
pineafan63fc5e22022-08-04 22:04:10 +0100226 stats: Record<string, {name: string, enabled: boolean}>
pineafan6fb3e072022-05-20 19:27:23 +0100227 logging: {
228 logs: {
229 enabled: boolean,
pineafan6702cef2022-06-13 17:52:37 +0100230 channel: string | null,
231 toLog: string | null,
pineafan6fb3e072022-05-20 19:27:23 +0100232 },
233 staff: {
pineafan6702cef2022-06-13 17:52:37 +0100234 channel: string | null,
pineafan73a7c4a2022-07-24 10:38:04 +0100235 },
236 attachments: {
237 channel: string | null,
pineafan63fc5e22022-08-04 22:04:10 +0100238 saved: Record<string, string>
pineafan6fb3e072022-05-20 19:27:23 +0100239 }
240 }
241 verify: {
242 enabled: boolean,
pineafan6702cef2022-06-13 17:52:37 +0100243 role: string | null,
pineafan6fb3e072022-05-20 19:27:23 +0100244 }
245 tickets: {
246 enabled: boolean,
pineafan6702cef2022-06-13 17:52:37 +0100247 category: string | null,
248 types: string | null,
pineafan6fb3e072022-05-20 19:27:23 +0100249 customTypes: string[],
pineafan6702cef2022-06-13 17:52:37 +0100250 useCustom: boolean,
251 supportRole: string | null,
pineafan6fb3e072022-05-20 19:27:23 +0100252 maxTickets: number
253 }
254 moderation: {
255 mute: {
256 timeout: boolean,
pineafan6702cef2022-06-13 17:52:37 +0100257 role: string | null,
258 text: string | null,
259 link: string | null
pineafan6fb3e072022-05-20 19:27:23 +0100260 },
261 kick: {
pineafan6702cef2022-06-13 17:52:37 +0100262 text: string | null,
263 link: string | null
pineafan6fb3e072022-05-20 19:27:23 +0100264 },
265 ban: {
pineafan6702cef2022-06-13 17:52:37 +0100266 text: string | null,
267 link: string | null
pineafan6fb3e072022-05-20 19:27:23 +0100268 },
269 softban: {
pineafan6702cef2022-06-13 17:52:37 +0100270 text: string | null,
271 link: string | null
pineafan6fb3e072022-05-20 19:27:23 +0100272 },
273 warn: {
pineafan6702cef2022-06-13 17:52:37 +0100274 text: string | null,
275 link: string | null
pineafan6fb3e072022-05-20 19:27:23 +0100276 },
277 role: {
pineafan6702cef2022-06-13 17:52:37 +0100278 role: string | null,
pineafan6fb3e072022-05-20 19:27:23 +0100279 }
280 }
281 tracks: {
282 name: string,
283 retainPrevious: boolean,
284 nullable: boolean,
285 track: string[],
286 manageableBy: string[]
287 }[]
288 roleMenu: {
289 enabled: boolean,
290 allowWebUI: boolean,
291 options: {
292 name: string,
293 description: string,
294 min: number,
295 max: number,
296 options: {
297 name: string,
pineafan4edb7762022-06-26 19:21:04 +0100298 description: string | null,
pineafan6fb3e072022-05-20 19:27:23 +0100299 role: string
300 }[]
301 }[]
302 }
pineafan63fc5e22022-08-04 22:04:10 +0100303 tags: Record<string, string>
304}
pineafan4edb7762022-06-26 19:21:04 +0100305
306export interface HistorySchema {
307 type: string,
308 guild: string,
309 user: string,
310 moderator: string | null,
311 reason: string,
312 occurredAt: Date,
313 before: string | null,
314 after: string | null,
315 amount: string | null
316}
317
318export interface ModNoteSchema {
319 guild: string,
320 user: string,
321 note: string
322}
323
pineafan73a7c4a2022-07-24 10:38:04 +0100324export interface PremiumSchema {
325 user: string,
326 level: number,
327 expires: Date,
328 appliesTo: string[]
pineafan4edb7762022-06-26 19:21:04 +0100329}