EN /HU | Login

Discord Updater Bot

The Updater is a small Go tool that watches the Grav wiki, Redmine and Gitea for changes and posts new activity to Discord webhooks. A local cache makes sure every change is announced only once.

How it works

Every INTERVAL_MINUTES minutes the updater walks through its sources:

  1. Fetch — asks each source for its latest *_CONTENT_LIMIT items.
  2. Compare — checks each item's change signal against cache.json.
  3. Notify — sends whatever is new or changed as a Markdown link to that source's webhook.
  4. Save the cache — writes cache.json back at the end of the round so the next one does not repeat it.
Source Request Change signal Message
Grav wiki GET /custom/updates.json?limit=N the page's modified timestamp 📖 [Grav] - Page title
Redmine GET /issues.json?limit=N&sort=updated_on:desc the issue's updated_on 🎫 [Redmine] - #123 Subject
Gitea GET /api/v1/repos/ORG/REPO/commits?limit=N per repo the commit SHA 📝 [Gitea] - (org/repo) commit message

The Grav source

The wiki lists its pages at /custom/updates.json, newest file modification first (template: themes/ttgwiki/templates/updates.json.twig in the wiki-pages repo). The updater only reads route, title and modified; the link is GRAV_BASE_URL + route.

JSON
{"lang":"en","count":1,"pages":[
  {"route":"/team/onboarding","title":"Onboarding","modified":1792479011,
   "updatedAt":"2026-10-20T06:50:11+00:00","locale":"en","tags":["team"],"private":true}
]}
  • The cache key is grav_ + route, so every save of a page (a new modified) produces a new message.
  • Login-only pages (private: true) are listed with their titles too; the endpoint does not return their bodies, and the updater does not ask for them.
  • It requests the unprefixed route, so it posts English titles and URLs.

Configuration

Environment variables; copy env-example to .env. Every source runs in every round — one without a URL quietly returns an empty list.

General

Variable Description Default
INTERVAL_MINUTES Time between two rounds (minutes). 5
DISCORD_WEBHOOK Fallback webhook; every source has its own now, so nothing uses it. -
DISCORD_FAKE If true, messages only go to the log, not to Discord. false

Grav wiki

Variable Description
GRAV_BASE_URL The wiki base URL, without a trailing slash: https://wiki.teletypegames.org.
GRAV_DISCORD_WEBHOOK Discord webhook for wiki notifications.
GRAV_CONTENT_LIMIT How many recently modified pages to check.

The Grav endpoint needs no token.

Redmine

Variable Description
REDMINE_BASE_URL The Redmine base URL (https://pm.teletypegames.org).
REDMINE_KEY Redmine API key (X-Redmine-API-Key).
REDMINE_DISCORD_WEBHOOK Discord webhook for Redmine notifications.
REDMINE_CONTENT_LIMIT How many recently updated issues to check.

Gitea

Variable Description
GITEA_BASE_URL The Gitea base URL (https://git.teletypegames.org).
GITEA_TOKEN Gitea API token.
GITEA_REPOS Comma-separated repos to watch, e.g. games/mranderson,games/impostor. Only commits are watched, not issues or PRs.
GITEA_DISCORD_WEBHOOK Discord webhook for Gitea notifications.
GITEA_CONTENT_LIMIT How many recent commits to check per repo.

Running

Set up .env first, then either:

BASH
go run main.go          # locally
docker compose up -d    # in a container

The container mounts the repo folder as /app and runs go run main.go on start. cache.json lives in the same folder and survives restarts. The container runs on the interstack network with a 384m memory limit.

In production: /srv/stacks/updater on the server, logs via docker logs updater-app. A push to master that changes the Go sources, the Dockerfile or compose.yaml deploys it from Woodpecker (ci-deploy updater, see Deployment) after go vet and a build; .env and cache.json stay on the server.

Files

File Contents
main.go Entry point, calls lib.Runner().
lib/runner.go Reads the config, builds the source and webhook list, runs the main loop.
lib/fetcher.grav.go, lib/fetcher.redmine.go, lib/fetcher.gitea.go The three live sources.
lib/fetcher.go Shared HTTP request and cache check.
lib/sender.discord.go, lib/message.go The Discord webhook and the message format.
lib/cache.go Loads and saves cache.json.
env-example Configuration template.

Repo

https://git.teletypegames.org/services/updater