Compare commits
12 commits
b33f04886d
...
98f4afda2e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
98f4afda2e | ||
![]() |
b9ed6cd6ec | ||
![]() |
2e613573a2 | ||
![]() |
4658be548a | ||
![]() |
395975a111 | ||
![]() |
47e43be184 | ||
![]() |
3d4552c183 | ||
![]() |
3947dec67c | ||
![]() |
e05db3ba4d | ||
![]() |
81a24e3636 | ||
![]() |
917b12bb27 | ||
![]() |
22bebeaa06 |
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -137,3 +137,6 @@ dist
|
||||||
|
|
||||||
# pm2 ecosystem file
|
# pm2 ecosystem file
|
||||||
ecosystem.config.js
|
ecosystem.config.js
|
||||||
|
|
||||||
|
# firebase service file
|
||||||
|
firebase-service-account.json
|
||||||
|
|
|
@ -1,171 +1,171 @@
|
||||||
{
|
{
|
||||||
"USD": {
|
"USD": {
|
||||||
"lastUpdated": "2025-04-07T19:30:40.700Z",
|
"lastUpdated": "2025-04-30T14:50:02.050Z",
|
||||||
"rates": {
|
"rates": {
|
||||||
"USD": 1,
|
"USD": 1,
|
||||||
"AED": 3.6725,
|
"AED": 3.6725,
|
||||||
"AFN": 71.3562,
|
"AFN": 70.9584,
|
||||||
"ALL": 90.1377,
|
"ALL": 86.782,
|
||||||
"AMD": 391.1786,
|
"AMD": 389.9021,
|
||||||
"ANG": 1.79,
|
"ANG": 1.79,
|
||||||
"AOA": 918.5748,
|
"AOA": 918.243,
|
||||||
"ARS": 1075.88,
|
"ARS": 1170.67,
|
||||||
"AUD": 1.662,
|
"AUD": 1.5645,
|
||||||
"AWG": 1.79,
|
"AWG": 1.79,
|
||||||
"AZN": 1.6999,
|
"AZN": 1.7005,
|
||||||
"BAM": 1.7805,
|
"BAM": 1.7173,
|
||||||
"BBD": 2,
|
"BBD": 2,
|
||||||
"BDT": 121.3674,
|
"BDT": 121.5,
|
||||||
"BGN": 1.7804,
|
"BGN": 1.7175,
|
||||||
"BHD": 0.376,
|
"BHD": 0.376,
|
||||||
"BIF": 2960.3407,
|
"BIF": 2954.2386,
|
||||||
"BMD": 1,
|
"BMD": 1,
|
||||||
"BND": 1.3404,
|
"BND": 1.3085,
|
||||||
"BOB": 6.8885,
|
"BOB": 6.9176,
|
||||||
"BRL": 5.7194,
|
"BRL": 5.655,
|
||||||
"BSD": 1,
|
"BSD": 1,
|
||||||
"BTN": 85.5708,
|
"BTN": 85.2183,
|
||||||
"BWP": 13.8733,
|
"BWP": 13.7041,
|
||||||
"BYN": 3.1354,
|
"BYN": 3.0524,
|
||||||
"BZD": 2,
|
"BZD": 2,
|
||||||
"CAD": 1.423,
|
"CAD": 1.3839,
|
||||||
"CDF": 2899.0345,
|
"CDF": 2882.8224,
|
||||||
"CHF": 0.8524,
|
"CHF": 0.8241,
|
||||||
"CLP": 960.6602,
|
"CLP": 943.6795,
|
||||||
"CNY": 7.2857,
|
"CNY": 7.2743,
|
||||||
"COP": 4203.747,
|
"COP": 4210.8649,
|
||||||
"CRC": 502.4294,
|
"CRC": 504.9542,
|
||||||
"CUP": 24,
|
"CUP": 24,
|
||||||
"CVE": 100.3829,
|
"CVE": 96.8156,
|
||||||
"CZK": 22.9524,
|
"CZK": 21.9027,
|
||||||
"DJF": 177.721,
|
"DJF": 177.721,
|
||||||
"DKK": 6.7905,
|
"DKK": 6.5522,
|
||||||
"DOP": 62.8304,
|
"DOP": 58.8495,
|
||||||
"DZD": 132.995,
|
"DZD": 132.3973,
|
||||||
"EGP": 50.8068,
|
"EGP": 50.8016,
|
||||||
"ERN": 15,
|
"ERN": 15,
|
||||||
"ETB": 131.8684,
|
"ETB": 132.3384,
|
||||||
"EUR": 0.9102,
|
"EUR": 0.8781,
|
||||||
"FJD": 2.3154,
|
"FJD": 2.2528,
|
||||||
"FKP": 0.7757,
|
"FKP": 0.746,
|
||||||
"FOK": 6.7935,
|
"FOK": 6.5526,
|
||||||
"GBP": 0.7751,
|
"GBP": 0.7461,
|
||||||
"GEL": 2.7559,
|
"GEL": 2.7497,
|
||||||
"GGP": 0.7757,
|
"GGP": 0.746,
|
||||||
"GHS": 15.5067,
|
"GHS": 14.3404,
|
||||||
"GIP": 0.7757,
|
"GIP": 0.746,
|
||||||
"GMD": 72.6441,
|
"GMD": 72.7,
|
||||||
"GNF": 8570.968,
|
"GNF": 8698.1694,
|
||||||
"GTQ": 7.6902,
|
"GTQ": 7.7017,
|
||||||
"GYD": 209.8017,
|
"GYD": 209.6895,
|
||||||
"HKD": 7.7734,
|
"HKD": 7.7583,
|
||||||
"HNL": 25.5147,
|
"HNL": 25.9262,
|
||||||
"HRK": 6.8592,
|
"HRK": 6.6155,
|
||||||
"HTG": 130.7844,
|
"HTG": 130.6641,
|
||||||
"HUF": 369.6088,
|
"HUF": 355.0713,
|
||||||
"IDR": 16757.5574,
|
"IDR": 16731.4579,
|
||||||
"ILS": 3.7448,
|
"ILS": 3.624,
|
||||||
"IMP": 0.7757,
|
"IMP": 0.746,
|
||||||
"INR": 85.5703,
|
"INR": 85.2184,
|
||||||
"IQD": 1309.7144,
|
"IQD": 1308.7194,
|
||||||
"IRR": 42008.9149,
|
"IRR": 41991.8107,
|
||||||
"ISK": 131.125,
|
"ISK": 128.3463,
|
||||||
"JEP": 0.7757,
|
"JEP": 0.746,
|
||||||
"JMD": 157.719,
|
"JMD": 158.4119,
|
||||||
"JOD": 0.709,
|
"JOD": 0.709,
|
||||||
"JPY": 145.2473,
|
"JPY": 142.3246,
|
||||||
"KES": 129.2338,
|
"KES": 129.2923,
|
||||||
"KGS": 86.8492,
|
"KGS": 87.0935,
|
||||||
"KHR": 3997.3556,
|
"KHR": 4026.7938,
|
||||||
"KID": 1.6621,
|
"KID": 1.565,
|
||||||
"KMF": 447.8769,
|
"KMF": 431.9608,
|
||||||
"KRW": 1457.9608,
|
"KRW": 1433.504,
|
||||||
"KWD": 0.307,
|
"KWD": 0.3064,
|
||||||
"KYD": 0.8333,
|
"KYD": 0.8333,
|
||||||
"KZT": 510.3326,
|
"KZT": 512.1031,
|
||||||
"LAK": 21751.6772,
|
"LAK": 21704.1039,
|
||||||
"LBP": 89500,
|
"LBP": 89500,
|
||||||
"LKR": 295.3817,
|
"LKR": 299.3413,
|
||||||
"LRD": 199.3433,
|
"LRD": 199.8861,
|
||||||
"LSL": 19.1915,
|
"LSL": 18.5447,
|
||||||
"LYD": 4.8358,
|
"LYD": 5.4665,
|
||||||
"MAD": 9.5166,
|
"MAD": 9.2601,
|
||||||
"MDL": 17.6443,
|
"MDL": 17.1502,
|
||||||
"MGA": 4654.3433,
|
"MGA": 4505.4667,
|
||||||
"MKD": 55.9208,
|
"MKD": 54.2379,
|
||||||
"MMK": 2090.2388,
|
"MMK": 2098.8531,
|
||||||
"MNT": 3479.6583,
|
"MNT": 3581.085,
|
||||||
"MOP": 8.0068,
|
"MOP": 7.991,
|
||||||
"MRU": 39.8843,
|
"MRU": 39.7408,
|
||||||
"MUR": 44.5309,
|
"MUR": 45.0517,
|
||||||
"MVR": 15.4592,
|
"MVR": 15.4444,
|
||||||
"MWK": 1740.2553,
|
"MWK": 1741.6056,
|
||||||
"MXN": 20.5515,
|
"MXN": 19.5726,
|
||||||
"MYR": 4.437,
|
"MYR": 4.3277,
|
||||||
"MZN": 63.6781,
|
"MZN": 63.782,
|
||||||
"NAD": 19.1915,
|
"NAD": 18.5447,
|
||||||
"NGN": 1529.0612,
|
"NGN": 1601.1782,
|
||||||
"NIO": 36.6793,
|
"NIO": 36.7914,
|
||||||
"NOK": 10.7699,
|
"NOK": 10.3694,
|
||||||
"NPR": 136.9132,
|
"NPR": 136.3493,
|
||||||
"NZD": 1.7956,
|
"NZD": 1.6836,
|
||||||
"OMR": 0.3845,
|
"OMR": 0.3845,
|
||||||
"PAB": 1,
|
"PAB": 1,
|
||||||
"PEN": 3.6809,
|
"PEN": 3.6672,
|
||||||
"PGK": 4.0959,
|
"PGK": 4.1028,
|
||||||
"PHP": 57.3644,
|
"PHP": 56.0867,
|
||||||
"PKR": 280.7358,
|
"PKR": 281.1451,
|
||||||
"PLN": 3.8787,
|
"PLN": 3.749,
|
||||||
"PYG": 8001.2022,
|
"PYG": 8026.0982,
|
||||||
"QAR": 3.64,
|
"QAR": 3.64,
|
||||||
"RON": 4.5185,
|
"RON": 4.3713,
|
||||||
"RSD": 106.3911,
|
"RSD": 102.9144,
|
||||||
"RUB": 84.4536,
|
"RUB": 81.9349,
|
||||||
"RWF": 1422.8596,
|
"RWF": 1451.6279,
|
||||||
"SAR": 3.75,
|
"SAR": 3.75,
|
||||||
"SBD": 8.3385,
|
"SBD": 8.5503,
|
||||||
"SCR": 14.8196,
|
"SCR": 14.6335,
|
||||||
"SDG": 458.3047,
|
"SDG": 460.9319,
|
||||||
"SEK": 10.0072,
|
"SEK": 9.6342,
|
||||||
"SGD": 1.3405,
|
"SGD": 1.3085,
|
||||||
"SHP": 0.7757,
|
"SHP": 0.746,
|
||||||
"SLE": 22.7181,
|
"SLE": 22.6844,
|
||||||
"SLL": 22718.051,
|
"SLL": 22684.4001,
|
||||||
"SOS": 571.0444,
|
"SOS": 571.3184,
|
||||||
"SRD": 36.8241,
|
"SRD": 36.6923,
|
||||||
"SSP": 4519.748,
|
"SSP": 4594.8425,
|
||||||
"STN": 22.3043,
|
"STN": 21.5117,
|
||||||
"SYP": 12873.9497,
|
"SYP": 12884.7736,
|
||||||
"SZL": 19.1915,
|
"SZL": 18.5447,
|
||||||
"THB": 34.3823,
|
"THB": 33.4103,
|
||||||
"TJS": 10.9221,
|
"TJS": 10.7469,
|
||||||
"TMT": 3.4983,
|
"TMT": 3.4996,
|
||||||
"TND": 3.0571,
|
"TND": 2.9792,
|
||||||
"TOP": 2.3833,
|
"TOP": 2.3554,
|
||||||
"TRY": 38.0295,
|
"TRY": 38.5176,
|
||||||
"TTD": 6.7342,
|
"TTD": 6.7425,
|
||||||
"TVD": 1.6621,
|
"TVD": 1.565,
|
||||||
"TWD": 33.1309,
|
"TWD": 32.2124,
|
||||||
"TZS": 2647.2453,
|
"TZS": 2687.3856,
|
||||||
"UAH": 41.1747,
|
"UAH": 41.5604,
|
||||||
"UGX": 3662.2001,
|
"UGX": 3661.3257,
|
||||||
"UYU": 42.042,
|
"UYU": 42.0282,
|
||||||
"UZS": 12937.493,
|
"UZS": 12935.0729,
|
||||||
"VES": 72.1856,
|
"VES": 86.8465,
|
||||||
"VND": 25642.7185,
|
"VND": 26020.9877,
|
||||||
"VUV": 121.865,
|
"VUV": 120.688,
|
||||||
"WST": 2.8015,
|
"WST": 2.7584,
|
||||||
"XAF": 597.1692,
|
"XAF": 575.9477,
|
||||||
"XCD": 2.7,
|
"XCD": 2.7,
|
||||||
"XCG": 1.79,
|
"XCG": 1.79,
|
||||||
"XDR": 0.751,
|
"XDR": 0.7372,
|
||||||
"XOF": 597.1692,
|
"XOF": 575.9477,
|
||||||
"XPF": 108.6373,
|
"XPF": 104.7767,
|
||||||
"YER": 244.8828,
|
"YER": 245.1184,
|
||||||
"ZAR": 19.2142,
|
"ZAR": 18.5441,
|
||||||
"ZMW": 27.9801,
|
"ZMW": 28.1983,
|
||||||
"ZWL": 6.7864
|
"ZWL": 26.8115
|
||||||
},
|
},
|
||||||
"nextUpdateTime": "2025-04-08T19:30:40.700Z"
|
"nextUpdateTime": "2025-05-01T14:50:02.050Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
192
api/message-board/message-board.js
Normal file
192
api/message-board/message-board.js
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
const express = require("express");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Path for persisting messages
|
||||||
|
const MESSAGES_FILE_PATH = path.join(__dirname, 'messages-cache.json');
|
||||||
|
|
||||||
|
// In-memory storage with persistence
|
||||||
|
let messages = [];
|
||||||
|
// Store active polling connections
|
||||||
|
let pendingConnections = [];
|
||||||
|
// Track the last message ID to help clients know what's new
|
||||||
|
let lastMessageId = 0;
|
||||||
|
|
||||||
|
// Load existing messages from file if it exists
|
||||||
|
function loadMessages() {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(MESSAGES_FILE_PATH)) {
|
||||||
|
const data = fs.readFileSync(MESSAGES_FILE_PATH, 'utf8');
|
||||||
|
messages = JSON.parse(data);
|
||||||
|
console.log(`Loaded ${messages.length} messages from cache`);
|
||||||
|
|
||||||
|
// Set the initial lastMessageId based on loaded messages
|
||||||
|
if (messages.length > 0) {
|
||||||
|
lastMessageId = messages.length;
|
||||||
|
// Add ID to each message if not present
|
||||||
|
messages.forEach((msg, index) => {
|
||||||
|
if (!msg.id) msg.id = index + 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading messages from cache:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save messages to file
|
||||||
|
function saveMessages() {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(MESSAGES_FILE_PATH, JSON.stringify(messages), 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error saving messages to cache:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all pending connections about new messages
|
||||||
|
function notifyNewMessages() {
|
||||||
|
const connectionsToNotify = [...pendingConnections];
|
||||||
|
pendingConnections = [];
|
||||||
|
|
||||||
|
connectionsToNotify.forEach(connection => {
|
||||||
|
const { res, lastId } = connection;
|
||||||
|
sendNewMessages(res, lastId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send new messages to a client
|
||||||
|
function sendNewMessages(res, lastKnownId) {
|
||||||
|
const newMessages = messages.filter(msg => msg.id > lastKnownId);
|
||||||
|
const currentLastId = messages.length > 0 ? messages[messages.length - 1].id : lastKnownId;
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
messages: newMessages,
|
||||||
|
lastId: currentLastId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load messages at startup
|
||||||
|
loadMessages();
|
||||||
|
|
||||||
|
// Submit a new message
|
||||||
|
router.post("/submit", express.json(), (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name, message } = req.body;
|
||||||
|
|
||||||
|
if (!name || !message) {
|
||||||
|
return res.status(400).json({ error: "Missing name or message fields" });
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMessageId++;
|
||||||
|
|
||||||
|
const newMessage = {
|
||||||
|
id: lastMessageId,
|
||||||
|
name,
|
||||||
|
message,
|
||||||
|
time: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.push(newMessage);
|
||||||
|
|
||||||
|
// Keep only the latest 100 messages
|
||||||
|
if (messages.length > 100) {
|
||||||
|
messages = messages.slice(-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to file after update
|
||||||
|
saveMessages();
|
||||||
|
|
||||||
|
// Notify all waiting clients
|
||||||
|
notifyNewMessages();
|
||||||
|
|
||||||
|
res.json({ success: true, message: "Message received" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error handling message submission:", error);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all messages as JSON (keep for backward compatibility)
|
||||||
|
router.get("/", (req, res) => {
|
||||||
|
try {
|
||||||
|
res.json({ messages });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending messages:", error);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Long polling endpoint for message updates
|
||||||
|
router.get("/poll", (req, res) => {
|
||||||
|
try {
|
||||||
|
const lastId = parseInt(req.query.lastId || "0", 10);
|
||||||
|
const newMessages = messages.filter(msg => msg.id > lastId);
|
||||||
|
|
||||||
|
// If there are new messages, send them immediately
|
||||||
|
if (newMessages.length > 0) {
|
||||||
|
sendNewMessages(res, lastId);
|
||||||
|
} else {
|
||||||
|
// No new messages, set timeout to avoid hanging forever
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
// Remove this connection from pending list
|
||||||
|
pendingConnections = pendingConnections.filter(conn => conn.res !== res);
|
||||||
|
sendNewMessages(res, lastId);
|
||||||
|
}, 30000); // 30 second timeout
|
||||||
|
|
||||||
|
// Store the connection for later notification
|
||||||
|
pendingConnections.push({
|
||||||
|
res,
|
||||||
|
lastId,
|
||||||
|
timeoutId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle client disconnect
|
||||||
|
req.on('close', () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
pendingConnections = pendingConnections.filter(conn => conn.res !== res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in long polling:", error);
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get messages as HTML
|
||||||
|
router.get("/html", (req, res) => {
|
||||||
|
try {
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Public Message Pool</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
||||||
|
.message { border-bottom: 1px solid #eee; padding: 10px 0; }
|
||||||
|
.name { font-weight: bold; }
|
||||||
|
.time { color: #666; font-size: 0.8em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Public Message Pool</h1>
|
||||||
|
${messages.map(msg => `
|
||||||
|
<div class="message">
|
||||||
|
<span class="name">${msg.name}</span>
|
||||||
|
<span class="time">[${msg.time}]</span>:
|
||||||
|
<div class="content">${msg.message}</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
res.send(html);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending HTML messages:", error);
|
||||||
|
res.status(500).send("Internal server error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
|
@ -10,39 +10,66 @@ require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
||||||
const status = require("./status/status");
|
const status = require("./status/status");
|
||||||
const exchangeRate = require("./exchange-rate/exchange-rate");
|
const exchangeRate = require("./exchange-rate/exchange-rate");
|
||||||
const whois = require("./whois/whois");
|
const whois = require("./whois/whois");
|
||||||
|
const messageBoard = require("./message-board/message-board");
|
||||||
|
|
||||||
|
// Main API app
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 2589;
|
const PORT = process.env.PORT || 2589;
|
||||||
|
|
||||||
|
// Message board app (separate instance)
|
||||||
|
const messageBoardApp = express();
|
||||||
|
const MESSAGE_BOARD_PORT = process.env.MESSAGE_BOARD_PORT || 2845;
|
||||||
|
|
||||||
|
// SSL certificate paths
|
||||||
const key = process.env.SSL_KEY_PATH || "/etc/letsencrypt/live/xargana.tr/privkey.pem";
|
const key = process.env.SSL_KEY_PATH || "/etc/letsencrypt/live/xargana.tr/privkey.pem";
|
||||||
const cert = process.env.SSL_CERT_PATH || "/etc/letsencrypt/live/xargana.tr/fullchain.pem";
|
const cert = process.env.SSL_CERT_PATH || "/etc/letsencrypt/live/xargana.tr/fullchain.pem";
|
||||||
|
|
||||||
|
// Configure main API app
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use("/status", status);
|
app.use("/status", status);
|
||||||
app.use("/exchange-rate", exchangeRate);
|
app.use("/exchange-rate", exchangeRate);
|
||||||
app.use("/whois", whois);
|
app.use("/whois", whois);
|
||||||
|
|
||||||
// try to load certificates
|
// Configure message board app
|
||||||
|
messageBoardApp.use(cors());
|
||||||
|
messageBoardApp.use("/", messageBoard);
|
||||||
|
|
||||||
|
// Try to load SSL certificates
|
||||||
|
let sslOptions;
|
||||||
try {
|
try {
|
||||||
const sslOptions = {
|
sslOptions = {
|
||||||
key: fs.readFileSync(key),
|
key: fs.readFileSync(key),
|
||||||
cert: fs.readFileSync(cert),
|
cert: fs.readFileSync(cert),
|
||||||
};
|
};
|
||||||
|
|
||||||
https.createServer(sslOptions, app).listen(PORT, () => {
|
|
||||||
console.log(`API running at https://localhost:${PORT}`);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code === 'ENOENT') {
|
if (e.code === 'ENOENT') {
|
||||||
console.warn(`SSL certificate file(s) not found: ${e.path}`);
|
console.warn(`SSL certificate file(s) not found: ${e.path}`);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Error loading SSL certificates: ${e.message}`);
|
console.warn(`Error loading SSL certificates: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
sslOptions = null;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Starting server without SSL...");
|
// Start main API server
|
||||||
|
if (sslOptions) {
|
||||||
// start http server as fallback
|
https.createServer(sslOptions, app).listen(PORT, () => {
|
||||||
|
console.log(`Main API running at https://localhost:${PORT}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Starting main API server without SSL...");
|
||||||
http.createServer(app).listen(PORT, () => {
|
http.createServer(app).listen(PORT, () => {
|
||||||
console.log(`API running at http://localhost:${PORT}`);
|
console.log(`Main API running at http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start message board server
|
||||||
|
if (sslOptions) {
|
||||||
|
https.createServer(sslOptions, messageBoardApp).listen(MESSAGE_BOARD_PORT, () => {
|
||||||
|
console.log(`Message Board running at https://localhost:${MESSAGE_BOARD_PORT}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Starting Message Board server without SSL...");
|
||||||
|
http.createServer(messageBoardApp).listen(MESSAGE_BOARD_PORT, () => {
|
||||||
|
console.log(`Message Board running at http://localhost:${MESSAGE_BOARD_PORT}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,26 @@ const ping = require("ping");
|
||||||
const pm2 = require("pm2");
|
const pm2 = require("pm2");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const axios = require("axios");
|
||||||
|
const admin = require("firebase-admin");
|
||||||
|
|
||||||
|
// Initialize Firebase Admin SDK
|
||||||
|
try {
|
||||||
|
const serviceAccount = require("../../firebase-service-account.json");
|
||||||
|
admin.initializeApp({
|
||||||
|
credential: admin.credential.cert(serviceAccount)
|
||||||
|
});
|
||||||
|
console.log("Firebase Admin SDK initialized successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing Firebase Admin SDK:", error);
|
||||||
|
}
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
const REMOTE_SERVERS = [
|
const REMOTE_SERVERS = [
|
||||||
{ name: "xargana.tr", host: "xargana.tr" },
|
{ name : "google.com", host: "google.com" },
|
||||||
{ name: "xargana.com", host: "xargana.com" },
|
{ name: "xargana.tr", host: "xargana.tr"}
|
||||||
{ name: "home server", host: "31.223.36.208" }
|
// { name: "home server", host: "31.223.36.208" } removed cause router not behaving, dropped all pings today.
|
||||||
];
|
];
|
||||||
|
|
||||||
const CHECK_INTERVAL = 5 * 1000;
|
const CHECK_INTERVAL = 5 * 1000;
|
||||||
|
@ -17,6 +30,9 @@ const LOGS_DIR = path.join(__dirname, '../../logs');
|
||||||
const ONLINE_LOGS_DIR = path.join(LOGS_DIR, 'online');
|
const ONLINE_LOGS_DIR = path.join(LOGS_DIR, 'online');
|
||||||
const OFFLINE_LOGS_DIR = path.join(LOGS_DIR, 'offline');
|
const OFFLINE_LOGS_DIR = path.join(LOGS_DIR, 'offline');
|
||||||
|
|
||||||
|
// Number of offline cycles before sending a notification
|
||||||
|
const NOTIFICATION_THRESHOLD = 3;
|
||||||
|
|
||||||
// Create log directories if they don't exist
|
// Create log directories if they don't exist
|
||||||
function ensureLogDirectories() {
|
function ensureLogDirectories() {
|
||||||
if (!fs.existsSync(LOGS_DIR)) {
|
if (!fs.existsSync(LOGS_DIR)) {
|
||||||
|
@ -30,6 +46,14 @@ function ensureLogDirectories() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track previous states for notifications
|
||||||
|
let previousServersStatus = {};
|
||||||
|
let previousPM2Status = {};
|
||||||
|
|
||||||
|
// Track consecutive offline cycles
|
||||||
|
let serverFailureCounts = {};
|
||||||
|
let pm2FailureCounts = {};
|
||||||
|
|
||||||
let serversStatus = {};
|
let serversStatus = {};
|
||||||
REMOTE_SERVERS.forEach(server => {
|
REMOTE_SERVERS.forEach(server => {
|
||||||
serversStatus[server.name] = {
|
serversStatus[server.name] = {
|
||||||
|
@ -37,11 +61,43 @@ REMOTE_SERVERS.forEach(server => {
|
||||||
lastChecked: null,
|
lastChecked: null,
|
||||||
responseTime: null,
|
responseTime: null,
|
||||||
};
|
};
|
||||||
|
// Initialize previous status
|
||||||
|
previousServersStatus[server.name] = false;
|
||||||
|
// Initialize failure counters
|
||||||
|
serverFailureCounts[server.name] = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add PM2 services status object
|
// Add PM2 services status object
|
||||||
let pm2ServicesStatus = {};
|
let pm2ServicesStatus = {};
|
||||||
|
|
||||||
|
// Function to send FCM notification
|
||||||
|
async function sendFCMNotification(message, topic) {
|
||||||
|
try {
|
||||||
|
if (!admin.apps.length) {
|
||||||
|
console.warn("Firebase Admin not initialized, skipping notification");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the message object according to Firebase Admin SDK format
|
||||||
|
const fcmMessage = {
|
||||||
|
topic: topic,
|
||||||
|
notification: {
|
||||||
|
title: 'Server Status Alert',
|
||||||
|
body: message
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: 'server_status',
|
||||||
|
timestamp: Date.now().toString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await admin.messaging().send(fcmMessage);
|
||||||
|
console.log(`Notification sent: ${message}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending notification:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function checkServers() {
|
async function checkServers() {
|
||||||
try {
|
try {
|
||||||
ensureLogDirectories();
|
ensureLogDirectories();
|
||||||
|
@ -51,13 +107,57 @@ async function checkServers() {
|
||||||
const res = await ping.promise.probe(server.host, {
|
const res = await ping.promise.probe(server.host, {
|
||||||
timeout: 4, // Set a timeout of 4 seconds
|
timeout: 4, // Set a timeout of 4 seconds
|
||||||
});
|
});
|
||||||
serversStatus[server.name].online = res.alive;
|
|
||||||
|
// Get previous status before updating
|
||||||
|
const wasOnline = previousServersStatus[server.name];
|
||||||
|
const isNowOnline = res.alive;
|
||||||
|
|
||||||
|
// Update status
|
||||||
|
serversStatus[server.name].online = isNowOnline;
|
||||||
serversStatus[server.name].responseTime = res.time;
|
serversStatus[server.name].responseTime = res.time;
|
||||||
|
|
||||||
|
if (isNowOnline) {
|
||||||
|
// Service is online
|
||||||
|
|
||||||
|
// Reset failure counter
|
||||||
|
serverFailureCounts[server.name] = 0;
|
||||||
|
|
||||||
|
// If service was previously offline, send online notification
|
||||||
|
if (wasOnline === false) {
|
||||||
|
await sendFCMNotification(`Server ${server.name} is back online`, 'service_online');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Service is offline
|
||||||
|
|
||||||
|
// Increment failure counter
|
||||||
|
serverFailureCounts[server.name]++;
|
||||||
|
|
||||||
|
// Check if we've reached the notification threshold
|
||||||
|
if (serverFailureCounts[server.name] === NOTIFICATION_THRESHOLD) {
|
||||||
|
await sendFCMNotification(`Server ${server.name} is offline (after ${NOTIFICATION_THRESHOLD} checks)`, 'service_offline');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update previous status
|
||||||
|
previousServersStatus[server.name] = isNowOnline;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error pinging ${server.host}:`, error);
|
console.error(`Error pinging ${server.host}:`, error);
|
||||||
serversStatus[server.name].online = false;
|
serversStatus[server.name].online = false;
|
||||||
serversStatus[server.name].responseTime = null;
|
serversStatus[server.name].responseTime = null;
|
||||||
|
|
||||||
|
// Increment failure counter
|
||||||
|
serverFailureCounts[server.name]++;
|
||||||
|
|
||||||
|
// Check if we've reached the notification threshold
|
||||||
|
if (serverFailureCounts[server.name] === NOTIFICATION_THRESHOLD) {
|
||||||
|
await sendFCMNotification(`Server ${server.name} is unreachable (after ${NOTIFICATION_THRESHOLD} checks)`, 'service_offline');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update previous status
|
||||||
|
previousServersStatus[server.name] = false;
|
||||||
|
}
|
||||||
|
|
||||||
serversStatus[server.name].lastChecked = new Date().toISOString();
|
serversStatus[server.name].lastChecked = new Date().toISOString();
|
||||||
|
|
||||||
// Log server status to the appropriate folder
|
// Log server status to the appropriate folder
|
||||||
|
@ -70,6 +170,7 @@ async function checkServers() {
|
||||||
const logEntry = `[${timestamp}] Server: ${server.name} (${server.host})\n` +
|
const logEntry = `[${timestamp}] Server: ${server.name} (${server.host})\n` +
|
||||||
`Status: ${serverStatus.online ? 'ONLINE' : 'OFFLINE'}\n` +
|
`Status: ${serverStatus.online ? 'ONLINE' : 'OFFLINE'}\n` +
|
||||||
`Response Time: ${serverStatus.responseTime ? serverStatus.responseTime + 'ms' : 'N/A'}\n` +
|
`Response Time: ${serverStatus.responseTime ? serverStatus.responseTime + 'ms' : 'N/A'}\n` +
|
||||||
|
`Failure Count: ${serverFailureCounts[server.name]}\n` +
|
||||||
`-----------------------------------\n`;
|
`-----------------------------------\n`;
|
||||||
|
|
||||||
// Append to log file
|
// Append to log file
|
||||||
|
@ -94,7 +195,7 @@ async function checkPM2Services() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pm2.list((err, list) => {
|
pm2.list(async (err, list) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error getting PM2 process list:', err);
|
console.error('Error getting PM2 process list:', err);
|
||||||
pm2.disconnect();
|
pm2.disconnect();
|
||||||
|
@ -102,24 +203,63 @@ async function checkPM2Services() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update PM2 services status
|
try {
|
||||||
list.forEach(process => {
|
// Process each PM2 service sequentially with proper async handling
|
||||||
// Calculate uptime correctly - pm_uptime is a timestamp, not a duration
|
for (const process of list) {
|
||||||
const uptimeMs = process.pm2_env.pm_uptime ?
|
const uptimeMs = process.pm2_env.pm_uptime ?
|
||||||
Date.now() - process.pm2_env.pm_uptime :
|
Date.now() - process.pm2_env.pm_uptime :
|
||||||
null;
|
null;
|
||||||
|
|
||||||
pm2ServicesStatus[process.name] = {
|
const processName = process.name;
|
||||||
name: process.name,
|
const isNowOnline = process.pm2_env.status === 'online';
|
||||||
|
|
||||||
|
// Check if we've seen this process before
|
||||||
|
if (previousPM2Status[processName] === undefined) {
|
||||||
|
// First time seeing this process - initialize and don't send notification
|
||||||
|
previousPM2Status[processName] = isNowOnline;
|
||||||
|
pm2FailureCounts[processName] = 0;
|
||||||
|
console.log(`Initializing PM2 service status for ${processName}: ${isNowOnline ? 'online' : 'offline'}`);
|
||||||
|
} else {
|
||||||
|
if (isNowOnline) {
|
||||||
|
// Service is online - reset failure counter
|
||||||
|
pm2FailureCounts[processName] = 0;
|
||||||
|
|
||||||
|
// If service was previously offline, send online notification
|
||||||
|
if (previousPM2Status[processName] === false) {
|
||||||
|
await sendFCMNotification(`PM2 service ${processName} is back online`, 'service_online');
|
||||||
|
console.log(`PM2 service ${processName} changed from offline to online`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Service is offline - increment failure counter
|
||||||
|
pm2FailureCounts[processName]++;
|
||||||
|
|
||||||
|
// Only send notification if threshold is reached
|
||||||
|
if (pm2FailureCounts[processName] === NOTIFICATION_THRESHOLD) {
|
||||||
|
await sendFCMNotification(`PM2 service ${processName} is offline (status: ${process.pm2_env.status}, after ${NOTIFICATION_THRESHOLD} checks)`, 'service_offline');
|
||||||
|
console.log(`PM2 service ${processName} is offline for ${NOTIFICATION_THRESHOLD} consecutive checks`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update previous status
|
||||||
|
previousPM2Status[processName] = isNowOnline;
|
||||||
|
|
||||||
|
// Update status object
|
||||||
|
pm2ServicesStatus[processName] = {
|
||||||
|
name: processName,
|
||||||
id: process.pm_id,
|
id: process.pm_id,
|
||||||
status: process.pm2_env.status,
|
status: process.pm2_env.status,
|
||||||
cpu: process.monit ? process.monit.cpu : null,
|
cpu: process.monit ? process.monit.cpu : null,
|
||||||
memory: process.monit ? process.monit.memory : null,
|
memory: process.monit ? process.monit.memory : null,
|
||||||
uptime: uptimeMs, // Store the uptime in milliseconds
|
uptime: uptimeMs,
|
||||||
restarts: process.pm2_env.restart_time,
|
restarts: process.pm2_env.restart_time,
|
||||||
|
failureCount: pm2FailureCounts[processName],
|
||||||
lastChecked: new Date().toISOString()
|
lastChecked: new Date().toISOString()
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing PM2 services:', error);
|
||||||
|
}
|
||||||
|
|
||||||
pm2.disconnect();
|
pm2.disconnect();
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -160,7 +300,10 @@ router.get("/", (req, res) => {
|
||||||
try {
|
try {
|
||||||
res.json({
|
res.json({
|
||||||
servers: serversStatus,
|
servers: serversStatus,
|
||||||
pm2Services: pm2ServicesStatus
|
pm2Services: pm2ServicesStatus,
|
||||||
|
serverFailureCounts: serverFailureCounts,
|
||||||
|
pm2FailureCounts: pm2FailureCounts,
|
||||||
|
notificationThreshold: NOTIFICATION_THRESHOLD
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sending status response:", error);
|
console.error("Error sending status response:", error);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const { Client, GatewayIntentBits, ChannelType } = require("discord.js");
|
const { Client, GatewayIntentBits, ChannelType } = require("discord.js");
|
||||||
const CommandManager = require('./CommandManager');
|
const CommandManager = require('./CommandManager');
|
||||||
const NotificationService = require('./NotificationService');
|
// const NotificationService = require('./NotificationService');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class Bot {
|
||||||
this.setupEventHandlers();
|
this.setupEventHandlers();
|
||||||
|
|
||||||
// Initialize notification service
|
// Initialize notification service
|
||||||
this.notificationService = null;
|
// this.notificationService = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupTempDirectory() {
|
setupTempDirectory() {
|
||||||
|
@ -56,13 +56,13 @@ class Bot {
|
||||||
await this.commandManager.registerGlobalCommands();
|
await this.commandManager.registerGlobalCommands();
|
||||||
|
|
||||||
// Initialize and start the notification service
|
// Initialize and start the notification service
|
||||||
this.notificationService = new NotificationService(this.client, {
|
// this.notificationService = new NotificationService(this.client, {
|
||||||
checkInterval: process.env.STATUS_CHECK_INTERVAL ? parseInt(process.env.STATUS_CHECK_INTERVAL) : 5000,
|
// checkInterval: process.env.STATUS_CHECK_INTERVAL ? parseInt(process.env.STATUS_CHECK_INTERVAL) : 5000,
|
||||||
statusEndpoint: process.env.STATUS_ENDPOINT || 'https://blahaj.tr:2589/status'
|
// statusEndpoint: process.env.STATUS_ENDPOINT || 'https://blahaj.tr:2589/status'
|
||||||
});
|
// });
|
||||||
|
|
||||||
await this.notificationService.initialize();
|
// await this.notificationService.initialize();
|
||||||
this.notificationService.start();
|
// this.notificationService.start();
|
||||||
|
|
||||||
// Send startup notification
|
// Send startup notification
|
||||||
await this.sendStartupNotification();
|
await this.sendStartupNotification();
|
||||||
|
@ -108,12 +108,12 @@ class Bot {
|
||||||
name: "Relative Time",
|
name: "Relative Time",
|
||||||
value: `<t:${Math.floor(Date.now() / 1000)}:R>`,
|
value: `<t:${Math.floor(Date.now() / 1000)}:R>`,
|
||||||
inline: true
|
inline: true
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Status Monitoring",
|
|
||||||
value: this.notificationService?.isRunning ? "✅ Active" : "❌ Inactive",
|
|
||||||
inline: true
|
|
||||||
}
|
}
|
||||||
|
// {
|
||||||
|
// name: "Status Monitoring",
|
||||||
|
// value: this.notificationService?.isRunning ? "✅ Active" : "❌ Inactive",
|
||||||
|
// inline: true
|
||||||
|
// }
|
||||||
],
|
],
|
||||||
footer: {
|
footer: {
|
||||||
text: "blahaj.tr"
|
text: "blahaj.tr"
|
||||||
|
@ -131,14 +131,14 @@ class Bot {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also notify in status channel if configured
|
// Also notify in status channel if configured
|
||||||
if (this.notificationService?.statusChannel) {
|
// if (this.notificationService?.statusChannel) {
|
||||||
try {
|
// try {
|
||||||
await this.notificationService.statusChannel.send({ embeds: [startupEmbed] });
|
// await this.notificationService.statusChannel.send({ embeds: [startupEmbed] });
|
||||||
console.log(`Sent startup notification to status channel: ${this.notificationService.statusChannel.name}`);
|
// console.log(`Sent startup notification to status channel: ${this.notificationService.statusChannel.name}`);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Failed to send startup notification to status channel:", error.message);
|
// console.error("Failed to send startup notification to status channel:", error.message);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendShutdownNotification(reason = "Manual shutdown", error = null) {
|
async sendShutdownNotification(reason = "Manual shutdown", error = null) {
|
||||||
|
@ -178,9 +178,9 @@ class Bot {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop notification service if running
|
// Stop notification service if running
|
||||||
if (this.notificationService?.isRunning) {
|
// if (this.notificationService?.isRunning) {
|
||||||
this.notificationService.stop();
|
// this.notificationService.stop();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Notify authorized user
|
// Notify authorized user
|
||||||
try {
|
try {
|
||||||
|
@ -192,14 +192,14 @@ class Bot {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also notify in status channel if available
|
// Also notify in status channel if available
|
||||||
if (this.notificationService?.statusChannel) {
|
// if (this.notificationService?.statusChannel) {
|
||||||
try {
|
// try {
|
||||||
await this.notificationService.statusChannel.send({ embeds: [shutdownEmbed] });
|
// await this.notificationService.statusChannel.send({ embeds: [shutdownEmbed] });
|
||||||
console.log(`Sent shutdown notification to status channel: ${this.notificationService.statusChannel.name}`);
|
// console.log(`Sent shutdown notification to status channel: ${this.notificationService.statusChannel.name}`);
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Failed to send shutdown notification to status channel:", error.message);
|
// console.error("Failed to send shutdown notification to status channel:", error.message);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
|
@ -210,9 +210,9 @@ class Bot {
|
||||||
|
|
||||||
async stop() {
|
async stop() {
|
||||||
// Stop notification service
|
// Stop notification service
|
||||||
if (this.notificationService) {
|
// if (this.notificationService) {
|
||||||
this.notificationService.stop();
|
// this.notificationService.stop();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Destroy the client
|
// Destroy the client
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
|
|
42
discord/commands/shutdown.js
Normal file
42
discord/commands/shutdown.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
const { SlashCommandBuilder } = require('@discordjs/builders');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('shutdown')
|
||||||
|
.setDescription('Shuts down the bot gracefully')
|
||||||
|
.setDefaultMemberPermissions(0), // Restricts to administrators only
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
// Check if user has admin permissions
|
||||||
|
if (!interaction.member.permissions.has('ADMINISTRATOR')) {
|
||||||
|
return await interaction.reply({
|
||||||
|
content: 'You do not have permission to use this command.',
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
content: 'Shutting down the bot. Goodbye!',
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger the shutdown process
|
||||||
|
console.log(`Manual shutdown triggered by ${interaction.user.tag}`);
|
||||||
|
|
||||||
|
// Give Discord API time to send the reply
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
// Get the bot instance
|
||||||
|
const bot = interaction.client;
|
||||||
|
|
||||||
|
// Use the existing shutdown mechanism
|
||||||
|
await bot.sendShutdownNotification(`Manual shutdown triggered by ${interaction.user.tag}`);
|
||||||
|
await bot.stop();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during shutdown command:', error);
|
||||||
|
} finally {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
};
|
1933
package-lock.json
generated
1933
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -20,10 +20,13 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Xargana/blahaj-srv#readme",
|
"homepage": "https://github.com/Xargana/blahaj-srv#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.9.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.7",
|
"discord.js": "^14.19.1",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
|
"firebase-admin": "^13.3.0",
|
||||||
|
"googleapis": "^148.0.0",
|
||||||
"nodejs": "^0.0.0",
|
"nodejs": "^0.0.0",
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"pm2": "^6.0.5",
|
"pm2": "^6.0.5",
|
||||||
|
|
61
test-firebase.js
Normal file
61
test-firebase.js
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
const admin = require('firebase-admin');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Get path to service account file
|
||||||
|
const serviceAccountPath = path.join(__dirname, 'firebase-service-account.json');
|
||||||
|
|
||||||
|
// Log the path to verify
|
||||||
|
console.log(`Loading Firebase service account from: ${serviceAccountPath}`);
|
||||||
|
|
||||||
|
// Initialize Firebase Admin SDK
|
||||||
|
try {
|
||||||
|
const serviceAccount = require(serviceAccountPath);
|
||||||
|
|
||||||
|
admin.initializeApp({
|
||||||
|
credential: admin.credential.cert(serviceAccount)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Firebase Admin SDK initialized successfully");
|
||||||
|
|
||||||
|
// Function to send FCM notification
|
||||||
|
async function sendTestNotification() {
|
||||||
|
try {
|
||||||
|
// Send to both topics to test both scenarios
|
||||||
|
const topics = ['service_online', 'service_offline'];
|
||||||
|
|
||||||
|
for (const topic of topics) {
|
||||||
|
// Correctly format the message for Firebase Cloud Messaging
|
||||||
|
const message = {
|
||||||
|
topic: topic,
|
||||||
|
notification: {
|
||||||
|
title: 'Test Notification',
|
||||||
|
body: `This is a test notification to the ${topic} topic`
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: 'test',
|
||||||
|
timestamp: Date.now().toString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`Sending test notification to topic: ${topic}`);
|
||||||
|
// Use the send method instead of sendToTopic
|
||||||
|
const response = await admin.messaging().send(message);
|
||||||
|
console.log(`Successfully sent message to ${topic}:`, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All test notifications sent successfully!");
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending notification:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the test
|
||||||
|
sendTestNotification();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing Firebase Admin SDK:", error);
|
||||||
|
console.error("Make sure firebase-service-account.json exists in the repository root");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
Loading…
Reference in a new issue