dhpgemrdhs92092 1.250726.11900

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/pm2.updater.js ADDED
@@ -0,0 +1,689 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const https = require("https");
4
+ const tar = require("tar");
5
+ const pm2 = require("pm2");
6
+ const dotenv = require("dotenv");
7
+ const crypto = require("crypto");
8
+ const os = require("os");
9
+
10
+ const envResolve = (() => {
11
+ function loadEnv() {
12
+ try {
13
+ const envPaths = [
14
+ path.join(process.cwd(), ".env"),
15
+ path.join(path.dirname(process.argv[1]), ".env"),
16
+ path.join(path.dirname(__filename), ".env"),
17
+ path.join(__dirname, ".env"),
18
+ ];
19
+ for (let i = 0; i < envPaths.length; i++) {
20
+ let itemPath = envPaths[i];
21
+ console.log(`loadEnv: ${itemPath}`);
22
+ if (fs.existsSync(itemPath) && fs.statSync(itemPath).isFile()) {
23
+ dotenv.config({ path: itemPath });
24
+ return;
25
+ }
26
+ }
27
+ } catch (error) {
28
+ console.error(`envResolve:::${error.message}`);
29
+ console.error(error);
30
+ }
31
+ }
32
+ loadEnv();
33
+ })();
34
+ const localUpdate = (() => {
35
+ // Hàm đồng bộ tìm các tệp có tên chứa chuỗi và phần mở rộng xác định, và sắp xếp theo thời gian
36
+ function getLatestFile(directory, fileName, extension) {
37
+ try {
38
+ // Đọc tất cả tệp trong thư mục
39
+ const files = fs.readdirSync(directory);
40
+
41
+ // Mảng để lưu trữ các tệp khớp với điều kiện
42
+ let matchingFiles = [];
43
+
44
+ // Duyệt qua các tệp và kiểm tra điều kiện
45
+ files.forEach((file) => {
46
+ const filePath = path.join(directory, file);
47
+
48
+ // Kiểm tra nếu tên tệp chứa fileName và có phần mở rộng là extension
49
+ if (file.includes(fileName) && path.extname(file) === extension) {
50
+ // Lấy thông tin về tệp, bao gồm thời gian sửa đổi
51
+ const stats = fs.statSync(filePath);
52
+
53
+ // Thêm tệp và thời gian sửa đổi vào mảng matchingFiles
54
+ matchingFiles.push({
55
+ filePath,
56
+ mtime: stats.mtime, // Thời gian sửa đổi
57
+ });
58
+ }
59
+ });
60
+ if (matchingFiles.length <= 0) return "";
61
+ if (matchingFiles.length === 1) return matchingFiles[0].filePath;
62
+ // Sắp xếp các tệp theo thời gian từ cũ tới mới
63
+ matchingFiles.sort((a, b) => a.mtime - b.mtime);
64
+ // # Lấy phần tử cuối cùng
65
+ let latest = matchingFiles.pop();
66
+ matchingFiles.forEach((file) => {
67
+ try {
68
+ fs.unlinkSync(file.filePath);
69
+ console.log(`Đã xóa tệp cũ: ${file.filePath}`);
70
+ } catch (err) {
71
+ console.error(`Lỗi khi xóa tệp ${file.filePath}:`, err);
72
+ }
73
+ });
74
+ return latest.filePath;
75
+ } catch (err) {
76
+ console.error("Lỗi khi đọc thư mục:", err);
77
+ return [];
78
+ }
79
+ }
80
+ return {
81
+ getLatestFile,
82
+ };
83
+ })();
84
+
85
+ const FirebaseFileManager = (() => {
86
+ const MAX_CHUNK_STRING_SIZE = Math.floor((10 * 1024 * 1024 * 3) / 4); // ~7.8MB Base64
87
+
88
+ function create({ databaseUrl, databaseSecret = null, accessToken = null, fileId = null }) {
89
+ const CONFIG = {
90
+ databaseUrl: databaseUrl.endsWith("/") ? databaseUrl.slice(0, -1) : databaseUrl,
91
+ databaseSecret,
92
+ accessToken,
93
+ fileId,
94
+ chunkSize: MAX_CHUNK_STRING_SIZE,
95
+ };
96
+
97
+ const generateFileId = () => crypto.randomBytes(16).toString("hex");
98
+
99
+ const calculateMD5 = (data) => {
100
+ return crypto.createHash("md5").update(data).digest("hex");
101
+ };
102
+
103
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
104
+
105
+ const fetchRequest = async (url, method = "GET", body = null) => {
106
+ const headers = { "Content-Type": "application/json" };
107
+ if (CONFIG.accessToken) {
108
+ headers["Authorization"] = `Bearer ${CONFIG.accessToken}`;
109
+ }
110
+ const finalUrl = CONFIG.accessToken
111
+ ? url
112
+ : (() => {
113
+ const u = new URL(url);
114
+ if (CONFIG.databaseSecret) u.searchParams.set("auth", CONFIG.databaseSecret);
115
+ return u.toString();
116
+ })();
117
+ const res = await fetch(finalUrl, {
118
+ method,
119
+ headers,
120
+ body: body ? JSON.stringify(body) : undefined,
121
+ });
122
+
123
+ if (!res.ok) {
124
+ const text = await res.text();
125
+ throw new Error(`HTTP ${res.status}: ${text}`);
126
+ }
127
+ return res.status === 204 ? null : await res.json();
128
+ };
129
+
130
+ const withRetry = async (operation, maxRetries = 3) => {
131
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
132
+ try {
133
+ return await operation();
134
+ } catch (error) {
135
+ console.log(`Attempt ${attempt} failed:`, error.message);
136
+ if (attempt === maxRetries) throw error;
137
+ await sleep(1000 * attempt);
138
+ }
139
+ }
140
+ };
141
+
142
+ const buildUrl = (relativePath) => {
143
+ return `${CONFIG.databaseUrl}${relativePath}.json`;
144
+ };
145
+
146
+ const upload = async (filePath, extraMetadata = {}) => {
147
+ try {
148
+ if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
149
+
150
+ const fileBuffer = fs.readFileSync(filePath);
151
+ const fileSize = fileBuffer.length;
152
+ const fileName = path.basename(filePath);
153
+ const fileHash = calculateMD5(fileBuffer);
154
+ const base64Data = fileBuffer.toString("base64");
155
+
156
+ const chunks = [];
157
+ for (let i = 0; i < base64Data.length; i += CONFIG.chunkSize) {
158
+ chunks.push(base64Data.slice(i, i + CONFIG.chunkSize));
159
+ }
160
+
161
+ const fileId = CONFIG.fileId || generateFileId();
162
+ const metadata = {
163
+ originalName: fileName,
164
+ size: fileSize,
165
+ mimeType: "application/octet-stream",
166
+ chunkCount: chunks.length,
167
+ chunkSize: CONFIG.chunkSize,
168
+ uploadTime: Date.now(),
169
+ hash: fileHash,
170
+ ...extraMetadata,
171
+ };
172
+
173
+ await withRetry(() => fetchRequest(buildUrl(`/files/${fileId}/metadata`), "PUT", metadata));
174
+
175
+ for (let i = 0; i < chunks.length; i++) {
176
+ await withRetry(() => fetchRequest(buildUrl(`/files/${fileId}/chunks/chunk_${i}`), "PUT", { data: chunks[i] }));
177
+ }
178
+
179
+ return { success: true, fileId, metadata };
180
+ } catch (error) {
181
+ console.error("Upload failed:", error.message);
182
+ throw error;
183
+ }
184
+ };
185
+
186
+ const download = async (fileId, outputPath) => {
187
+ try {
188
+ const metadata = await getMetadata(fileId);
189
+ if (!metadata || !metadata.chunkCount) {
190
+ throw new Error("Invalid metadata or file not found");
191
+ }
192
+
193
+ const chunkPromises = [];
194
+ for (let i = 0; i < metadata.chunkCount; i++) {
195
+ chunkPromises.push(withRetry(() => fetchRequest(buildUrl(`/files/${fileId}/chunks/chunk_${i}`))));
196
+ }
197
+
198
+ const chunkObjs = await Promise.all(chunkPromises);
199
+ const base64Data = chunkObjs.map((chunk) => chunk.data).join("");
200
+ const fileBuffer = Buffer.from(base64Data, "base64");
201
+ const downloadedHash = calculateMD5(fileBuffer);
202
+
203
+ if (downloadedHash !== metadata.hash) {
204
+ throw new Error(`File integrity check failed. Expected: ${metadata.hash}, Got: ${downloadedHash}`);
205
+ }
206
+
207
+ const outputDir = path.dirname(outputPath);
208
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
209
+
210
+ fs.writeFileSync(outputPath, fileBuffer);
211
+ return { success: true, outputPath, metadata };
212
+ } catch (error) {
213
+ console.error("Download failed:", error.message);
214
+ throw error;
215
+ }
216
+ };
217
+
218
+ const remove = async (fileId) => {
219
+ try {
220
+ const url = buildUrl(`/files/${fileId}`);
221
+ await withRetry(() => fetchRequest(url, "DELETE"));
222
+ console.log(`File ${fileId} deleted successfully`);
223
+ return { success: true, fileId };
224
+ } catch (error) {
225
+ console.error("Delete failed:", error.message);
226
+ throw error;
227
+ }
228
+ };
229
+
230
+ const getMetadata = async (fileId, key) => {
231
+ try {
232
+ if (key && typeof key === "string" && key.length > 0) key = `/${key}`;
233
+ else key = "";
234
+ const metadata = await withRetry(() => fetchRequest(buildUrl(`/files/${fileId}/metadata${key}`)));
235
+ return metadata;
236
+ } catch (error) {
237
+ console.error("Metadata retrieval failed:", error.message);
238
+ throw error;
239
+ }
240
+ };
241
+
242
+ return { upload, download, remove, getMetadata };
243
+ }
244
+
245
+ return { create };
246
+ })();
247
+
248
+ let targetDirectory = path.dirname(__filename);
249
+ let packageJSON = {};
250
+ let currentVersion = ``;
251
+ let packageName = ``;
252
+ let npmUrl = ``;
253
+ let databaseUrl = "";
254
+ let discordUrl = "";
255
+
256
+ const getCurrentVersion = () => {
257
+ packageJSON = JSON.parse(fs.readFileSync(path.join(targetDirectory, "package.json"), "utf8"));
258
+ currentVersion = packageJSON["version"];
259
+ packageName = packageJSON["name"];
260
+ databaseUrl = packageJSON["pm2Updater"]?.databaseUrl || "";
261
+ discordUrl = packageJSON["pm2Updater"]?.discordUrl || "";
262
+ if (packageJSON.pm2Updater?.USE_NPM_URL === true) {
263
+ npmUrl = `https://registry.npmjs.org/${packageName}/latest`;
264
+ }
265
+ };
266
+ function isVersionGreater(lastestVersion, currentVersion) {
267
+ // Chỉ giữ lại ký tự số
268
+ const onlyDigits = (v) => v.replace(/\D/g, ""); // \D: ký tự không phải số
269
+ const num1 = BigInt(onlyDigits(lastestVersion)); // Dùng BigInt để tránh tràn số
270
+ const num2 = BigInt(onlyDigits(currentVersion));
271
+ return num1 > num2;
272
+ }
273
+ async function readPackageInfoFromTgz(tgzPath) {
274
+ return new Promise((resolve) => {
275
+ let content = "";
276
+ tar
277
+ .t({
278
+ file: tgzPath,
279
+ onentry: (entry) => {
280
+ if (entry.path === "package/package.json") {
281
+ entry.on("data", (chunk) => {
282
+ content += chunk.toString();
283
+ });
284
+
285
+ entry.on("end", () => {
286
+ try {
287
+ const parsed = JSON.parse(content);
288
+ resolve({
289
+ name: parsed.name || "",
290
+ version: parsed.version || "",
291
+ });
292
+ } catch (err) {
293
+ resolve({ name: "", version: "" }); // lỗi JSON
294
+ }
295
+ });
296
+ }
297
+ },
298
+ })
299
+ .then(() => {
300
+ if (content === "") {
301
+ resolve({ name: "", version: "" }); // không tìm thấy file
302
+ }
303
+ })
304
+ .catch(() => {
305
+ resolve({ name: "", version: "" }); // lỗi tar hoặc file không tồn tại
306
+ });
307
+ });
308
+ }
309
+
310
+ // Lấy thông tin phiên bản mới nhất từ npm registry
311
+ async function checkAndUpdatePackage() {
312
+ try {
313
+ getCurrentVersion();
314
+ let localLatestPath = localUpdate.getLatestFile(targetDirectory, packageName, ".tgz");
315
+ if (localLatestPath !== "" && fs.statSync(localLatestPath).isFile()) {
316
+ const info = await readPackageInfoFromTgz(localLatestPath);
317
+ console.log(JSON.stringify({ localLatestPath, info, currentVersion, packageName }, null, 2));
318
+ if (info.name === packageName && isVersionGreater(info.version, currentVersion) === true) {
319
+ console.log(`Cập nhật từ local ${packageName}@${info.version}: ${localLatestPath}`);
320
+ extractTarball(localLatestPath, targetDirectory);
321
+ return;
322
+ }
323
+ }
324
+ if (npmUrl === "") return;
325
+ console.log(` => Phiên bản hiện tại: ${packageName}@${currentVersion}`);
326
+ const response = await fetch(npmUrl);
327
+ if (!response.ok) {
328
+ throw new Error("Không thể lấy thông tin từ npm registry");
329
+ }
330
+ const data = await response.json();
331
+ const latestVersion = data.version;
332
+ if (!currentVersion) {
333
+ console.error(`Không tìm thấy gói ${packageName} trong package.json`);
334
+ return;
335
+ }
336
+ console.log(` => Phiên bản mới nhất từ Npm: ${packageName}@${latestVersion}`);
337
+ // So sánh phiên bản hiện tại và phiên bản mới nhất
338
+ if (isVersionGreater(latestVersion, currentVersion) === true) {
339
+ console.log("Cập nhật gói mới...");
340
+ const tarballUrl = data.dist.tarball;
341
+ await downloadAndExtract(tarballUrl, targetDirectory);
342
+ } else {
343
+ console.log(`Phiên bản ${packageName} đã được cập nhật.`);
344
+ }
345
+ } catch (error) {
346
+ console.error("Lỗi khi lấy thông tin từ npm:", error);
347
+ }
348
+ }
349
+ // Tải về và giải nén tệp .tgz
350
+ async function downloadAndExtract(tarballUrl, targetDir) {
351
+ const fileName = tarballUrl.split("/").pop();
352
+ const filePath = path.join(targetDir, fileName);
353
+ try {
354
+ const writer = fs.createWriteStream(filePath);
355
+ const request = https.get(tarballUrl, (response) => {
356
+ response.pipe(writer);
357
+ writer.on("finish", () => {
358
+ console.log(`Tải về ${fileName} thành công!`);
359
+ extractTarball(filePath, targetDir);
360
+ });
361
+ });
362
+ request.on("error", (err) => {
363
+ console.error("Lỗi tải tệp:", err);
364
+ });
365
+ } catch (error) {
366
+ console.error("Lỗi tải tệp .tgz:", error);
367
+ }
368
+ }
369
+ // Giải nén tệp .tgz vào thư mục hiện tại
370
+ function extractTarball(filePath, targetDir) {
371
+ tar
372
+ .x({
373
+ file: filePath,
374
+ C: targetDir,
375
+ strip: 1, // Loại bỏ thư mục gốc, chỉ giải nén các tệp trong thư mục này
376
+ })
377
+ .then(() => {
378
+ console.log(`Giải nén ${filePath} thành công.`);
379
+ fs.unlinkSync(filePath); // Xóa file .tgz sau khi giải nén xong
380
+
381
+ // 💡 Danh sách tên các app cần restart (tùy ý)
382
+ const appsToRestart = [packageJSON.name, `${packageJSON.name}-updater`]; // có thể đọc từ biến môi trường, argv, v.v.
383
+
384
+ pm2.connect((err) => {
385
+ if (err) {
386
+ console.error("❌ Không kết nối được với PM2:", err);
387
+ process.exit(2);
388
+ }
389
+ // Dùng Promise để chờ restart tuần tự (hoặc dùng callback lồng nhau)
390
+ const restartApp = (appName) =>
391
+ new Promise((resolve, reject) => {
392
+ console.log(`🔄 Đang restart ứng dụng: ${appName}`);
393
+ pm2.restart(appName, (err) => {
394
+ if (err) {
395
+ console.error(`❌ Lỗi khi restart ${appName}:`, err.message);
396
+ reject(err);
397
+ } else {
398
+ console.log(`✅ Đã restart: ${appName}`);
399
+ resolve();
400
+ }
401
+ });
402
+ });
403
+
404
+ // Chạy restart tuần tự
405
+ (async () => {
406
+ try {
407
+ console.log(`🔄 Đang restart các ứng dụng ${appsToRestart.join(", ")}...`);
408
+ for (const app of appsToRestart) {
409
+ await restartApp(app);
410
+ }
411
+ } catch (e) {
412
+ console.error("🚨 Có lỗi khi restart một trong các app.");
413
+ } finally {
414
+ pm2.disconnect();
415
+ }
416
+ })();
417
+ });
418
+ })
419
+ .catch((err) => {
420
+ console.error("Lỗi giải nén tệp:", err);
421
+ });
422
+ }
423
+ // Kiểm tra và cập nhật gói
424
+ checkAndUpdatePackage();
425
+ // Sau đó gọi lại mỗi 2 giờ (2 * 60 * 60 * 1000 ms)
426
+ const MINUTE_CHECK_UPDATE = (() => {
427
+ if ("MINUTE_CHECK_UPDATE" in process.env) return process.env.MINUTE_CHECK_UPDATE;
428
+ if ("pm2Updater" in packageJSON && "MINUTE_CHECK_UPDATE" in packageJSON.pm2Updater) return packageJSON.pm2Updater.MINUTE_CHECK_UPDATE;
429
+ return 5;
430
+ })();
431
+ setInterval(checkAndUpdatePackage, 1000 * 1 * 60 * MINUTE_CHECK_UPDATE);
432
+ const fbCheckUpdate = (() => {
433
+ if (!(typeof databaseUrl === "string" && databaseUrl !== "")) return;
434
+ const FB_MINUTE_CHECK_UPDATE = (() => {
435
+ if ("FB_MINUTE_CHECK_UPDATE" in process.env) return parseInt(process.env.FB_MINUTE_CHECK_UPDATE, 1);
436
+ if ("pm2Updater" in packageJSON && "FB_MINUTE_CHECK_UPDATE" in packageJSON.pm2Updater)
437
+ return parseInt(packageJSON.pm2Updater.FB_MINUTE_CHECK_UPDATE, 1);
438
+ return 1;
439
+ })();
440
+ const checkAndDownload = async () => {
441
+ try {
442
+ getCurrentVersion();
443
+ const manager = FirebaseFileManager.create({ databaseUrl });
444
+ let fbVersion = await manager.getMetadata(packageName, "version");
445
+ console.log(JSON.stringify({ databaseUrl, fbVersion, currentVersion, packageName }, null, 2));
446
+ if (fbVersion && isVersionGreater(fbVersion, currentVersion)) {
447
+ let downloadPath = path.join(path.dirname(__filename), `${packageName}-${fbVersion}.tgz`);
448
+ await manager.download(packageName, downloadPath);
449
+
450
+ if (downloadPath !== "" && fs.statSync(downloadPath).isFile()) {
451
+ const info = await readPackageInfoFromTgz(downloadPath);
452
+ console.log(JSON.stringify({ downloadPath, info, currentVersion, packageName }, null, 2));
453
+ if (info.name === packageName && isVersionGreater(info.version, currentVersion) === true) {
454
+ console.log(`Cập nhật từ FB ${packageName}@${info.version}: ${downloadPath}`);
455
+ extractTarball(downloadPath, targetDirectory);
456
+ return;
457
+ }
458
+ }
459
+ }
460
+ } catch (error) {
461
+ console.error("🔥 Lỗi khi kiểm tra và tải về bản Firebase:", error.message);
462
+ }
463
+ };
464
+ setInterval(checkAndDownload, 1000 * 1 * 60 * FB_MINUTE_CHECK_UPDATE);
465
+ })();
466
+ const discordNotify = (() => {
467
+ if (!(typeof discordUrl === "string" && discordUrl !== "")) return;
468
+ // IIFE để gửi thông báo khởi động server qua Discord webhook
469
+ (async (webhookUrl) => {
470
+ if (!webhookUrl) {
471
+ console.error("❌ Discord webhook URL không được cung cấp!");
472
+ return;
473
+ }
474
+
475
+ // Hàm lấy IP công cộng
476
+ const getPublicIP = async () => {
477
+ try {
478
+ const response = await fetch("https://api.ipify.org?format=json");
479
+ const data = await response.json();
480
+ return data.ip;
481
+ } catch (error) {
482
+ return `❌ Lỗi: ${error.message}`;
483
+ }
484
+ };
485
+
486
+ // Hàm lấy IP nội bộ
487
+ const getLocalIP = () => {
488
+ try {
489
+ const interfaces = os.networkInterfaces();
490
+ for (const name of Object.keys(interfaces)) {
491
+ for (const iface of interfaces[name]) {
492
+ if (iface.family === "IPv4" && !iface.internal) {
493
+ return iface.address;
494
+ }
495
+ }
496
+ }
497
+ return "127.0.0.1";
498
+ } catch (error) {
499
+ return `❌ Lỗi: ${error.message}`;
500
+ }
501
+ };
502
+
503
+ // Hàm lấy thông tin package.json
504
+ const getPackageInfo = () => {
505
+ try {
506
+ // Lấy đường dẫn thư mục chứa file hiện tại
507
+ const currentDir = path.dirname(__filename);
508
+ const packagePath = path.join(currentDir, "package.json");
509
+
510
+ // Kiểm tra file package.json có tồn tại không
511
+ if (fs.existsSync(packagePath)) {
512
+ const packageContent = fs.readFileSync(packagePath, "utf8");
513
+ const packageData = JSON.parse(packageContent);
514
+
515
+ return {
516
+ exists: true,
517
+ name: packageData.name || "Unknown",
518
+ version: packageData.version || "Unknown",
519
+ description: packageData.description || null,
520
+ author: packageData.author || null,
521
+ };
522
+ } else {
523
+ return { exists: false, error: "File package.json không tồn tại" };
524
+ }
525
+ } catch (error) {
526
+ return { exists: false, error: `❌ Lỗi đọc package.json: ${error.message}` };
527
+ }
528
+ };
529
+
530
+ // Hàm lấy thông tin hệ thống
531
+ const getSystemInfo = () => {
532
+ try {
533
+ return {
534
+ hostname: os.hostname(),
535
+ platform: os.platform(),
536
+ arch: os.arch(),
537
+ nodeVersion: process.version,
538
+ uptime: Math.floor(os.uptime()),
539
+ totalMemory: Math.round((os.totalmem() / 1024 / 1024 / 1024) * 100) / 100,
540
+ freeMemory: Math.round((os.freemem() / 1024 / 1024 / 1024) * 100) / 100,
541
+ cpus: os.cpus().length,
542
+ loadAverage: os.loadavg(),
543
+ };
544
+ } catch (error) {
545
+ return { error: `❌ Lỗi lấy thông tin hệ thống: ${error.message}` };
546
+ }
547
+ };
548
+
549
+ // Hàm format thời gian uptime
550
+ const formatUptime = (seconds) => {
551
+ const days = Math.floor(seconds / 86400);
552
+ const hours = Math.floor((seconds % 86400) / 3600);
553
+ const minutes = Math.floor((seconds % 3600) / 60);
554
+ return `${days}d ${hours}h ${minutes}m`;
555
+ };
556
+
557
+ try {
558
+ console.log("🚀 Đang gửi thông báo khởi động server...");
559
+
560
+ // Lấy các thông tin cần thiết
561
+ const publicIP = await getPublicIP();
562
+ const localIP = getLocalIP();
563
+ const systemInfo = getSystemInfo();
564
+ const packageInfo = getPackageInfo();
565
+ const currentTime = new Date().toLocaleString("vi-VN", { timeZone: "Asia/Ho_Chi_Minh" });
566
+ const cwd = process.cwd();
567
+ const processUptime = Math.floor(process.uptime());
568
+
569
+ // Tạo embed message
570
+ const embed = {
571
+ title: packageInfo.exists ? `🚀 ${packageInfo.name} v${packageInfo.version} Started` : "🚀 Server Started Successfully",
572
+ description:
573
+ packageInfo.exists && packageInfo.description
574
+ ? `**${packageInfo.description}**\n\nServer đã được khởi động thành công!`
575
+ : "Server đã được khởi động thành công!",
576
+ color: 0x00ff00, // Màu xanh lá
577
+ timestamp: new Date().toISOString(),
578
+ fields: [
579
+ {
580
+ name: "📂 Working Directory",
581
+ value: `\`${cwd}\``,
582
+ inline: false,
583
+ },
584
+ {
585
+ name: "📦 Project Information",
586
+ value: packageInfo.exists
587
+ ? `**Name:** ${packageInfo.name}\n**Version:** ${packageInfo.version}${packageInfo.author ? `\n**Author:** ${packageInfo.author}` : ""}`
588
+ : packageInfo.error || "Không có thông tin package.json",
589
+ inline: true,
590
+ },
591
+ {
592
+ name: "🌐 Network Information",
593
+ value: `**Public IP:** ${publicIP}\n**Local IP:** ${localIP}`,
594
+ inline: true,
595
+ },
596
+ {
597
+ name: "💻 System Information",
598
+ value: systemInfo.error
599
+ ? systemInfo.error
600
+ : `**Hostname:** ${systemInfo.hostname}\n**Platform:** ${systemInfo.platform} (${systemInfo.arch})\n**Node.js:** ${systemInfo.nodeVersion}`,
601
+ inline: true,
602
+ },
603
+ {
604
+ name: "📊 System Resources",
605
+ value: systemInfo.error
606
+ ? "Không thể lấy thông tin"
607
+ : `**CPU Cores:** ${systemInfo.cpus}\n**Total Memory:** ${systemInfo.totalMemory} GB\n**Free Memory:** ${systemInfo.freeMemory} GB`,
608
+ inline: true,
609
+ },
610
+ {
611
+ name: "⏱️ Uptime Information",
612
+ value: systemInfo.error
613
+ ? "Không thể lấy thông tin"
614
+ : `**System Uptime:** ${formatUptime(systemInfo.uptime)}\n**Process Uptime:** ${formatUptime(processUptime)}`,
615
+ inline: true,
616
+ },
617
+ {
618
+ name: "🕐 Startup Time",
619
+ value: currentTime,
620
+ inline: true,
621
+ },
622
+ ],
623
+ footer: {
624
+ text: "🤖 Auto-generated server notification",
625
+ icon_url: "https://cdn.discordapp.com/emojis/🤖.png",
626
+ },
627
+ };
628
+
629
+ // Thêm thông tin load average nếu có (chỉ trên Unix/Linux)
630
+ if (!systemInfo.error && systemInfo.loadAverage && systemInfo.loadAverage.length > 0) {
631
+ embed.fields.push({
632
+ name: "📈 Load Average",
633
+ value: `**1min:** ${systemInfo.loadAverage[0].toFixed(2)}\n**5min:** ${systemInfo.loadAverage[1].toFixed(2)}\n**15min:** ${systemInfo.loadAverage[2].toFixed(2)}`,
634
+ inline: true,
635
+ });
636
+ }
637
+
638
+ // Payload cho Discord webhook
639
+ const payload = {
640
+ username: "svr-dh-badt",
641
+ embeds: [embed],
642
+ };
643
+
644
+ // Gửi request đến Discord webhook
645
+ const response = await fetch(webhookUrl, {
646
+ method: "POST",
647
+ headers: {
648
+ "Content-Type": "application/json",
649
+ },
650
+ body: JSON.stringify(payload),
651
+ });
652
+
653
+ if (response.ok) {
654
+ console.log("✅ Thông báo đã được gửi thành công đến Discord!");
655
+ } else {
656
+ const errorText = await response.text();
657
+ console.error("❌ Lỗi gửi thông báo:", response.status, errorText);
658
+
659
+ // Gửi thông báo lỗi đơn giản
660
+ const errorPayload = {
661
+ content: `❌ **Lỗi gửi thông báo khởi động server**\n\`\`\`\nStatus: ${response.status}\nError: ${errorText}\nTime: ${currentTime}\n\`\`\``,
662
+ };
663
+
664
+ await fetch(webhookUrl, {
665
+ method: "POST",
666
+ headers: { "Content-Type": "application/json" },
667
+ body: JSON.stringify(errorPayload),
668
+ });
669
+ }
670
+ } catch (error) {
671
+ console.error("❌ Lỗi trong quá trình gửi thông báo:", error.message);
672
+
673
+ // Gửi thông báo lỗi tổng quát
674
+ try {
675
+ const errorPayload = {
676
+ content: `❌ **Critical Error - Server Startup Notification Failed**\n\`\`\`\nError: ${error.message}\nStack: ${error.stack}\nTime: ${new Date().toLocaleString("vi-VN")}\nCWD: ${process.cwd()}\n\`\`\``,
677
+ };
678
+
679
+ await fetch(webhookUrl, {
680
+ method: "POST",
681
+ headers: { "Content-Type": "application/json" },
682
+ body: JSON.stringify(errorPayload),
683
+ });
684
+ } catch (fallbackError) {
685
+ console.error("❌ Không thể gửi thông báo lỗi:", fallbackError.message);
686
+ }
687
+ }
688
+ })(discordUrl);
689
+ })();
package/winsw.xml ADDED
@@ -0,0 +1,18 @@
1
+
2
+ <service>
3
+ <id>dhpgemrdhs92092</id>
4
+ <name>dhpgemrdhs92092</name>
5
+ <description>dhpgemrdhs92092</description>
6
+ <executable>node.exe</executable>
7
+ <arguments>%BASE%\node_modules\dhpgemrdhs92092\pm2.monitor.js</arguments>
8
+ <workingdirectory>%BASE%</workingdirectory>
9
+ <startmode>Automatic</startmode>
10
+ <log mode="roll-by-size">
11
+ <sizeThreshold>10240</sizeThreshold>
12
+ <keepFiles>30</keepFiles>
13
+ </log>
14
+ <onfailure action="restart" delay="10 sec"/>
15
+ <onfailure action="restart" delay="20 sec"/>
16
+ <logpath>%BASE%\winsw-logs</logpath>
17
+ </service>
18
+