185 lines
5.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const axios = require('axios');
const fs = require('fs');
const path = require('path');
const dns = require('dns');
const configPath = path.join(__dirname, 'config.json');
const statePath = path.join(__dirname, './data/state.json');
const errorLogPath = path.join(__dirname, './data/error.log');
// Load configs
let rawConfigs;
try {
rawConfigs = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (error) {
console.error('❗ Failed to load config.json: ' + error.message);
process.exit(1);
}
const globalSettings = rawConfigs[0] || {};
const configs = rawConfigs.slice(1);
const globalTimezone = globalSettings.timezone || 'UTC';
const pollIntervalMs = parseInt(globalSettings.pollingRateMS || '30000');
if (configs.length === 0) {
console.error('❗ No configs found in config.json.');
process.exit(1);
}
// --- ERROR LOGGER ---
function logError(message) {
const timestamp = new Date().toLocaleString('en-US', {
timeZone: globalTimezone
});
const line = `[${timestamp}] ${message}\n`;
fs.appendFileSync(errorLogPath, line);
console.error(message);
}
// Load or initialize global state
let state = {};
if (fs.existsSync(statePath)) {
try {
state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
} catch (error) {
logError('❗ Failed to load state.json: ' + error.message);
process.exit(1);
}
}
// Save global state
function saveState() {
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
}
// Normalize content
function normalizeContent(content) {
return content.replace(/\r\n/g, '\n').trim();
}
// Check if the host of a URL is reachable
async function checkHostAvailability(url) {
const { hostname } = new URL(url);
return new Promise((resolve) => {
dns.lookup(hostname, (err) => {
if (err) {
logError(`🌐 Host unreachable: ${hostname} ${err.message}`);
resolve(false);
} else {
resolve(true);
}
});
});
}
// Fetch markdown content
async function fetchMarkdown(url) {
const hostOK = await checkHostAvailability(url);
if (!hostOK) return '';
try {
const { data } = await axios.get(url);
return data;
} catch (error) {
logError(`❗ Error fetching markdown from ${url}: ${error.message}`);
return '';
}
}
// Send a new message
async function createNewMessage(config) {
try {
const content = state[config.name].lastContent;
const payload = { content: content.length > 2000 ? content.slice(0, 1997) + '...' : content };
const { data } = await axios.post(`${config.webhookURL}?wait=true`, payload);
if (data?.id) {
state[config.name].messageId = data.id;
saveState();
console.log(`✅ [${config.name}] New message sent and ID saved.`);
} else {
logError(`❗ [${config.name}] Failed to get message ID.`);
}
} catch (error) {
logError(`❌ [${config.name}] Error sending new message: ${error.message}`);
}
}
// Update existing message
async function updateMessage(config) {
try {
const content = state[config.name].lastContent;
const payload = { content: content.length > 2000 ? content.slice(0, 1997) + '...' : content };
await axios.patch(`${config.webhookURL}/messages/${state[config.name].messageId}`, payload);
console.log(`✅ [${config.name}] Message updated.`);
} catch (error) {
if (error.response && error.response.status === 404) {
logError(`⚠️ [${config.name}] Message not found (deleted?). Creating new...`);
state[config.name].messageId = '';
await createNewMessage(config);
} else {
logError(`❌ [${config.name}] Error updating message: ${error.message}`);
}
}
}
// Validate existing message
async function validateExistingMessage(config) {
if (!state[config.name]?.messageId) return;
try {
await axios.get(`${config.webhookURL}/messages/${state[config.name].messageId}`);
console.log(`✅ [${config.name}] Existing message validated.`);
} catch (error) {
if (error.response && error.response.status === 404) {
logError(`⚠️ [${config.name}] Message not found. Will create new.`);
state[config.name].messageId = '';
} else {
logError(`❗ [${config.name}] Error validating message: ${error.message}`);
}
}
}
// Check for updates for a single config
async function checkForUpdates(config) {
if (!state[config.name]) {
state[config.name] = { messageId: '', lastContent: '' };
}
await validateExistingMessage(config);
console.log(`🔄 [${config.name}] Checking for updates...`);
const markdown = await fetchMarkdown(config.markdownURL);
const normalizedMarkdown = normalizeContent(markdown);
if (!normalizedMarkdown) {
console.log(`⚠️ [${config.name}] No content fetched.`);
return;
}
if (!state[config.name].messageId) {
console.log(`📄 [${config.name}] No message ID found, creating...`);
state[config.name].lastContent = normalizedMarkdown;
await createNewMessage(config);
} else if (normalizedMarkdown !== state[config.name].lastContent) {
console.log(`📄 [${config.name}] Content changed, updating...`);
state[config.name].lastContent = normalizedMarkdown;
saveState();
await updateMessage(config);
} else {
console.log(`⏳ [${config.name}] No changes detected.`);
}
}
// Main loop
(async () => {
console.log('🚀 Starting poller...');
for (const config of configs) {
await checkForUpdates(config);
}
setInterval(async () => {
for (const config of configs) {
await checkForUpdates(config);
}
}, pollIntervalMs);
})();