/[sudobot]/branches/5.x/src/services/StartupManager.ts
ViewVC logotype

Annotation of /branches/5.x/src/services/StartupManager.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: 6786 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 { formatDistanceToNowStrict } from "date-fns";
21     import { APIEmbed, ActivityType, Colors, WebhookClient, escapeCodeBlock } from "discord.js";
22     import { existsSync, readFileSync } from "fs";
23     import { rm } from "fs/promises";
24     import path from "path";
25     import Service from "../core/Service";
26     import { GatewayEventListener } from "../decorators/GatewayEventListener";
27     import { HasEventListeners } from "../types/HasEventListeners";
28     import { safeChannelFetch, safeMessageFetch } from "../utils/fetch";
29     import { log, logError, logInfo } from "../utils/logger";
30     import { chunkedString, getEmoji, sudoPrefix } from "../utils/utils";
31    
32     export const name = "startupManager";
33    
34     const { BACKUP_CHANNEL_ID, ERROR_WEKHOOK_URL } = process.env;
35    
36     export default class StartupManager extends Service implements HasEventListeners {
37     interval: NodeJS.Timer | undefined = undefined;
38    
39     @GatewayEventListener("ready")
40     async onReady() {
41     if (BACKUP_CHANNEL_ID) {
42     this.setBackupQueue();
43     }
44    
45     if (ERROR_WEKHOOK_URL) {
46     log("Error webhook URL found. Setting up error handlers...");
47     this.setupErrorHandlers();
48     }
49    
50     const restartJsonFile = path.join(sudoPrefix("tmp", true), "restart.json");
51    
52     if (existsSync(restartJsonFile)) {
53     logInfo("Found restart.json file: ", restartJsonFile);
54    
55     try {
56     const { guildId, messageId, channelId, time } = JSON.parse(readFileSync(restartJsonFile, { encoding: "utf-8" }));
57    
58     const guild = this.client.guilds.cache.get(guildId);
59    
60     if (!guild) {
61     return;
62     }
63    
64     const channel = await safeChannelFetch(guild, channelId);
65    
66     if (!channel || !channel.isTextBased()) {
67     return;
68     }
69    
70     const message = await safeMessageFetch(channel, messageId);
71    
72     if (!message) {
73     return;
74     }
75    
76     await message.edit({
77     embeds: [
78     {
79     color: Colors.Green,
80     title: "System Restart",
81     description: `${getEmoji(this.client, "check")} Operation completed. (took ${(
82     (Date.now() - time) /
83     1000
84     ).toFixed(2)}s)`
85     }
86     ]
87     });
88     } catch (e) {
89     logError(e);
90     }
91    
92     rm(restartJsonFile).catch(logError);
93     }
94    
95     const { presence } = this.client.configManager.systemConfig;
96    
97     this.client.user?.setPresence({
98     activities: [
99     {
100     name: presence?.name ?? "Moderating the server",
101     type: ActivityType[presence?.type ?? "Custom"],
102     url: presence?.url
103     }
104     ],
105     status: presence?.status ?? "dnd"
106     });
107     }
108    
109     async sendErrorLog(content: string) {
110     const url = ERROR_WEKHOOK_URL;
111    
112     if (!url) {
113     return;
114     }
115    
116     const client = new WebhookClient({
117     url
118     });
119     const chunks = chunkedString(content, 4000);
120     const embeds: APIEmbed[] = [
121     {
122     title: "Fatal error",
123     color: 0xf14a60,
124     description: "```" + escapeCodeBlock(chunks[0]) + "```"
125     }
126     ];
127    
128     if (chunks.length > 1) {
129     for (let i = 1; i < chunks.length; i++) {
130     embeds.push({
131     color: 0xf14a60,
132     description: "```" + escapeCodeBlock(chunks[i]) + "```",
133     timestamp: i === chunks.length - 1 ? new Date().toISOString() : undefined
134     });
135     }
136     } else {
137     embeds[0].timestamp = new Date().toISOString();
138     }
139    
140     await client
141     .send({
142     embeds
143     })
144     .catch(logError);
145     }
146    
147     setupErrorHandlers() {
148     process.on("unhandledRejection", (reason: unknown) => {
149     process.removeAllListeners("unhandledRejection");
150     logError(reason);
151     this.sendErrorLog(
152     `Unhandled promise rejection: ${
153     typeof reason === "string" || typeof (reason as any)?.toString === "function"
154     ? escapeCodeBlock((reason as any)?.toString ? (reason as any).toString() : (reason as any))
155     : reason
156     }`
157     ).finally(() => process.exit(-1));
158     });
159    
160     process.on("uncaughtException", async (error: Error) => {
161     process.removeAllListeners("uncaughtException");
162     logError(error);
163     this.sendErrorLog(
164     error.stack ?? `Uncaught ${error.name.trim() === "" ? "Error" : error.name}: ${error.message}`
165     ).finally(() => process.exit(-1));
166     });
167     }
168    
169     async sendConfigBackupCopy() {
170     if (!BACKUP_CHANNEL_ID) {
171     return;
172     }
173    
174     const channel = this.client.channels.cache.get(BACKUP_CHANNEL_ID);
175    
176     if (!channel?.isTextBased()) {
177     return;
178     }
179    
180     await channel
181     ?.send({
182     content: "# Configuration Backup",
183     files: [this.client.configManager.configPath, this.client.configManager.systemConfigPath]
184     })
185     .catch(logError);
186     }
187    
188     setBackupQueue() {
189     const time = process.env.BACKUP_INTERVAL ? parseInt(process.env.BACKUP_INTERVAL) : 1000 * 60 * 60 * 2;
190     const finalTime = isNaN(time) ? 1000 * 60 * 60 * 2 : time;
191     this.interval = setInterval(this.sendConfigBackupCopy.bind(this), finalTime);
192     logInfo(`Configuration backups will be sent in each ${formatDistanceToNowStrict(new Date(Date.now() - finalTime))}`);
193     logInfo(`Sending initial backup`);
194     this.sendConfigBackupCopy();
195     }
196     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26