/[sudobot]/branches/6.x/src/core/Client.ts
ViewVC logotype

Annotation of /branches/6.x/src/core/Client.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (hide annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 14321 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 rakinar2 577 /**
2     * This file is part of SudoBot.
3     *
4     * Copyright (C) 2021-2023 OSN Developers.
5     *
6     * SudoBot is free software; you can redistribute it and/or modify it
7     * under the terms of the GNU Affero General Public License as published by
8     * the Free Software Foundation, either version 3 of the License, or
9     * (at your option) any later version.
10     *
11     * SudoBot is distributed in the hope that it will be useful, but
12     * WITHOUT ANY WARRANTY; without even the implied warranty of
13     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     * GNU Affero General Public License for more details.
15     *
16     * You should have received a copy of the GNU Affero General Public License
17     * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18     */
19    
20     import { PrismaClient } from "@prisma/client";
21     import { Awaitable, ClientOptions, Collection, Client as DiscordClient, GuildEmoji, UserResolvable } from "discord.js";
22     import { Stats } from "fs";
23     import fs from "fs/promises";
24     import path, { basename, dirname } from "path";
25     import Server from "../api/Server";
26     import type AIAutoModService from "../automod/AIAutoModService";
27     import type Antijoin from "../automod/Antijoin";
28     import type Antiraid from "../automod/Antiraid";
29     import type Antispam from "../automod/Antispam";
30     import type FileFilterService from "../automod/FileFilterService";
31     import type MessageFilter from "../automod/MessageFilter";
32     import type MessageRuleService from "../automod/MessageRuleService";
33     import type ProfileFilter from "../automod/ProfileFilter";
34     import { SuppressErrorsMetadata } from "../decorators/SuppressErrors";
35     import type AFKService from "../services/AFKService";
36     import type AutoRoleService from "../services/AutoRoleService";
37     import type BallotManager from "../services/BallotManager";
38     import type BumpReminderService from "../services/BumpReminderService";
39     import type ChannelLockManager from "../services/ChannelLockManager";
40     import type CommandManager from "../services/CommandManager";
41     import CommandPermissionOverwriteManager from "../services/CommandPermissionOverwriteManager";
42     import type ConfigManager from "../services/ConfigManager";
43     import type CooldownService from "../services/CooldownService";
44     import type ExtensionService from "../services/ExtensionService";
45     import type InfractionManager from "../services/InfractionManager";
46     import type InviteTrackerService from "../services/InviteTrackerService";
47     import type KeypressHandlerService from "../services/KeypressHandlerService";
48     import type LogServer from "../services/LogServer";
49     import type LoggerService from "../services/LoggerService";
50     import type MetadataService from "../services/MetadataService";
51     import type PermissionManager from "../services/PermissionManager";
52     import type QueueManager from "../services/QueueManager";
53     import type QuickMuteService from "../services/QuickMuteService";
54     import type ReactionRoleService from "../services/ReactionRoleService";
55     import type SnippetManager from "../services/SnippetManager";
56     import type StartupManager from "../services/StartupManager";
57     import type TranslationService from "../services/TranslationService";
58     import type TriggerService from "../services/TriggerService";
59     import type WelcomerService from "../services/WelcomerService";
60     import { CustomEvents, type ClientEvents } from "../types/ClientEvents";
61     import { log, logError, logInfo } from "../utils/logger";
62     import { developmentMode } from "../utils/utils";
63     import type Command from "./Command";
64     import ServiceManager from "./ServiceManager";
65    
66     export default class Client<Ready extends boolean = boolean> extends DiscordClient<Ready> {
67     aliases = {
68     "@services": path.resolve(__dirname, "../services"),
69     "@automod": path.resolve(__dirname, "../automod")
70     };
71    
72     services = [
73     "@services/StartupManager",
74     "@services/ConfigManager",
75     "@services/CommandManager",
76     "@services/InfractionManager",
77     "@services/LoggerService",
78     "@services/QueueManager",
79     "@services/WelcomerService",
80     "@services/SnippetManager",
81     "@services/ChannelLockManager",
82     "@services/PermissionManager",
83     "@services/MetadataService",
84     "@services/QuickMuteService",
85     "@services/TranslationService",
86     "@services/AutoRoleService",
87     "@services/ReactionRoleService",
88     "@services/AFKService",
89     "@services/InviteTrackerService",
90     "@services/BallotManager",
91     "@services/TriggerService",
92     "@services/ExtensionService",
93     "@services/BumpReminderService",
94     "@services/LogServer",
95     "@services/CooldownService",
96     "@services/KeypressHandlerService",
97     "@services/CommandPermissionOverwriteManager",
98    
99     "@automod/MessageFilter",
100     "@automod/Antispam",
101     "@automod/Antiraid",
102     "@automod/Antijoin",
103     "@automod/ProfileFilter",
104     "@automod/FileFilterService",
105     "@automod/MessageRuleService",
106     "@automod/AIAutoModService"
107     ];
108    
109     commandsDirectory = path.resolve(__dirname, "../commands");
110     eventsDirectory = path.resolve(__dirname, "../events");
111    
112     serviceManager = new ServiceManager(this);
113    
114     startupManager: StartupManager = {} as StartupManager;
115     configManager: ConfigManager = {} as ConfigManager;
116     commandManager: CommandManager = {} as CommandManager;
117     infractionManager: InfractionManager = {} as InfractionManager;
118     logger: LoggerService = {} as LoggerService;
119     messageFilter: MessageFilter = {} as MessageFilter;
120     antispam: Antispam = {} as Antispam;
121     queueManager: QueueManager = {} as QueueManager;
122     snippetManager: SnippetManager = {} as SnippetManager;
123     welcomerService: WelcomerService = {} as WelcomerService;
124     antiraid: Antiraid = {} as Antiraid;
125     channelLockManager: ChannelLockManager = {} as ChannelLockManager;
126     antijoin: Antijoin = {} as Antijoin;
127     profileFilter: ProfileFilter = {} as ProfileFilter;
128     permissionManager: PermissionManager = {} as PermissionManager;
129     metadata: MetadataService = {} as MetadataService;
130     quickMute: QuickMuteService = {} as QuickMuteService;
131     translator: TranslationService = {} as TranslationService;
132     autoRoleService: AutoRoleService = {} as AutoRoleService;
133     reactionRoleService: ReactionRoleService = {} as ReactionRoleService;
134     afkService: AFKService = {} as AFKService;
135     inviteTracker: InviteTrackerService = {} as InviteTrackerService;
136     ballotManager: BallotManager = {} as BallotManager;
137     fileFilter: FileFilterService = {} as FileFilterService;
138     messageRuleService: MessageRuleService = {} as MessageRuleService;
139     triggerService: TriggerService = {} as TriggerService;
140     aiAutoMod: AIAutoModService = {} as AIAutoModService;
141     extensionService: ExtensionService = {} as ExtensionService;
142     bumpReminder: BumpReminderService = {} as BumpReminderService;
143     logServer: LogServer = {} as LogServer;
144     cooldown: CooldownService = {} as CooldownService;
145     keypressHandler: KeypressHandlerService = {} as KeypressHandlerService;
146     commandPermissionOverwriteManager: CommandPermissionOverwriteManager = {} as CommandPermissionOverwriteManager;
147    
148     prisma = new PrismaClient({
149     errorFormat: "pretty",
150     log: developmentMode() ? ["query", "error", "info", "warn"] : ["error", "info", "warn"]
151     });
152    
153     server = new Server(this);
154    
155     commands = new Collection<string, Command>();
156     emojiMap = new Collection<string, GuildEmoji>();
157     registeredEvents: string[] = [];
158     eventMap = new Map<keyof ClientEvents, Function[]>();
159    
160     private static _instance: Client;
161    
162     static get instance() {
163     return this._instance;
164     }
165    
166     constructor(options: ClientOptions, { services }: { services?: string[] } = {}) {
167     super(options);
168     Client._instance = this;
169    
170     if (services) {
171     this.services = services;
172     }
173     }
174    
175     async boot() {
176     await this.serviceManager.loadServices();
177     await this.extensionService.bootUp();
178     }
179    
180     async fetchUserSafe(user: UserResolvable) {
181     try {
182     return await this.users.fetch(user);
183     } catch (e) {
184     logError(e);
185     return null;
186     }
187     }
188    
189     async loadCommands(
190     directory = this.commandsDirectory,
191     metadataLoader?: Function,
192     filter?: (path: string, name: string, info: Stats) => Awaitable<boolean>
193     ) {
194     const files = await fs.readdir(directory);
195     const includeOnly = process.env.COMMANDS?.split(",");
196    
197     for (const file of files) {
198     const filePath = path.join(directory, file);
199     const info = await fs.lstat(filePath);
200     const isDirectory = info.isDirectory();
201    
202     if (isDirectory) {
203     await this.loadCommands(filePath, metadataLoader, filter);
204     continue;
205     }
206    
207     if ((!file.endsWith(".ts") && !file.endsWith(".js")) || file.endsWith(".d.ts")) {
208     continue;
209     }
210    
211     if (includeOnly && !includeOnly.includes(file.replace(/\.(ts|js)/gi, ""))) {
212     continue;
213     }
214    
215     if (filter !== undefined && !(await filter(filePath, file, info))) {
216     continue;
217     }
218    
219     await this.loadCommand(filePath, metadataLoader);
220     }
221     }
222    
223     async loadCommand(filePath: string, metadataLoader?: Function) {
224     const { default: CommandClass }: { default: new (client: Client) => Command } = await import(filePath);
225     const command = new CommandClass(this);
226     command.group = basename(dirname(filePath));
227     this.commands.set(command.name, command);
228    
229     for (const alias of command.aliases) {
230     this.commands.set(alias, command);
231     }
232    
233     if (!metadataLoader) {
234     this.loadEventListenersFromMetadata(CommandClass, command);
235     } else {
236     metadataLoader(CommandClass, command);
237     log("Custom metadata loader found");
238     }
239    
240     logInfo("Loaded command: ", command.name);
241     }
242    
243     async loadEvents(directory = this.eventsDirectory) {
244     const files = await fs.readdir(directory);
245    
246     for (const file of files) {
247     const filePath = path.join(directory, file);
248     const isDirectory = (await fs.lstat(filePath)).isDirectory();
249    
250     if (isDirectory) {
251     await this.loadEvents(filePath);
252     continue;
253     }
254    
255     if ((!file.endsWith(".ts") && !file.endsWith(".js")) || file.endsWith(".d.ts")) {
256     continue;
257     }
258    
259     await this.loadEvent(filePath, true);
260     }
261     }
262    
263     async loadEvent(filePath: string, addToList = false) {
264     const { default: Event } = await import(filePath);
265     const event = new Event(this);
266     this.on(event.name, event.execute.bind(event));
267    
268     if (addToList) {
269     this.registeredEvents.push(event.name);
270     }
271     }
272    
273     private supressErrorMessagesHandler(suppressErrors: SuppressErrorsMetadata, e: unknown) {
274     if (suppressErrors.mode === "log") {
275     logError(e);
276     } else if (suppressErrors.mode === "disabled") {
277     throw e;
278     }
279     }
280    
281     loadEventListenersFromMetadata<I extends Object = Object>(Class: I["constructor"], instance?: I) {
282     const metadata: { event: keyof ClientEvents; handler: Function; methodName: string }[] | undefined = Reflect.getMetadata(
283     "event_listeners",
284     Class.prototype
285     );
286    
287     if (metadata) {
288     for (const data of metadata) {
289     const callback = instance ? data.handler.bind(instance) : data.handler;
290     const suppressErrors: SuppressErrorsMetadata | undefined = Reflect.getMetadata(
291     "supress_errors",
292     Class.prototype,
293     data.methodName
294     );
295    
296     this.addEventListener(
297     data.event,
298     suppressErrors
299     ? (...args: any[]) => {
300     try {
301     const ret = callback(...args);
302    
303     if (ret instanceof Promise) ret.catch(e => this.supressErrorMessagesHandler(suppressErrors, e));
304    
305     return ret;
306     } catch (e) {
307     this.supressErrorMessagesHandler(suppressErrors, e);
308     }
309     }
310     : callback
311     );
312     }
313     }
314     }
315    
316     addEventListener<K extends keyof ClientEvents>(event: K, callback: (...args: ClientEvents[K]) => any) {
317     const handlers = this.eventMap.get(event) ?? [];
318    
319     if (!this.eventMap.has(event) && !CustomEvents.includes(event)) {
320     this.on(event as any, (...args: any[]) => this.fireEvent(event, ...args));
321     log("Registered parent handler for event: ", event);
322     }
323    
324     handlers.push(callback);
325     this.eventMap.set(event, handlers);
326    
327     log("Added event listener for event: ", event);
328     }
329    
330     async emitWait<K extends keyof ClientEvents>(eventName: K, ...args: ClientEvents[K]) {
331     const handlers = this.eventMap.get(eventName);
332    
333     if (handlers) {
334     for (const handler of handlers) {
335     await handler(...args);
336     }
337     }
338     }
339    
340     async getHomeGuild() {
341     const id = process.env.HOME_GUILD_ID;
342    
343     if (!id) {
344     logError(
345     "Environment variable `HOME_GUILD_ID` is not set. The bot can't work without it. Please follow the setup guide in the bot documentation."
346     );
347     process.exit(-1);
348     }
349    
350     try {
351     return this.guilds.cache.get(id) || (await this.guilds.fetch(id));
352     } catch (e) {
353     logError(e);
354     logError("Error fetching home guild: make sure the ID inside `HOME_GUILD_ID` environment variable is correct.");
355     process.exit(-1);
356     }
357     }
358    
359     fireEvent(name: keyof ClientEvents, ...args: any[]) {
360     const handlers = this.eventMap.get(name);
361    
362     if (handlers) {
363     for (const handler of handlers) {
364     handler(...args);
365     }
366     }
367     }
368     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26