TITLE=Discord Updater Bot ROUTE=/services/updater LOCALE=en MODIFIED=1792749862 URL=https://wiki.teletypegames.org/services/updater L=1 https://git.teletypegames.org/services/wiki-pages L=2 #31 q│ The Updater is a small Go tool that watches the Grav wiki, Redmine and Gitea q│ for changes and posts new activity to Discord webhooks. A local cache makes q│ sure every change is announced only once. 1How it works 1════════════ Every cINTERVAL_MINUTES0 minutes the updater walks through its sources: 1. bFetch0 — asks each source for its latest c*_CONTENT_LIMIT0 items. 2. bCompare0 — checks each item's change signal against ccache.json0. 3. bNotify0 — sends whatever is new or changed as a Markdown link to that source's webhook. 4. bSave the cache0 — writes ccache.json0 back at the end of the round so the next one does not repeat it. t┌───────────┬────────────────────┬─────────────────────┬─────────────────────┐ t│h Source t│h Request t│h Change signal t│h Message t│ t├───────────┼────────────────────┼─────────────────────┼─────────────────────┤ t│0 Grav wiki t│0 cGET0 t│0 the page's cmodified0 t│0 c📖 [Grav] - Page0 t│ t│0 t│0 c/custom/updates.0 t│0 timestamp t│0 ctitle0 t│ t│0 t│0 cjson?limit=N0 t│0 t│0 t│ t├───────────┼────────────────────┼─────────────────────┼─────────────────────┤ t│0 Redmine t│0 cGET0 t│0 the issue's t│0 c🎫 [Redmine] - #1230 t│ t│0 t│0 c/issues.0 t│0 cupdated_on0 t│0 cSubject0 t│ t│0 t│0 cjson?limit=N&sort=0 t│0 t│0 t│ t│0 t│0 cupdated_on:desc0 t│0 t│0 t│ t├───────────┼────────────────────┼─────────────────────┼─────────────────────┤ t│0 Gitea t│0 cGET0 t│0 the commit SHA t│0 c📝 [Gitea] -0 t│ t│0 t│0 c/api/v1/repos/ORG/0 t│0 t│0 c(org/repo) commit0 t│ t│0 t│0 cREPO/0 t│0 t│0 cmessage0 t│ t│0 t│0 ccommits?limit=N0 t│0 t│0 t│ t│0 t│0 per repo t│0 t│0 t│ t└───────────┴────────────────────┴─────────────────────┴─────────────────────┘ 2The Grav source 2─────────────── The wiki lists its pages at c/custom/updates.json0, newest file modification first (template: cthemes/ttgwiki/templates/updates.json.twig0 in the 1wiki-pages repo). The updater only reads croute0, ctitle0 and cmodified0; the link is cGRAV_BASE_URL0 + croute0. p {"lang":"en","count":1,"pages":[ p {"route":"/team/onboarding","title":"Onboarding","modified":1792479011, p "updatedAt":"2026-10-20T06:50:11+00:00","locale":"en","tags":["team"],"pr» p ivate":true} p ]} • The cache key is cgrav_0 + route, so every save of a page (a new cmodified0) produces a new message. • Login-only pages (cprivate: true0) 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. 1Configuration 1═════════════ Environment variables; copy cenv-example0 to c.env0. Every source runs in every round — one without a URL quietly returns an empty list. 2General 2─────── t┌──────────────────┬───────────────────────────────────────────────┬─────────┐ t│h Variable t│h Description t│h Default t│ t├──────────────────┼───────────────────────────────────────────────┼─────────┤ t│0 cINTERVAL_MINUTES0 t│0 Time between two rounds (minutes). t│0 c50 t│ t├──────────────────┼───────────────────────────────────────────────┼─────────┤ t│0 cDISCORD_WEBHOOK0 t│0 Fallback webhook; every source has its own t│0 - t│ t│0 t│0 now, so nothing uses it. t│0 t│ t├──────────────────┼───────────────────────────────────────────────┼─────────┤ t│0 cDISCORD_FAKE0 t│0 If ctrue0, messages only go to the log, not to t│0 cfalse0 t│ t│0 t│0 Discord. t│0 t│ t└──────────────────┴───────────────────────────────────────────────┴─────────┘ 2Grav wiki 2───────── t┌──────────────────────┬─────────────────────────────────────────────────────┐ t│h Variable t│h Description t│ t├──────────────────────┼─────────────────────────────────────────────────────┤ t│0 cGRAV_BASE_URL0 t│0 The wiki base URL, bwithout a trailing slash0: t│ t│0 t│0 chttps://wiki.teletypegames.org0. t│ t├──────────────────────┼─────────────────────────────────────────────────────┤ t│0 cGRAV_DISCORD_WEBHOOK0 t│0 Discord webhook for wiki notifications. t│ t├──────────────────────┼─────────────────────────────────────────────────────┤ t│0 cGRAV_CONTENT_LIMIT0 t│0 How many recently modified pages to check. t│ t└──────────────────────┴─────────────────────────────────────────────────────┘ The Grav endpoint needs no token. 2Redmine 2─────── t┌─────────────────────────┬──────────────────────────────────────────────────┐ t│h Variable t│h Description t│ t├─────────────────────────┼──────────────────────────────────────────────────┤ t│0 cREDMINE_BASE_URL0 t│0 The Redmine base URL t│ t│0 t│0 (chttps://pm.teletypegames.org0). t│ t├─────────────────────────┼──────────────────────────────────────────────────┤ t│0 cREDMINE_KEY0 t│0 Redmine API key (cX-Redmine-API-Key0). t│ t├─────────────────────────┼──────────────────────────────────────────────────┤ t│0 cREDMINE_DISCORD_WEBHOOK0 t│0 Discord webhook for Redmine notifications. t│ t├─────────────────────────┼──────────────────────────────────────────────────┤ t│0 cREDMINE_CONTENT_LIMIT0 t│0 How many recently updated issues to check. t│ t└─────────────────────────┴──────────────────────────────────────────────────┘ 2Gitea 2───── t┌───────────────────────┬────────────────────────────────────────────────────┐ t│h Variable t│h Description t│ t├───────────────────────┼────────────────────────────────────────────────────┤ t│0 cGITEA_BASE_URL0 t│0 The Gitea base URL t│ t│0 t│0 (chttps://git.teletypegames.org0). t│ t├───────────────────────┼────────────────────────────────────────────────────┤ t│0 cGITEA_TOKEN0 t│0 Gitea API token. t│ t├───────────────────────┼────────────────────────────────────────────────────┤ t│0 cGITEA_REPOS0 t│0 Comma-separated repos to watch, e.g. t│ t│0 t│0 cgames/mranderson,games/impostor0. Only commits are t│ t│0 t│0 watched, not issues or PRs. t│ t├───────────────────────┼────────────────────────────────────────────────────┤ t│0 cGITEA_DISCORD_WEBHOOK0 t│0 Discord webhook for Gitea notifications. t│ t├───────────────────────┼────────────────────────────────────────────────────┤ t│0 cGITEA_CONTENT_LIMIT0 t│0 How many recent commits to check per repo. t│ t└───────────────────────┴────────────────────────────────────────────────────┘ 1Running 1═══════ Set up c.env0 first, then either: p go run main.go # locally p docker compose up -d # in a container The container mounts the repo folder as c/app0 and runs cgo run main.go0 on start. ccache.json0 lives in the same folder and survives restarts. The container runs on the cinterstack0 network with a c384m0 memory limit. In production: c/srv/stacks/updater0 on the server, logs via cdocker logs cupdater-app0. A push to master that changes the Go sources, the cDockerfile0 or ccompose.yaml0 deploys it from Woodpecker (cci-deploy updater0, see 2Deployment) after cgo vet0 and a build; c.env0 and ccache.json0 stay on the server. 1Files 1═════ t┌─────────────────────────────────────┬──────────────────────────────────────┐ t│h File t│h Contents t│ t├─────────────────────────────────────┼──────────────────────────────────────┤ t│0 cmain.go0 t│0 Entry point, calls clib.Runner()0. t│ t├─────────────────────────────────────┼──────────────────────────────────────┤ t│0 clib/runner.go0 t│0 Reads the config, builds the source t│ t│0 t│0 and webhook list, runs the main t│ t│0 t│0 loop. t│ t├─────────────────────────────────────┼──────────────────────────────────────┤ t│0 clib/fetcher.grav.go0, t│0 The three live sources. t│ t│0 clib/fetcher.redmine.go0, t│0 t│ t│0 clib/fetcher.gitea.go0 t│0 t│ t├─────────────────────────────────────┼──────────────────────────────────────┤ t│0 clib/fetcher.go0 t│0 Shared HTTP request and cache check. t│ t├─────────────────────────────────────┼──────────────────────────────────────┤ t│0 clib/sender.discord.go0, t│0 The Discord webhook and the message t│ t│0 clib/message.go0 t│0 format. t│ t├─────────────────────────────────────┼──────────────────────────────────────┤ t│0 clib/cache.go0 t│0 Loads and saves ccache.json0. t│ t├─────────────────────────────────────┼──────────────────────────────────────┤ t│0 cenv-example0 t│0 Configuration template. t│ t└─────────────────────────────────────┴──────────────────────────────────────┘ 1Repo 1════ https://git.teletypegames.org/services/updater