From 3130f5ec36433e511a4432d755f406b60aa47eca Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 3 Oct 2026 11:16:54 +0700 Subject: [PATCH] docs: describe the bundled renderer service --- .env.example | 11 ++--- AGENTS.md | 5 +++ README.md | 5 ++- docs/deploy-coolify-selfhosted.md | 41 +++++++++---------- .../modules/random/wheelofnames_api_client.go | 6 +-- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index d989771..dc090e1 100644 --- a/.env.example +++ b/.env.example @@ -38,12 +38,13 @@ STICKER_PACK_NAME=miti99_by_miti99bot # Without it every /lol* fetch fails; the stale cache covers ≤60 min. LOL_PANDASCORE_TOKEN= -# Optional /wheelofnames GIF and /gacha MP4 renderer. Standard deployment: -# https://github.com/tiennm99/wheelofnames. Set the full /api/gif endpoint; -# /gacha calls /api/gacha on the same service. Leave blank to fall back to text -# selection. +# Optional /wheelofnames GIF and /gacha MP4 renderer: the renderer/ service in +# this repository. compose.yml fixes this to http://renderer:3000/api/gif, so +# set it only when running the bot outside compose. /gacha calls /api/gacha on +# the same service. Leave blank to fall back to text selection. 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= # ====================== Leave UNSET on self-host ================== diff --git a/AGENTS.md b/AGENTS.md index fc993b3..c5372d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,11 @@ port of `tiennm99/mttools/monkeyd-crawler` under `internal/modules/monkeyd/{crawler,pdf,export}`. Change them here; they no 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 - Keep changes scoped to the requested module or shared contract. diff --git a/README.md b/README.md index 91ec9f8..1b1fdf9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Atlas via long polling and an in-process cron scheduler. |---|---| | `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 | -| `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 | | `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) | @@ -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/systemstate/ shared `system` collection helper for startup migration records 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 diff --git a/docs/deploy-coolify-selfhosted.md b/docs/deploy-coolify-selfhosted.md index 082d77f..ad67a8d 100644 --- a/docs/deploy-coolify-selfhosted.md +++ b/docs/deploy-coolify-selfhosted.md @@ -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 | | `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) | -| `WHEELOFNAMES_API_URL` | optional | full `/api/gif` endpoint for remote `/wheelofnames` GIF rendering | -| `WHEELOFNAMES_API_TOKEN` | optional | bearer token matching the wheelofnames service `API_TOKEN` | +| `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` | ✅ 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 | | `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` | @@ -51,31 +51,30 @@ has no webhook. > 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. -### Optional wheelofnames renderer +### Animation renderer -`/wheelofnames` uses a remote GIF renderer when `WHEELOFNAMES_API_URL` is set. -The standard renderer is a deployment of -[`tiennm99/wheelofnames`](https://github.com/tiennm99/wheelofnames), but any -service that implements the same `/api/gif` contract can be used. Set the URL -to the full GIF endpoint and set the token to the same value as the service -`API_TOKEN`: +`/wheelofnames`, `/gacha`, and `/genshin` are drawn by the Node renderer in +[`renderer/`](../renderer/README.md) (Remotion and headless Chrome). +`compose.yml` deploys it as a second service, `renderer`, next to the bot. It +is internal only: the bot reaches it at `http://renderer:3000/api/gif` over the +compose network, so it needs no domain and publishes no port. The only setting +is the shared token: ```env -WHEELOFNAMES_API_URL=http://wheelofnames:3000/api/gif -WHEELOFNAMES_API_TOKEN= +WHEELOFNAMES_API_TOKEN= ``` -Use a public HTTPS URL instead when the bot cannot reach the service on a -private Coolify/Docker network: +Renderer tuning (`MAX_CONCURRENT_RENDERS`, `RENDER_TIMEOUT_MS`, `MAX_OPTIONS`, +`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 -WHEELOFNAMES_API_URL=https://wheelofnames.example.com/api/gif -WHEELOFNAMES_API_TOKEN= -``` +Outside compose, `WHEELOFNAMES_API_URL` can point the bot at any service that +implements the same `/api/gif` contract. 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 -service is unset, unavailable, unauthorized, or returns a non-GIF response, +renders use `512px`, `20fps`, and `7` seconds total by default. If the renderer +is unset, unavailable, unauthorized, or returns a non-GIF response, `/wheelofnames` falls back to the same plain text winner reply as `/random`. Successful GIF replies include the result behind Telegram spoiler formatting. @@ -146,8 +145,8 @@ MP4, with the same text fallback. ## 2. Coolify 1. New resource → from this Git repo (Docker Compose), or a prebuilt image. - The committed [`compose.yml`](../compose.yml) defines the single - `bot` service. + The committed [`compose.yml`](../compose.yml) defines the `bot` service + and the internal `renderer` service. 2. Set the env vars above in Coolify. 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 diff --git a/internal/modules/random/wheelofnames_api_client.go b/internal/modules/random/wheelofnames_api_client.go index c1c6c90..fe2780b 100644 --- a/internal/modules/random/wheelofnames_api_client.go +++ b/internal/modules/random/wheelofnames_api_client.go @@ -16,9 +16,9 @@ import ( ) const ( - // The standard renderer is a deployment of - // https://github.com/tiennm99/wheelofnames. Operators may point this to any - // service that implements the same /api/gif contract. + // The standard renderer is the renderer/ service in this repository, wired + // in by compose.yml. Operators may point this to any service that + // implements the same /api/gif contract. wheelOfNamesAPIURLEnv = "WHEELOFNAMES_API_URL" wheelOfNamesAPITokenEnv = "WHEELOFNAMES_API_TOKEN"