Files
tiennm99 7008a57cf5 refactor!: rebrand miti99bot to tiennm99bot
BREAKING CHANGE: the Go module path is now github.com/tiennm99/tiennm99bot,
the default sticker pack is stickers_by_<bot username>, and the health body,
deploy DM, User-Agents and image names say tiennm99bot.
2026-10-07 15:14:02 +07:00

188 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# renderer
Part of tiennm99bot: the service behind `/wheelofnames`, `/gacha`, and
`/genshin`, deployed by the root `compose.yml` as the `renderer` service.
Self-hosted API that renders wheel-of-names GIF animations and Genshin-style
meteor wish MP4 animations with Remotion, and card-pack gacha wish MP4
animations with pack-cards.
## API
```http
POST /api/gif
Content-Type: application/json
Accept: image/gif
```
```json
{
"options": ["alice", "bob", "carol"],
"winnerIndex": 1,
"durationMs": 6500,
"holdMs": 1200,
"fps": 15,
"size": 512,
"theme": "classic"
}
```
Response is `image/gif` with winner metadata headers:
- `X-Wheel-Winner-Index`
- `X-Wheel-Winner`
- `X-Render-Duration-Ms`
`X-Wheel-Winner` is URL-encoded so non-ASCII labels are safe in HTTP headers.
### Gacha wish
```http
POST /api/gacha
Content-Type: application/json
Accept: video/mp4
```
```json
{
"label": "Bún bò",
"rarity": 5,
"fps": 24,
"width": 640
}
```
Renders a 6-second portrait wish: a collectible card pack from
[pack-cards](https://github.com/paubineau/pack-cards) is torn open, its card
spins out with a burst of sparkles, and lands showing the request's `label`,
its rank, and its stars. `rarity` (required) picks the rank (`B` for 3★, `A`
for 4★, `S` for 5★), the star count, the card's material (rare, epic,
legendary), and the colour (blue, purple, gold). 4★ and 5★ cards are engraved
with pack-cards' rarity textures (contour on 4★; rings or facets on 5★, chosen
by the label) drawn in a still polychrome rainbow; 3★ cards are plain.
`fps` is `24` or `30`. `width` is the long edge of the portrait frame:
`640` renders 360×640 and `854` renders 480×854. Optional `seed` (integer,
`0` to `2147483647`) seeds the page's randomness; when omitted the service
picks a random one, so every roll looks different. The caller chooses the
result and its rarity — the service only draws it.
Response is a silent H.264 `video/mp4` (Telegram plays it as an animation)
with `X-Gacha-Rarity` and `X-Render-Duration-Ms` headers. All routes share the
`RENDERER_MAX_CONCURRENT_RENDERS` slots. No game assets are used.
pack-cards animates on the browser clock, so the wish does not use Remotion
compositions. `src/render/render-gacha.js` keeps one headless Chrome per
server process in `--deterministic-mode`, steps virtual time one frame at a
time, tears the pack with a scripted drag, captures each frame, and encodes
them with Remotion's bundled ffmpeg. The page (`src/gacha/page/`) and the
package are served from disk; the page has no network access. The first render
compiles the pack's WebGL shaders in software and the first card of each
engraving rasterises its texture, which takes several seconds, so server
start-up renders a throwaway 5★ and 4★ wish first.
pack-cards has no npm release, so it is installed from a GitHub tarball pinned
to a commit. A moving branch URL would change the tarball's checksum and break
`npm ci` against the lockfile, and the Docker image has no `git` for a git
dependency.
### Genshin wish
`POST /api/genshin` takes the same body as `/api/gacha` and returns the same
response, rendering a 7-second landscape wish in the style of a gacha game: a
meteor coloured by rarity (blue 3★, purple 4★, gold 5★) flies in from the left
across a night sky and bursts in a white flash where the rank emblem appears,
and the label is revealed beside a rank emblem (`B`, `A`, `S`) with its stars
popping in. Each tier is louder than the one below: 4★ adds a bigger meteor, a
lens flare, impact shake, and a double shockwave; 5★ adds a rainbow sunburst
before landing, a gold sky flood, a starburst, counter rotating rays, falling
sparkles, and a sheen across the emblem. The per-tier table lives in
`src/remotion/genshin-timeline.js`. `width` is `640` (360 tall) or `854` (480
tall), and `seed` lays out the twinkling stars and particles. All visuals are
drawn procedurally; no game assets are used.
## Local
Install dependencies and Chromium once:
```sh
npm install
npm run browser:ensure
```
Start the local API:
```sh
npm run dev
```
### Generate GIF files locally
Generate the quick smoke fixtures at the git-ignored paths
`fixtures/smoke.gif`, `fixtures/genshin-5-star.mp4`, and `fixtures/gacha-5-star.mp4`:
```sh
npm run render:smoke
```
Generate the complete fixture set at `fixtures/smoke.gif`,
`fixtures/vietnamese.gif`, `fixtures/sixteen-options.gif`, and
`fixtures/genshin-{3,4,5}-star.mp4`, and `fixtures/gacha-{3,4,5}-star.mp4`:
```sh
npm run render:fixtures
```
Render a custom GIF directly without starting the API server:
```powershell
npm run render:local -- `
--output wheel.gif `
--option "Chiều nay uống CraneTea" `
--option "Chiều nay uống CraneTea" `
--option "Cà phê" `
--winner 1
```
macOS, Linux, or Git Bash:
```sh
npm run render:local -- \
--output wheel.gif \
--option "Chiều nay uống CraneTea" \
--option "Chiều nay uống CraneTea" \
--option "Cà phê" \
--winner 1
```
`--winner` is a zero-based index and is random when omitted. Run
`npm run render:local -- --help` for duration, hold, FPS, size, theme, and timeout
options. The documented root `wheel.gif` and `fixtures/*.gif`/`*.mp4` outputs are
git-ignored and safe to delete; custom output paths may need their own ignore
rule.
### Verify
Run the API smoke test and quality gates:
```sh
npm run api:smoke
npm run lint
npm run typecheck
npm test
```
## Deploy
Use a container runtime first. Static-only platforms cannot satisfy
`POST /api/gif` because Remotion server rendering needs Node, Chromium/runtime
dependencies, and FFmpeg/compositor support.
```sh
docker build -t tiennm99bot-renderer .
docker run --rm -p 3000:3000 tiennm99bot-renderer
```
Recommended starting resources: 1-2 vCPU and 1-2 GB RAM, with
`RENDERER_MAX_CONCURRENT_RENDERS=1`. `RENDERER_RENDER_TIMEOUT_MS` defaults to
`30000` and is raised to Remotion's `7000ms` browser timeout floor when configured lower.
The API has no authentication; publish its port only on a trusted network.