commit 81ab5e4f7b59e50276abe6460c079515fd04b52f Author: Sophia Atkinson Date: Mon Apr 28 20:04:55 2025 -0700 Initial commit (v1.0.5) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1d35cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +config.json +data/state.json +old.js +package-lock.json diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..51162a9 --- /dev/null +++ b/config.json.example @@ -0,0 +1,13 @@ +[ + { + "name": "first", + "markdownURL": "https://example.com/s/example/download", + "webhookURL": "https://discord.com/api/webhooks/example/example" + }, + { + "name": "second", + "markdownURL": "https://example.com/s/example/download", + "webhookURL": "https://discord.com/api/webhooks/example/example" + } + ] + \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..755ece4 --- /dev/null +++ b/index.js @@ -0,0 +1,145 @@ +const axios = require('axios'); +const fs = require('fs'); +const path = require('path'); + +// --- CONFIG --- +const pollIntervalMs = 30 * 1000; // 30 seconds +const configPath = path.join(__dirname, 'config.json'); +const statePath = path.join(__dirname, './data/state.json'); + +// Load configs +let configs; +try { + configs = JSON.parse(fs.readFileSync(configPath, 'utf8')); +} catch (error) { + console.error('❗ Failed to load config.json:', error.message); + process.exit(1); +} + +// Load or initialize global state +let state = {}; +if (fs.existsSync(statePath)) { + try { + state = JSON.parse(fs.readFileSync(statePath, 'utf8')); + } catch (error) { + console.error('❗ 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(); +} + +// Fetch markdown content +async function fetchMarkdown(url) { + try { + const { data } = await axios.get(url); + return data; + } catch (error) { + console.error('❗ Error fetching markdown:', 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 { + console.error(`❗ [${config.name}] Failed to get message ID.`); + } + } catch (error) { + console.error(`❌ [${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) { + console.error(`⚠️ [${config.name}] Message not found (deleted?). Creating new...`); + state[config.name].messageId = ''; + await createNewMessage(config); + } else { + console.error(`❌ [${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) { + console.error(`⚠️ [${config.name}] Message not found. Will create new.`); + state[config.name].messageId = ''; + } else { + console.error(`❗ [${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); +})(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..f103910 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "discord-hedgedoc-md", + "version": "1.0.5", + "main": "index.js", + "scripts": { + "test": "node index.js" + }, + "keywords": [], + "author": "Sophia Atkinson", + "license": "MIT", + "description": "", + "dependencies": { + "axios": "^1.9.0", + "fs": "^0.0.1-security", + "path": "^0.12.7" + } +}