docs: describe the bundled renderer service

This commit is contained in:
tiennm99 committed 2026-10-03 11:16:54 +07:00
1 parent 2bb5108151
commit 3130f5ec36
5 files changed
+37 -31

No files matched your search

+6 -5
View File
@@ -38,12 +38,13 @@ STICKER_PACK_NAME=miti99_by_miti99bot
# Without it every /lol* fetch fails; the stale cache covers ≤60 min. # Without it every /lol* fetch fails; the stale cache covers ≤60 min.
LOL_PANDASCORE_TOKEN= LOL_PANDASCORE_TOKEN=
# Optional /wheelofnames GIF and /gacha MP4 renderer. Standard deployment: # Optional /wheelofnames GIF and /gacha MP4 renderer: the renderer/ service in
# https://github.com/tiennm99/wheelofnames. Set the full /api/gif endpoint; # this repository. compose.yml fixes this to http://renderer:3000/api/gif, so
# /gacha calls /api/gacha on the same service. Leave blank to fall back to text # set it only when running the bot outside compose. /gacha calls /api/gacha on
# selection. # the same service. Leave blank to fall back to text selection.
WHEELOFNAMES_API_URL= WHEELOFNAMES_API_URL=
# Bearer token matching the wheelofnames service API_TOKEN when URL is set. # Bearer token shared with the renderer; compose passes it to the renderer as
# API_TOKEN. Required for the renderer to start.
WHEELOFNAMES_API_TOKEN= WHEELOFNAMES_API_TOKEN=
# ====================== Leave UNSET on self-host ================== # ====================== Leave UNSET on self-host ==================
+5
View File
@@ -12,6 +12,11 @@ port of `tiennm99/mttools/monkeyd-crawler` under
`internal/modules/monkeyd/{crawler,pdf,export}`. Change them here; they no `internal/modules/monkeyd/{crawler,pdf,export}`. Change them here; they no
longer track the mttools copy. longer track the mttools copy.
`renderer/` is a separate Node 24 service (JavaScript + JSDoc, Remotion) that
draws the `random` module's animations. It has its own `package.json`, tests,
and `renderer/AGENTS.md`; run its npm commands from that folder. `compose.yml`
deploys it next to the bot.
## Development Rules ## Development Rules
- Keep changes scoped to the requested module or shared contract. - Keep changes scoped to the requested module or shared contract.
+3 -2
View File
@@ -9,7 +9,7 @@ Atlas via long polling and an in-process cron scheduler.
|---|---| |---|---|
| `util` | `/help`, `/info` (admin), `/stickerid` (owner) | | `util` | `/help`, `/info` (admin), `/stickerid` (owner) |
| `misc` | `/ping`, `/ping_stats` (admin), `/ff` (admin), `/xlt1`, `/giaxang` (Petrolimex retail fuel prices), `/the_answer` (owner), `/trongtruonghop` + `/tth`, `/trongtruonghopvng` + `/tthvng` disclaimers | | `misc` | `/ping`, `/ping_stats` (admin), `/ff` (admin), `/xlt1`, `/giaxang` (Petrolimex retail fuel prices), `/the_answer` (owner), `/trongtruonghop` + `/tth`, `/trongtruonghopvng` + `/tthvng` disclaimers |
| `random` | Pick one comma-separated option at random: `/random` (text), `/wheelofnames` (wheel GIF), `/gacha` (card-pack wish MP4; options are 5★ by default, prefix `4*` or `3*`), `/genshin` (unlisted; the same wish as a Genshin-style meteor). The animations use the optional wheelofnames renderer | | `random` | Pick one comma-separated option at random: `/random` (text), `/wheelofnames` (wheel GIF), `/gacha` (card-pack wish MP4; options are 5★ by default, prefix `4*` or `3*`), `/genshin` (unlisted; the same wish as a Genshin-style meteor). The animations use the bundled [renderer](renderer/README.md) service |
| `amlich` | Vietnamese lunar calendar: `/amlich` (dương lịch → âm lịch, defaults to today), `/duonglich` (âm lịch → dương lịch, `nhuan` flag for leap months); dates accept `d`, `d/m`, or `d/m/yyyy` — missing parts fill from today in the input's calendar. Years 1800–2199 only | | `amlich` | Vietnamese lunar calendar: `/amlich` (dương lịch → âm lịch, defaults to today), `/duonglich` (âm lịch → dương lịch, `nhuan` flag for leap months); dates accept `d`, `d/m`, or `d/m/yyyy` — missing parts fill from today in the input's calendar. Years 1800–2199 only |
| `wordle` | Daily Wordle game: `/wordle [word]`, `/wordle_new`, `/wordle_giveup`, `/wordle_stats` | | `wordle` | Daily Wordle game: `/wordle [word]`, `/wordle_new`, `/wordle_giveup`, `/wordle_stats` |
| `loldle` | League-of-Legends "guess the champion": `/loldle [champion]`, `/loldle_giveup`, `/loldle_stats`, `/loldle_setmax` (owner) | | `loldle` | League-of-Legends "guess the champion": `/loldle [champion]`, `/loldle_giveup`, `/loldle_stats`, `/loldle_setmax` (owner) |
@@ -240,7 +240,8 @@ internal/modules/ Module framework, registry, dispatchers, modules
internal/storage/ typed DocStore[T] (Provider + Typed); mongodb runtime + memory (tests). Values persist as flattened native BSON root documents internal/storage/ typed DocStore[T] (Provider + Typed); mongodb runtime + memory (tests). Values persist as flattened native BSON root documents
internal/systemstate/ shared `system` collection helper for startup migration records internal/systemstate/ shared `system` collection helper for startup migration records
internal/log/, metrics/ JSON logging (LOG_LEVEL) and periodic metrics flush internal/log/, metrics/ JSON logging (LOG_LEVEL) and periodic metrics flush
compose.yml Coolify self-host stack (single bot service) renderer/ Node animation renderer for /wheelofnames, /gacha, /genshin
compose.yml Coolify self-host stack (bot + renderer services)
``` ```
## Documentation ## Documentation
+20 -21
View File
@@ -36,8 +36,8 @@ Copy [`.env.example`](../.env.example) → `.env` (gitignored) and fill in.
| `ADMIN_IDS` | optional | CSV of Telegram user ids for admin-only commands | | `ADMIN_IDS` | optional | CSV of Telegram user ids for admin-only commands |
| `STICKER_PACK_NAME` | optional | set `/addsticker` writes to; default `miti99_by_miti99bot`. See [sticker packs](sticker-packs.md) | | `STICKER_PACK_NAME` | optional | set `/addsticker` writes to; default `miti99_by_miti99bot`. See [sticker packs](sticker-packs.md) |
| `LOL_PANDASCORE_TOKEN` | ✅ for lol module | PandaScore API token (free tier) — secret, never logged; without it every `/lol*` fetch fails (stale cache may still serve briefly) | | `LOL_PANDASCORE_TOKEN` | ✅ for lol module | PandaScore API token (free tier) — secret, never logged; without it every `/lol*` fetch fails (stale cache may still serve briefly) |
| `WHEELOFNAMES_API_URL` | optional | full `/api/gif` endpoint for remote `/wheelofnames` GIF rendering | | `WHEELOFNAMES_API_URL` | leave unset | fixed by `compose.yml` to the bundled renderer (`http://renderer:3000/api/gif`); a Coolify value is ignored |
| `WHEELOFNAMES_API_TOKEN` | optional | bearer token matching the wheelofnames service `API_TOKEN` | | `WHEELOFNAMES_API_TOKEN` | ✅ for animations | bearer token shared by the bot and the bundled renderer (its `API_TOKEN`) — secret; unset = the renderer refuses to start and the animated commands reply with text |
| `LOG_LEVEL` | optional | `debug`, `info` (default), `warn`, or `error`; logs are JSON on stdout | | `LOG_LEVEL` | optional | `debug`, `info` (default), `warn`, or `error`; logs are JSON on stdout |
| `GOLD_VNAPP_API_KEY` | leave unset | VNAppMob key; unset = the gold module fetches one and caches it in MongoDB | | `GOLD_VNAPP_API_KEY` | leave unset | VNAppMob key; unset = the gold module fetches one and caches it in MongoDB |
| `KV_PROVIDER` | leave unset | `memory` or `mongodb`; unset = `mongodb` when `MONGO_URL` is set, otherwise `memory` | | `KV_PROVIDER` | leave unset | `memory` or `mongodb`; unset = `mongodb` when `MONGO_URL` is set, otherwise `memory` |
@@ -51,31 +51,30 @@ has no webhook.
> Cron runs in-process (`internal/cron`) — there is no `/cron` HTTP route and no > Cron runs in-process (`internal/cron`) — there is no `/cron` HTTP route and no
> `CRON_SHARED_SECRET`. The scheduler is the sole trigger; nothing inbound. > `CRON_SHARED_SECRET`. The scheduler is the sole trigger; nothing inbound.
### Optional wheelofnames renderer ### Animation renderer
`/wheelofnames` uses a remote GIF renderer when `WHEELOFNAMES_API_URL` is set. `/wheelofnames`, `/gacha`, and `/genshin` are drawn by the Node renderer in
The standard renderer is a deployment of [`renderer/`](../renderer/README.md) (Remotion and headless Chrome).
[`tiennm99/wheelofnames`](https://github.com/tiennm99/wheelofnames), but any `compose.yml` deploys it as a second service, `renderer`, next to the bot. It
service that implements the same `/api/gif` contract can be used. Set the URL is internal only: the bot reaches it at `http://renderer:3000/api/gif` over the
to the full GIF endpoint and set the token to the same value as the service compose network, so it needs no domain and publishes no port. The only setting
`API_TOKEN`: is the shared token:
```env ```env
WHEELOFNAMES_API_URL=http://wheelofnames:3000/api/gif WHEELOFNAMES_API_TOKEN=<random secret>
WHEELOFNAMES_API_TOKEN=<same value as wheelofnames API_TOKEN>
``` ```
Use a public HTTPS URL instead when the bot cannot reach the service on a Renderer tuning (`MAX_CONCURRENT_RENDERS`, `RENDER_TIMEOUT_MS`, `MAX_OPTIONS`,
private Coolify/Docker network: `MAX_OPTION_CHARS`) can be set in Coolify too; the defaults are in
[`renderer/docs/deployment.md`](../renderer/docs/deployment.md). Give the host
1-2 GB of headroom for the renderer's Chrome.
```env Outside compose, `WHEELOFNAMES_API_URL` can point the bot at any service that
WHEELOFNAMES_API_URL=https://wheelofnames.example.com/api/gif implements the same `/api/gif` contract.
WHEELOFNAMES_API_TOKEN=<same value as wheelofnames API_TOKEN>
```
The bot sends outbound HTTP only; no public bot ingress is required. Remote The bot sends outbound HTTP only; no public bot ingress is required. Remote
renders use `512px`, `20fps`, and `7` seconds total by default. If the remote renders use `512px`, `20fps`, and `7` seconds total by default. If the renderer
service is unset, unavailable, unauthorized, or returns a non-GIF response, is unset, unavailable, unauthorized, or returns a non-GIF response,
`/wheelofnames` falls back to the same plain text winner reply as `/random`. `/wheelofnames` falls back to the same plain text winner reply as `/random`.
Successful GIF replies include the result behind Telegram spoiler formatting. Successful GIF replies include the result behind Telegram spoiler formatting.
@@ -146,8 +145,8 @@ MP4, with the same text fallback.
## 2. Coolify ## 2. Coolify
1. New resource → from this Git repo (Docker Compose), or a prebuilt image. 1. New resource → from this Git repo (Docker Compose), or a prebuilt image.
The committed [`compose.yml`](../compose.yml) defines the single The committed [`compose.yml`](../compose.yml) defines the `bot` service
`bot` service. and the internal `renderer` service.
2. Set the env vars above in Coolify. 2. Set the env vars above in Coolify.
3. **No public domain / port** is needed — polling is outbound-only. Do not 3. **No public domain / port** is needed — polling is outbound-only. Do not
publish a port or attach a domain. `expose: 8080` keeps the health endpoint publish a port or attach a domain. `expose: 8080` keeps the health endpoint
@@ -16,9 +16,9 @@ import (
) )
const ( const (
// The standard renderer is a deployment of // The standard renderer is the renderer/ service in this repository, wired
// https://github.com/tiennm99/wheelofnames. Operators may point this to any // in by compose.yml. Operators may point this to any service that
// service that implements the same /api/gif contract. // implements the same /api/gif contract.
wheelOfNamesAPIURLEnv = "WHEELOFNAMES_API_URL" wheelOfNamesAPIURLEnv = "WHEELOFNAMES_API_URL"
wheelOfNamesAPITokenEnv = "WHEELOFNAMES_API_TOKEN" wheelOfNamesAPITokenEnv = "WHEELOFNAMES_API_TOKEN"