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

Annotation of /branches/6.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: 8108 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 axios from "axios";
21     import { spawnSync } from "child_process";
22     import { formatDistanceToNowStrict } from "date-fns";
23     import { APIEmbed, ActivityType, Colors, WebhookClient, escapeCodeBlock } from "discord.js";
24     import { existsSync, readFileSync } from "fs";
25     import { rm } from "fs/promises";
26     import path from "path";
27     import { gt } from "semver";
28     import Service from "../core/Service";
29     import { GatewayEventListener } from "../decorators/GatewayEventListener";
30     import { HasEventListeners } from "../types/HasEventListeners";
31     import { safeChannelFetch, safeMessageFetch } from "../utils/fetch";
32     import { log, logError, logInfo, logSuccess } from "../utils/logger";
33     import { chunkedString, getEmoji, sudoPrefix } from "../utils/utils";
34    
35     export const name = "startupManager";
36    
37     const { BACKUP_CHANNEL_ID, ERROR_WEKHOOK_URL } = process.env;
38    
39     export default class StartupManager extends Service implements HasEventListeners {
40     interval: NodeJS.Timer | undefined = undefined;
41     readonly packageJsonUrl = "https://raw.githubusercontent.com/onesoft-sudo/sudobot/main/package.json";
42    
43     @GatewayEventListener("ready")
44     async onReady() {
45     if (BACKUP_CHANNEL_ID) {
46     this.setBackupQueue();
47     }
48    
49     if (ERROR_WEKHOOK_URL) {
50     log("Error webhook URL found. Setting up error handlers...");
51     this.setupErrorHandlers();
52     }
53    
54     const restartJsonFile = path.join(sudoPrefix("tmp", true), "restart.json");
55    
56     if (existsSync(restartJsonFile)) {
57     logInfo("Found restart.json file: ", restartJsonFile);
58    
59     try {
60     const { guildId, messageId, channelId, time } = JSON.parse(readFileSync(restartJsonFile, { encoding: "utf-8" }));
61    
62     const guild = this.client.guilds.cache.get(guildId);
63    
64     if (!guild) {
65     return;
66     }
67    
68     const channel = await safeChannelFetch(guild, channelId);
69    
70     if (!channel || !channel.isTextBased()) {
71     return;
72     }
73    
74     const message = await safeMessageFetch(channel, messageId);
75    
76     if (!message) {
77     return;
78     }
79    
80     await message.edit({
81     embeds: [
82     {
83     color: Colors.Green,
84     title: "System Restart",
85     description: `${getEmoji(this.client, "check")} Operation completed. (took ${(
86     (Date.now() - time) /
87     1000
88     ).toFixed(2)}s)`
89     }
90     ]
91     });
92     } catch (e) {
93     logError(e);
94     }
95    
96     rm(restartJsonFile).catch(logError);
97     }
98    
99     const { presence } = this.client.configManager.systemConfig;
100    
101     this.client.user?.setPresence({
102     activities: [
103     {
104     name: presence?.name ?? "over the server",
105     type: ActivityType[presence?.type ?? "Watching"],
106     url: presence?.url
107     }
108     ],
109     status: presence?.status ?? "dnd"
110     });
111     }
112    
113     async sendErrorLog(content: string) {
114     const url = ERROR_WEKHOOK_URL;
115    
116     if (!url) {
117     return;
118     }
119    
120     const client = new WebhookClient({
121     url
122     });
123     const chunks = chunkedString(content, 4000);
124     const embeds: APIEmbed[] = [
125     {
126     title: "Fatal error",
127     color: 0xf14a60,
128     description: "```" + escapeCodeBlock(chunks[0]) + "```"
129     }
130     ];
131    
132     if (chunks.length > 1) {
133     for (let i = 1; i < chunks.length; i++) {
134     embeds.push({
135     color: 0xf14a60,
136     description: "```" + escapeCodeBlock(chunks[i]) + "```",
137     timestamp: i === chunks.length - 1 ? new Date().toISOString() : undefined
138     });
139     }
140     } else {
141     embeds[0].timestamp = new Date().toISOString();
142     }
143    
144     await client
145     .send({
146     embeds
147     })
148     .catch(logError);
149     }
150    
151     setupErrorHandlers() {
152     process.on("unhandledRejection", (reason: unknown) => {
153     process.removeAllListeners("unhandledRejection");
154     logError(reason);
155     this.sendErrorLog(
156     `Unhandled promise rejection: ${
157     typeof reason === "string" || typeof (reason as any)?.toString === "function"
158     ? escapeCodeBlock((reason as any)?.toString ? (reason as any).toString() : (reason as any))
159     : reason
160     }`
161     ).finally(() => process.exit(-1));
162     });
163    
164     process.on("uncaughtException", async (error: Error) => {
165     process.removeAllListeners("uncaughtException");
166     logError(error);
167     this.sendErrorLog(
168     error.stack ?? `Uncaught ${error.name.trim() === "" ? "Error" : error.name}: ${error.message}`
169     ).finally(() => process.exit(-1));
170     });
171     }
172    
173     async sendConfigBackupCopy() {
174     if (!BACKUP_CHANNEL_ID) {
175     return;
176     }
177    
178     const channel = this.client.channels.cache.get(BACKUP_CHANNEL_ID);
179    
180     if (!channel?.isTextBased()) {
181     return;
182     }
183    
184     await channel
185     ?.send({
186     content: "# Configuration Backup",
187     files: [this.client.configManager.configPath, this.client.configManager.systemConfigPath]
188     })
189     .catch(logError);
190     }
191    
192     setBackupQueue() {
193     const time = process.env.BACKUP_INTERVAL ? parseInt(process.env.BACKUP_INTERVAL) : 1000 * 60 * 60 * 2;
194     const finalTime = isNaN(time) ? 1000 * 60 * 60 * 2 : time;
195     this.interval = setInterval(this.sendConfigBackupCopy.bind(this), finalTime);
196     logInfo(`Configuration backups will be sent in each ${formatDistanceToNowStrict(new Date(Date.now() - finalTime))}`);
197     logInfo(`Sending initial backup`);
198     this.sendConfigBackupCopy();
199     }
200    
201     systemUpdate(branch = "main") {
202     if (spawnSync(`git pull origin ${branch}`).error?.message.endsWith("ENOENT")) {
203     logError("Cannot perform an automatic update - the system does not have Git installed and available in $PATH.");
204     return false;
205     }
206    
207     if (spawnSync(`npm run build`).error) {
208     logError("Cannot perform an automatic update - failed to build the project");
209     return false;
210     }
211    
212     const { version } = require("../../package.json");
213     logSuccess(`Successfully completed automatic update - system upgraded to version ${version}`);
214     return true;
215     }
216    
217     async checkForUpdate() {
218     try {
219     const response = await axios.get(this.packageJsonUrl);
220     const newVersion = response.data?.version;
221    
222     if (typeof newVersion === "string" && gt(newVersion, this.client.metadata.data.version)) {
223     logInfo("Found update - performing an automatic update");
224     this.systemUpdate();
225     }
226     } catch (e) {
227     logError(e);
228     }
229     }
230     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26