You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A bidirectional bridge between MeshCore mesh networks and Discord, with webhook-based message display, image uploads, emoji reaction mirroring, channel subscriptions, emergency alerts, and operator controls.
This fork significantly extends the original project with multi-channel routing, multi-server support, flood protection, message chunking, reaction mirroring (compatible with MeshCoreOne), webhook-based posting with per-user avatars, image uploads, and much more.
Prerequisites
Node.js: v18.0.0 or higher (required for top-level await and native fetch)
Hardware: A MeshCore node flashed with the latest client firmware, connected via USB
Process Manager: PM2 is recommended for 24/7 background operation
Quick Start
git clone https://github.com/tek126/MeshCoreDiscordBridge.git
cd MeshCoreDiscordBridge
npm install
node setup.js
The setup wizard will walk you through configuration step by step, including where to find each value in Discord.
Webhook-based display -- Mesh users appear with their own username and unique auto-generated avatar in Discord
Packet path display -- Each message shows the repeater path it took in small text below the message (e.g. 2 hops: [ACE1] Westmere X2 -> [C4] ESP | W). Supports 1, 2, and 3-byte hash modes with automatic repeater name resolution from the contacts list and persistent backup
Reaction mirroring -- Emoji reactions from MeshCoreOne are applied as native Discord reactions on the correct message (hash-compatible with MeshCoreOne's Crockford Base32 algorithm). Supports both old (emoji@[Name]hash) and new (@[Name]emoji\nhash) formats
Bridge prefix stripping -- Configurable list of other bridge node names to strip (e.g. txtMesh)
Message deduplication -- Duplicate messages from multiple bridges are detected and dropped (30-second window)
Node join announcements -- New mesh nodes are announced in Discord when first discovered
Unmapped channel labels -- Messages from mesh channels without a Discord route are labeled with the channel name
Language filter -- Configurable word filter with customizable response (see LANGUAGE_FILTER config)
Discord to Mesh
Always-forward channels -- Designated Discord channels automatically relay all messages to mesh
Image uploads -- Images posted in Discord are uploaded to your file host (FILE_HOST_URL), or to ImgBB if none is set, and the link is sent to mesh
File attachments -- Non-image files are sent with file type, size, and a link (e.g. [PDF, 1.4MB] https://unym.pics/x7Kp2a.pdf). Without a file host, the link is a TinyURL to the Discord CDN URL, which expires after about a day
File host contract -- POST {FILE_HOST_URL}/upload with Authorization: Bearer <token>, the raw file as the body, and an X-Filename header; respond with JSON {"url": "..."}
Reaction mirroring -- Discord emoji reactions on bridged messages are sent to mesh in MeshCoreOne-compatible format with correct hash
Mention translation -- Discord mentions (@user, @role, #channel) are resolved to readable names before forwarding to mesh
[D] tag -- Discord-origin messages are tagged with [D] so mesh users can identify them
Message chunking -- Long messages are split with n/N counters and paced to avoid mesh overload
@everyone alerts -- First message on the emergency mesh channel pings @everyone in Discord
Auto-reply -- Sends a confirmation message back to mesh ("Your message has been forwarded...")
Reminder pings -- If no Discord user responds within a configurable time (default 5 min), pings @everyone again
Cooldown -- Configurable cooldown (default 30 min) before a new alert cycle can trigger
Channel Subscriptions
Reaction-based roles -- Users react to a message to subscribe/unsubscribe from mesh channels
Auto-setup -- /subscribe-setup creates roles, sets channel permissions, and posts the subscription message
Channel visibility -- Subscribed channels are visible only to users with the corresponding role
Public channels -- Public and emergency channels remain visible to everyone. On startup, routed channels that are not subscribable are made visible to @everyone, except those listed in VISIBILITY_SYNC_SKIP_CHANNEL_IDS
User Moderation
Admin block -- /block <username> to permanently block a mesh user from Discord forwarding
Community vote-block -- /voteblock <username> <reason> starts a 15-minute community vote. Requires 10% of online members (minimum 3). Admins can veto with a reaction
Appeals -- Blocked users can reply "appeal" on mesh once per day to send a message to Discord
Auto-expiry -- Vote-blocks expire automatically with notifications to both mesh and Discord
Direct Messages
DM forwarding -- Mesh DMs to the bridge node are forwarded as Discord DMs to a configurable user
DM replies -- Reply to the forwarded Discord DM to send a response back to the mesh user
Welcome messages -- Two-step welcome for new mesh users: a channel greeting on their first Public message, then a DM with details when their advert arrives. Both messages are configurable and can be enabled/disabled via WELCOME_ENABLED
DM chunking -- Long DMs are automatically split into multiple messages with part numbers
Scheduled Messages
Flexible scheduling -- Send recurring messages to mesh channels, Discord channels, or both
Multiple formats -- daily HH:MM, weekly mon HH:MM, every Nh, every Nm
Manage via commands -- /schedule add, /schedule list, /schedule remove
Bridge Controls
Auto-reconnect -- Automatically reconnects if the USB serial connection drops
Live config reload -- Reload config.json without restarting via /bridge reload
Pause/resume -- Temporarily halt all forwarding in both directions
Graceful shutdown -- Clean shutdown on SIGTERM/SIGINT saves message history and stops timers
Persistent message history -- Reaction hash tracking survives restarts (saved to disk, 24-hour expiry)
Contacts backup -- Repeater/node names are backed up to disk for path resolution even after device resets
Contact pruning -- Automatic removal of stale contacts from the radio to free slots (configurable threshold and age)
Web Configuration UI
Browser-based admin dashboard -- Edit config, view status/metrics, and control the bridge from a web browser
Discord OAuth -- Only users with admin roles or permissions can access (uses BRIDGE_ADMIN_ROLE_IDS)
Live status -- Auto-refreshing dashboard showing uptime, message counts, mesh connection, error counts
Full config editor -- Dynamic form with appropriate inputs for each value type (text, number, boolean, arrays, objects)
Bridge controls -- Pause/resume forwarding and reload config from the browser
Max unknown repeaters in path before skipping welcome DM (default 1)
WELCOME_CHANNEL_MESSAGE
Channel greeting for new users on Public. Use {name} for username
WELCOME_DM_MESSAGE
DM sent to new users when their advert is received
Scheduled Messages
Key
Description
SCHEDULED_MESSAGES
Auto-managed array of scheduled messages (use /schedule commands)
Bridge Configuration
Key
Description
BRIDGE_PREFIXES
Array of other bridge node names to strip from messages
BRIDGE_ADMIN_ROLE_IDS
Discord role IDs allowed to use admin commands
NODE_ANNOUNCE_CHANNEL_ID
Channel for new node announcements (falls back to default)
DEBUG
Enable verbose debug logging (true/false)
Message Handling
Key
Description
MESH_MAXLEN
Max message length for mesh in characters (default 160)
MESH_CHUNK_DELAY_MS
Delay in ms between chunked message parts (default 2500)
MESH_MIN_SEND_GAP_MS
Minimum gap in ms between any two mesh transmissions, so repeaters aren't handed packets back to back (default 5000, 0 disables)
MESH_CHANNEL_SCOPES
Per-channel flood scope (region) for messages the bridge sends, e.g. {"2": "troy", "1475203223852814470": "alb"}. Keys are mesh channel indexes or routed Discord channel IDs; values are public region names, written without a # (a leading # is ignored), or "*" for unscoped. Channels not listed, and DMs, use the radio's default scope. Only repeaters with that region configured will forward scoped messages. Needs companion firmware v8+ ("*" needs v12+); private $ regions aren't supported
MESH_KNOWN_SCOPES
Extra region names to recognise on received messages, e.g. ["troy", "sar"]. The hop line on Discord shows the scope a message arrived with (· alb) when it's one of these or a MESH_CHANNEL_SCOPES region, · unknown scope for other scoped messages, and nothing for unscoped ones. Scope never affects what the bridge receives
Flood Protection (FLOOD_PROTECT)
Key
Description
WINDOW_SECONDS
Sliding window duration for rate limiting (default 15)
MAX_MESSAGES_PER_WINDOW
Max messages allowed per window before cooldown (default 6)
COOLDOWN_SECONDS
How long to block forwarding after limit is hit (default 60)
WARN_IN_CHANNEL
Post a warning in Discord when flood protection triggers (true/false)
Language Filter (LANGUAGE_FILTER)
Key
Description
ENABLED
Enable/disable the language filter (default true if section present)
WORDS
Array of words to filter (case-insensitive)
RESPONSE
Message sent back to mesh and Discord when triggered
Contact Pruning
Key
Description
CONTACT_PRUNE_INTERVAL_MINUTES
How often to run auto-prune (default 60)
CONTACT_PRUNE_STALE_HOURS
Remove contacts not seen for this many hours (default 48)
CONTACT_PRUNE_THRESHOLD
Only prune when contact count exceeds this (default 200)
User Moderation
Key
Description
BLOCKED_MESH_USERS
Array of blocked users (managed via /block and /voteblock commands)
Web UI
Key
Description
WEB_PORT
Port for the web configuration UI (web server only starts if set)