بدون توضیح

ArtyomV2X 90d439a682 Add WebUI Template Builder 1 ماه پیش
README.md 90d439a682 Add WebUI Template Builder 1 ماه پیش
app.py 90d439a682 Add WebUI Template Builder 1 ماه پیش
dis2hook.initd 79fa917e95 Initial Commit 1 ماه پیش
index.html 90d439a682 Add WebUI Template Builder 1 ماه پیش
install.sh 5554a3cdd8 Bug fixes 1 ماه پیش
update.sh 5554a3cdd8 Bug fixes 1 ماه پیش

README.md

Dis2Hook — Webhook Relay to Discord Bot

Dis2Hook receives webhooks from anything that can send an HTTP POST — Gitea, Gogs, Proxmox notifications, Uptime Kuma, home-automation scripts — formats them as Markdown, and relays them into Discord channels through the Discord Bot API (not Discord's incoming webhooks, so one bot identity serves every channel).

It is built to live in a small Alpine Linux LXC on Proxmox and is configured entirely from a built-in web UI.

  source A ──▶ POST /hook/gitea-ci ──┐
  source B ──▶ POST /hook/kuma ──────┼──▶  Dis2Hook  ──▶  Discord Bot API ──▶ #channels
  source C ──▶ POST /hook/backup ────┘

Features

  • Web UI configurator — patch in any number of webhook sources, each routed to its own Discord server/channel, from a single console page.
  • Markdown templates — shape each source's message with Discord Markdown and {dotted.path[0]} placeholders resolved from the incoming JSON payload; leave the template empty to relay the raw payload as a pretty-printed JSON block.
  • Event filters — per-source rules (equals / contains / exists on any payload path) so only the events you care about reach Discord.
  • Drag-and-drop template builder — Dis2Hook remembers the last event each source received; the UI shows its JSON keys as draggable chips you drop straight into the template (or click to insert), so templates are built from real data instead of guesswork.
  • Image embedding — point a source at any payload path holding an image: an internet URL (embedded by Discord), a LAN URL like http://192.168.x.x/… (Dis2Hook fetches it and uploads it as an attachment, since Discord can't reach your LAN), or base64 image data / data-URIs (decoded and attached). JPEG/PNG/GIF/WebP, up to 8 MB.
  • Heartbeat — optional periodic pulse to a channel with uptime and relay counts.
  • Status updates — optional online/offline announcements when the service starts or stops.
  • Secrets stay local — bot token and admin key live in a token.json generated at install time; it is never pulled from, nor pushed to, this repo.
  • One-file updater — pulls fresh app.py / index.html straight from the repo, validates them, backs up the old ones, and restarts the service.

Install

Run this inside an existing Alpine LXC — never on the Proxmox host. The installer refuses to run if it detects Proxmox VE.

  1. On the Proxmox host, enter your Alpine container: pct enter <ctid>
  2. Run the one-liner:

    wget -qO- https://gogs.av2x.dev/av2x/Dis2Hook/raw/master/install.sh | ash
    

The installer will:

  • verify it is running inside Alpine (and not on a Proxmox host),
  • install python3, py3-flask, py3-requests via apk,
  • pull app.py, index.html, update.sh, and the OpenRC service script directly from this repo,
  • prompt for your Discord bot token (Developer Portal → your app → Bot),
  • generate /opt/dis2hook/token.json (mode 600) with the token and a fresh random admin key, printed once at the end — save it,
  • register and start the dis2hook OpenRC service.

Non-interactive install:

DISCORD_BOT_TOKEN=xxxxx wget -qO- https://gogs.av2x.dev/av2x/Dis2Hook/raw/master/install.sh | ash

Optional environment overrides: D2H_BRANCH (defaults to master, auto-falls back to main), D2H_DIR (default /opt/dis2hook), D2H_PORT (default 8823).

First-time setup

  1. Create a bot at the Discord Developer Portal and copy its token.
  2. Invite it to your server with the Send Messages and View Channel permissions (OAuth2 URL generator → scope bot).
  3. In Discord, enable Developer Mode, right-click a channel → Copy Channel ID.
  4. Open http://<container-ip>:8823/, unlock with the admin key, add a source, paste the channel ID, save, and hit Send test message.
  5. Point your service at the source's webhook URL, sending the source secret in the X-Hook-Secret header (or ?secret= query, or a Bearer token).

Sending a webhook by hand

curl -X POST "http://<container-ip>:8823/hook/gitea-ci" \
  -H "Content-Type: application/json" \
  -H "X-Hook-Secret: <source secret>" \
  -d '{"action":"opened","repository":{"full_name":"av2x/Dis2Hook"}}'

Template example

🔔 **{repository.full_name}** — {pusher.name} pushed to `{ref}`
> {commits[0].message}
{commits[0].url}

Unresolved placeholders render as empty strings; objects/lists render as compact JSON. Messages are truncated to Discord's 2000-character limit.

Building templates from a real event

Once a source has received at least one webhook, open its strip in the UI: under the template you'll find "Payload keys from the last received event" — every key/value pair from that event as a chip. Drag chips into the template (or click them) to insert {path} placeholders at the cursor; hover a chip to preview its value. Hit ↻ refresh after new events arrive. Blue chips are values that look like images.

Embedding images

Set a source's Image — payload path to the field holding the picture (drag a blue chip into it), and pick a delivery mode:

  • fetch & upload (default) — Dis2Hook downloads the URL itself, or decodes base64 / data: image data, and uploads it to Discord as an attachment. Use this for LAN URLs (e.g. http://192.168.51.234:8000/...) and base64 fields — Discord's servers can never reach your LAN directly.
  • embed link — Discord fetches the URL itself; only works for URLs reachable from the public internet.

Example for a Bambu Lab print notifier: template 🖨️ **{printer}** — {title}\n\{filename}` after {duration}with image path image(base64 snapshot) orfinish_photo_url` (LAN URL) in fetch & upload mode.

Updating

Pull the latest app.py and index.html directly from the repo and restart:

/opt/dis2hook/update.sh          # app.py + index.html
/opt/dis2hook/update.sh --all    # also refresh update.sh and the service script

Downloads are validated (version marker + compile check) before they replace anything, previous files are kept as *.bak, and token.json and config.json are never touched.

Files

Path Purpose In repo?
/opt/dis2hook/app.py relay service (Flask) yes
/opt/dis2hook/index.html web UI yes
/opt/dis2hook/update.sh updater yes
/etc/init.d/dis2hook OpenRC service (dis2hook.initd) yes
/opt/dis2hook/token.json bot token + admin key — generated at install, mode 600 never
/opt/dis2hook/config.json sources & settings, managed by the web UI never
/var/log/dis2hook.log service log

Service management

rc-service dis2hook status|start|stop|restart
tail -f /var/log/dis2hook.log

Security notes

  • token.json is generated locally, chmod 600, and listed in .gitignore; treat the admin key like a password — it is the only credential for the UI/API.
  • Every source has its own shared secret, compared in constant time; requests with a wrong or missing secret are rejected with 401.
  • Dis2Hook serves plain HTTP. Keep it on a trusted LAN/VLAN, or front it with a reverse proxy (Caddy, nginx, Nginx Proxy Manager) for TLS if sources send webhooks across the internet.
  • Relayed messages are sent with all mentions disabled, so a hostile payload cannot ping @everyone.
  • The UI cannot change the listen host/port — edit config.json and restart if you need to, so a bad save can never lock you out.

How it compares to Discohook

Discohook is a superb editor for Discord's incoming webhooks — you compose a message and Discord hosts the endpoint. Dis2Hook is the opposite direction: it hosts the endpoint, accepts webhooks from your own services, filters and reformats them, and delivers via a bot token — so one self-hosted relay fans out many sources to many servers and channels, with heartbeat and uptime status built in.

Uninstall

rc-service dis2hook stop; rc-update del dis2hook default
rm -rf /opt/dis2hook /etc/init.d/dis2hook /var/log/dis2hook.log