mirror of
https://github.com/SophiaAtkinson/discord-hedgedoc-md.git
synced 2025-06-27 00:47:42 -07:00
add beter configuration, & added readme with guide
This commit is contained in:
78
README.md
Normal file
78
README.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Hedgedoc to Discord
|
||||||
|
|
||||||
|
## What does it do?
|
||||||
|
|
||||||
|
- Monitors Markdown content from multiple configurable URLs such as HedgeDoc, or any markdown source (designed and tested for HedgeDoc).
|
||||||
|
- Pushes polled markdown content to a Discord webhook.
|
||||||
|
- Checks for changes by comparing content against a local copy of the last known version (stored in /data/state.json).
|
||||||
|
- Automatically updates the Discord message when the Markdown changes.
|
||||||
|
- Logs markdown source errors (timeouts, HTTP errors, etc.) in the specified timezone (stored in /data/error.log).
|
||||||
|
- Truncates Markdown content to 2000 characters to comply with Discord limits.
|
||||||
|
- Recreates manually deleted messages on the next content change.
|
||||||
|
|
||||||
|
## Why use this?
|
||||||
|
|
||||||
|
- Collaborate with your staff to manage rules for your Discord server.
|
||||||
|
- Ensure your community always sees the most up-to-date information.
|
||||||
|
- It's not a bot, it uses webhooks, so it’s lightweight and easy to deploy.
|
||||||
|
|
||||||
|
## Requirments
|
||||||
|
|
||||||
|
- NodeJS 20+
|
||||||
|
- NPM
|
||||||
|
|
||||||
|
## How to set up
|
||||||
|
|
||||||
|
Start by downloading the code. You can clone the repository by running:
|
||||||
|
|
||||||
|
`git clone https://github.com/SophiaAtkinson/discord-hedgedoc-md.git`
|
||||||
|
|
||||||
|
Or download the ZIP directly from [GitHub](https://github.com/SophiaAtkinson/discord-hedgedoc-md/archive/refs/heads/main.zip).
|
||||||
|
|
||||||
|
Once downloaded, navigate into the directory:
|
||||||
|
|
||||||
|
`cd discord-hedgedoc-md`
|
||||||
|
|
||||||
|
Next, install the dependencies:
|
||||||
|
|
||||||
|
`npm install`
|
||||||
|
|
||||||
|
Copy the example config to get started:
|
||||||
|
|
||||||
|
`cp config.json.example config.json`
|
||||||
|
|
||||||
|
Then open config.json in your preferred text editor.
|
||||||
|
|
||||||
|
### Config options
|
||||||
|
|
||||||
|
- timezone: Set your preferred timezone. If left blank, it defaults to `UTC`.
|
||||||
|
- pollingRate: Recommended to keep at the default `30000` (30 seconds). You can increase this, but don’t go lower.
|
||||||
|
- webhooks: For each webhook, you’ll need:
|
||||||
|
- name: A name to identify it.
|
||||||
|
- markdownURL: The URL to fetch markdown from (e.g., https://example.com/s/example/download).
|
||||||
|
- webhookURL: Your Discord webhook URL (found under Channel Settings → Integrations → Webhooks).
|
||||||
|
|
||||||
|
If you only want one webhook, just include one in the array. If you want more, add additional entries.
|
||||||
|
|
||||||
|
Once configured, start the tool:
|
||||||
|
|
||||||
|
`npm run`
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
`node index.js`
|
||||||
|
|
||||||
|
That’s it! The messages should now appear in Discord. If you run into issues, feel free to open an issue on GitHub.
|
||||||
|
|
||||||
|
### Run it 24/7
|
||||||
|
|
||||||
|
You can use **[pm2](https://pm2.io/)** or **[Docker](https://www.docker.com/)** to keep the service running continuously.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Timezones must be in [TZ format](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
|
||||||
|
- Markdown must be supported by [Discord’s Markdown guide](https://support.discord.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline)
|
||||||
|
- Your markdown source should not have aggressive rate limiting.
|
||||||
|
- Do not set the polling rate below 30 seconds (30000ms), as too many requests might be considered API abuse.
|
||||||
|
- With the recommended max of 20 webhook sources, the tool should not exceed 128MB of RAM. If it does, please open an issue.
|
||||||
|
- If you are using Hedgedoc, you must provide `/download` to the end of you published content!
|
@ -1,4 +1,8 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"timezone": "America/Los_Angeles",
|
||||||
|
"pollingRateMS": "30000"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "first",
|
"name": "first",
|
||||||
"markdownURL": "https://example.com/s/example/download",
|
"markdownURL": "https://example.com/s/example/download",
|
||||||
|
34
index.js
34
index.js
@ -3,32 +3,40 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const dns = require('dns');
|
const dns = require('dns');
|
||||||
|
|
||||||
// --- CONFIG ---
|
|
||||||
const pollIntervalMs = 30 * 1000; // 30 seconds
|
|
||||||
const configPath = path.join(__dirname, 'config.json');
|
const configPath = path.join(__dirname, 'config.json');
|
||||||
const statePath = path.join(__dirname, './data/state.json');
|
const statePath = path.join(__dirname, './data/state.json');
|
||||||
const errorLogPath = path.join(__dirname, './data/error.log');
|
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 ---
|
// --- ERROR LOGGER ---
|
||||||
function logError(message) {
|
function logError(message) {
|
||||||
const timestamp = new Date().toLocaleString('en-US', {
|
const timestamp = new Date().toLocaleString('en-US', {
|
||||||
timeZone: 'America/Los_Angeles'
|
timeZone: globalTimezone
|
||||||
});
|
});
|
||||||
const line = `[${timestamp}] ${message}\n`;
|
const line = `[${timestamp}] ${message}\n`;
|
||||||
fs.appendFileSync(errorLogPath, line);
|
fs.appendFileSync(errorLogPath, line);
|
||||||
console.error(message);
|
console.error(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Load configs
|
|
||||||
let configs;
|
|
||||||
try {
|
|
||||||
configs = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
||||||
} catch (error) {
|
|
||||||
logError('❗ Failed to load config.json: ' + error.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load or initialize global state
|
// Load or initialize global state
|
||||||
let state = {};
|
let state = {};
|
||||||
if (fs.existsSync(statePath)) {
|
if (fs.existsSync(statePath)) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "discord-hedgedoc-md",
|
"name": "discord-hedgedoc-md",
|
||||||
"version": "1.0.5",
|
"version": "1.1",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node index.js",
|
"test": "node index.js",
|
||||||
|
Reference in New Issue
Block a user