100 Commits
Author SHA1 Message Date
tiennm99 629b513c91 feat(games): move /wordle and /loldle to the web games and add /loldledaily
/wordle and /loldle now send the wordle and loldle BotFather games in an
unlimited mode: each player gets their own word or champion, saved after
every guess, with a New game button. Cards the bot did not record as
unlimited, including the daily cards, play the daily puzzle. The helper
commands keep working on the player's round.

/loldledaily mirrors /wordledaily: one champion per day at 07:00 ICT,
subscribe and unsubscribe, the morning recap and the live group results.

The daily and unlimited machinery is shared in util/guessgame, the old
in-chat wordle module is removed, and a one-time startup migration carries
each player's stats and unfinished round over without changing old data.
2026-10-10 09:29:48 +07:00
tiennm99 bc8a7d8869 feat(wordledaily): send the wordle BotFather game
Cards from /wordledaily and the 07:00 push now use the BotFather game
wordle, and Play answers it. The module, commands and page route keep the
wordledaily name.
2026-10-09 17:23:17 +07:00
tiennm99 c0231ceadf feat(wordledaily): add the daily Wordle HTML5 game
/wordledaily sends a BotFather game card whose page plays one shared word
per day, changing at 07:00 ICT. The server checks guesses, saves each
player's board after every guess and keeps NYT-style stats. A win is
reported with setGameScore, and in a group one live results message shows
everyone's colour grids.

/wordledaily_subscribe and /wordledaily_unsubscribe opt a chat or topic in
to the 07:00 push: yesterday's recap, opening with the answer and the
group's win streak, then today's card.

The HTML5 game plumbing that noitu used is now shared in util/htmlgame, and
the wordle word list and scoring live in wordle/wordlist.
2026-10-09 17:20:02 +07:00
tiennm99 871b4a1042 fix(noitu): drop unrelated htmlgame edits from the noitubot commit
The previous commit picked up an unfinished refactor of noitu.go that imports
internal/modules/util/htmlgame, which is not committed, so main did not build.
Keep only the noitubot game change.
2026-10-09 16:42:59 +07:00
tiennm99 f60b661444 feat(noitu): send the separate noitubot game from /noitubot
/noitubot now sends its own BotFather game, noitubot, so players can tell a
solo game from a /noitu room card. Both games share the Play handler and page.
A noitubot Play press skips the card lookup and always plays solo.
2026-10-09 16:42:31 +07:00
tiennm99 d84bc3d1fb feat(noitu): make /noitu the group game and add /noitutop
/noitu now posts the group play card that /noitupvp used to, and the solo
game against the bot moves to /noitubot. In a private chat /noitu points
the user to a group, or to /noitubot.

/noitutop ranks a group's players across all of its cards by wins, best
game and games played.

A one-time startup migration moves /stats history to the new names in
order: noitu to noitubot first, then noitupvp to noitu.
2026-10-09 16:22:54 +07:00
tiennm99 b8565164b3 feat(noitu): add /noitupvp group play
/noitupvp posts the noitu game card in a group and records it as a PvP
card. Everyone who presses Play on that card joins one room: the first
member hosts and starts, 2 to 8 players take 30-second turns, a player
whose turn runs out or who gives up is eliminated, and the last one
standing wins. Each player's points, plus a winner bonus, go to the
card's high-score table, and the bot replies to the card with the result.

The engine now shares one board between the solo game and the new
multiplayer match, so both use the same word rules and scoring.
2026-10-09 16:07:56 +07:00
tiennm99 231fc96b5f docs(groups): explain reading other bots' messages via admin and bot-to-bot mode 2026-10-09 14:38:17 +07:00
tiennm99 6f30d13b2d feat(noitu): add the noitu HTML5 Telegram game
Serve the BotFather game noitu from the bot's HTTP server under
/games/noitu when GAME_BASE_URL is set. /noitu sends the game, Play
answers with a signed, expiring link, and the server validates every
word against the embedded noitu dictionary (CC BY-SA 4.0), runs the
bot opponent and turn timer, and reports the score with setGameScore.

Modules can now register game-short-name callbacks and HTTP routes.
2026-10-09 14:32:57 +07:00
tiennm99 fe552f43e4 feat(menu): sort Telegram command suggestions by name
The command menu was in module load order, which split related commands across modules. Sorting by name keeps /thoitiet next to /thoitiethomnay. The weather module also lists /thoitiet first so /help matches.
2026-10-08 17:10:42 +07:00
tiennm99 9801a64e18 feat(weather): default to Tân Thuận and accept lat,long coordinates
Forecast commands without a location now use phường Tân Thuận (formerly Quận 7), shared with the flood view, instead of the Ho Chi Minh City centre; city aliases still resolve to the city. A decimal "lat,long" argument skips geocoding and is used directly.
2026-10-08 17:02:05 +07:00
tiennm99 512b9fc995 docs(env): drop renamed-from notes in .env.example 2026-10-08 10:35:21 +07:00
tiennm99 5e96490254 refactor: remove completed one-time startup migrations
Production data is migrated and every marker is recorded in the system
collection, so the stock dividend-history, sticker legacy-pack and stats
stock_dividend migrations no longer do anything. Stats keeps its startup
index creation and still hides retired stock_dividend rows.
2026-10-08 10:35:21 +07:00
tiennm99 5344111787 fix(dispatcher): ignore commands addressed to another bot
The matcher stripped any @suffix without checking it, so in groups where
the bot sees every message (privacy mode off, or admin rights) it answered
/cmd@otherbot as if it were /cmd. The suffix must now name this bot,
compared case-insensitively. When the username is unknown because startup
getMe failed, any suffix is still accepted so group commands keep working.
2026-10-07 16:58:47 +07:00
tiennm99 cc9080c72e chore: remove the plans directory
Finished plans, journals and reports stay in git history.
2026-10-07 16:40:24 +07:00
tiennm99 be46d4c19c docs(plans): record the tiennm99bot rebrand plan and cutover 2026-10-07 16:22:32 +07:00
tiennm99 f0ca083421 docs(loldle): note sticker file_ids carry across bot accounts 2026-10-07 16:22:31 +07:00
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
tiennm99 79104ef3d7 feat(config): resolve the bot username from BOT_USERNAME or getMe
The default sticker pack name and the lol User-Agent now follow whichever
bot runs the code, so a bot account change needs no code edit. The default
pack becomes miti99_by_<bot username>.
2026-10-07 15:08:08 +07:00
tiennm99 c18006fabc fix(renderer): raise the default render timeout to 30s
Under normal load on the shared host, a /wheelofnames GIF takes 11-12s
and a /gacha wish 14-15s to render, so the 15s default turned ordinary
renders into 504s. The default is now 30s, and the bot waits 45s so it
never abandons a render the renderer is still allowed to finish.
2026-10-03 13:06:04 +07:00
tiennm99 1c64b44a46 chore(deploy): comment out optional compose variables
Coolify passes every dashboard variable to each service through its
generated .env, so optional values need no compose reference. It also
creates a dashboard entry for every referenced variable and keeps an
existing empty entry over a ${VAR:-default}, which overrides the compose
default. Optional variables are therefore commented-out ${VAR:-default}
lines, and the code supplies their defaults.
2026-10-03 12:46:11 +07:00
tiennm99 e418bf3390 revert: restore code fallbacks for config defaults
This reverts commit 153fc21. With no fallback in code, the deploy
crash-looped: Coolify keeps an entry for every variable compose.yml has
referenced, and empty entries appear to reach the containers as empty
values instead of the compose defaults.
2026-10-03 12:42:16 +07:00
tiennm99 153fc211cf refactor!: move every config default into compose.yml
compose.yml now holds each default as ${VAR:-default}, and the code keeps
no fallback values. The bot stops at startup on a missing or invalid
PORT or LOG_LEVEL, /addsticker refuses without STICKER_PACK_NAME, and the
renderer refuses to start until every RENDERER_* setting is a valid
value, listing each problem. Settings whose empty value means none or
all (MODULES, OWNER_ID, ADMIN_IDS, the API tokens) use ${VAR:-}.

The renderer's npm start and dev load .env when present, so a local run
works from a copy of .env.example.

BREAKING CHANGE: running outside compose now requires LOG_LEVEL and PORT
for the bot, STICKER_PACK_NAME for /addsticker, and every RENDERER_*
variable for the renderer.
2026-10-03 12:38:39 +07:00
tiennm99 4f8c01dae7 chore(deploy): write compose env defaults as "default: value" 2026-10-03 12:26:41 +07:00
tiennm99 be7ad347bd chore(deploy): group compose env vars and list optional ones without defaults
Each service's environment is grouped into required, optional, and
stack-fixed values. Optional variables are referenced without a default,
so Coolify lists them as settings that can be filled in or left empty;
an empty value falls back to the code's default.
2026-10-03 12:24:16 +07:00
tiennm99 9457baf81a fix(deploy): pass LOL_PANDASCORE_TOKEN to the bot explicitly
The lol module reads the PandaScore token, but compose.yml never listed
it, so the bot depended on Coolify passing unlisted variables through. It
is optional: without it the lol commands fail and the rest of the bot
runs.
2026-10-03 12:19:56 +07:00
tiennm99 8c7a0ee313 chore(deploy): keep interpolation syntax out of compose comments 2026-10-03 12:11:38 +07:00
tiennm99 1ee679ad89 fix(deploy): stop exposing renderer tuning as Coolify settings
Coolify lists every ${VAR} that compose.yml references as an app setting,
so the four renderer tuning values showed up as entries to fill in. They
are no longer referenced: the renderer's own defaults apply, and an
override goes into compose.yml as a literal.
2026-10-03 12:11:25 +07:00
tiennm99 83321fa330 refactor(renderer)!: prefix every renderer env var with RENDERER_
The renderer's settings now read RENDERER_HOST, RENDERER_PORT,
RENDERER_MAX_CONCURRENT_RENDERS, RENDERER_RENDER_TIMEOUT_MS,
RENDERER_MAX_OPTIONS, and RENDERER_MAX_OPTION_CHARS, so they read as
renderer settings and cannot clash with the bot's variables, which
Coolify injects into every service. NODE_ENV keeps its standard name.

BREAKING CHANGE: the unprefixed HOST, PORT, MAX_CONCURRENT_RENDERS,
RENDER_TIMEOUT_MS, MAX_OPTIONS, and MAX_OPTION_CHARS are no longer read
by the renderer.
2026-10-03 12:05:56 +07:00
tiennm99 11ed121af9 fix(deploy): add a compose healthcheck for the bot
Coolify ignores its UI health-check settings for Docker Compose apps and
reads each service's healthcheck instead, so the bot reported an unknown
status. It now checks GET / with the image's busybox wget.
2026-10-03 12:01:30 +07:00
tiennm99 277c913f71 refactor(random)!: configure the renderer by base URL as RENDERER_URL
The bot now takes the renderer's base URL and appends each /api/<name>
route itself, instead of taking the full /api/gif endpoint and swapping
its last path segment for /gacha and /genshin. The client, its files, and
its errors are named after the renderer rather than wheelofnames; the
/wheelofnames command keeps its name.

BREAKING CHANGE: WHEELOFNAMES_API_URL is replaced by RENDERER_URL, which
holds the base URL (e.g. http://renderer:3000) rather than the /api/gif
endpoint. compose.yml sets it, so Coolify needs no value.
2026-10-03 11:52:50 +07:00
tiennm99 2ec4a2b371 refactor(renderer)!: drop the API token from the internal renderer
The renderer now runs only on the compose network with no published port
or domain, so a shared bearer token adds nothing. The renderer no longer
checks Authorization or requires API_TOKEN in production, and the bot no
longer sends a token.

BREAKING CHANGE: WHEELOFNAMES_API_TOKEN and the renderer's API_TOKEN are
removed. Never publish the renderer's port: its API is unauthenticated.
2026-10-03 11:45:26 +07:00
tiennm99 3130f5ec36 docs: describe the bundled renderer service 2026-10-03 11:16:54 +07:00
tiennm99 2bb5108151 feat(renderer): deploy the renderer next to the bot in compose
The bot now reaches the renderer at http://renderer:3000/api/gif on the
compose network instead of a separately deployed service. Both share
WHEELOFNAMES_API_TOKEN. CI runs the renderer's lint, typecheck, tests,
render smoke, and image build.
2026-10-03 11:16:54 +07:00
tiennm99 de46809c96 docs: describe the in-tree monkeyd crawler and record the port plan 2026-10-03 11:23:45 +07:00
tiennm99 d211124aae refactor(monkeyd): port the crawler in-tree and drop the submodule
The monkeyd-crawler repository moved into tiennm99/mttools, so recursive
clones of the old submodule URL fail and block every deploy. The crawler,
PDF renderer, and export flow now live under internal/modules/monkeyd,
trimmed to the bot's use: a fixed phone page, the bundled font only, and
the font size as the single export option.
2026-10-03 11:23:45 +07:00
tiennm99 c1393f0ec8 style(gacha): slow the mirror glint sweep to 1800ms 2026-10-02 17:28:18 +07:00
tiennm99 994749cee4 style(gacha): drop the glint flare and slow the streaks to a closer pair
Remove the four-ray star from the mirror glint, bring the two streaks
closer and make them wider, and run the sweep for 1200ms on ease-in-out.
2026-10-02 16:13:42 +07:00
tiennm99 4f80538d51 style(gacha): widen and soften the mirror glint, add a trailing streak
The glint band now fades out over a wider span at a lower peak, a thin
streak trails it, and the sweep runs 800ms on easeInOutSine.
2026-10-02 15:52:09 +07:00
tiennm99 c08839d59e style(gacha): cap the mirror glint at 120/255 alpha 2026-10-02 15:29:39 +07:00
tiennm99 c4bc131372 fix(gacha): blend the mirror glint additively and sweep the whole card
The glint band was a card-sized box sliding diagonally, so the top-right
and bottom-left corners never fell inside it. Draw the band on a box three
times the card's size, centred on it, so every corner is lit.

Blend the glint with plus-lighter and tint it with the card's accent so it
brightens the card's own tone instead of painting flat white over it.
2026-10-02 15:17:13 +07:00
tiennm99 21b9e596bc feat(gacha): put the moon emblem on the pack and card back
The pack prints a crescent moon in its diamond in place of the default star, and the card back carries the same moon in its diamond seal over the miti99bot caption.
2026-10-02 14:57:06 +07:00
tiennm99 dc65ce7df2 feat(gacha): show the card text on its last turn face up
The text switches on as the spinning card turns edge-on for the last time, so it rides in on the final turn instead of fading in after the spin stops.
2026-10-02 14:29:18 +07:00
tiennm99 b48f7602f9 feat(gacha): clear printed text and add a mirror glint on reveal
The pack and the card corners no longer carry text. The card's text stays hidden while it spins and fades in once it lies flat, then a mirror glint sweeps from the top-left corner to the bottom-right as the card comes to rest.
2026-10-02 14:17:09 +07:00
tiennm99 fb0f6dc68e docs(plans): record the thuyvan flood alert research, plan and journal 2026-10-02 13:06:08 +07:00
tiennm99 053fd07e5a feat(weather)!: add /thuyvan flood alerts for Tân Thuận
/thuyvan shows the 5-day tide-peak forecast at Phú An and Nhà Bè from
the Đài KTTV Nam Bộ bulletin PDF, the Open-Meteo rain forecast, and live
VNDMS river gauges within 30 km. /thuyvan_subscribe opts a chat into a
10:30 ICT push, retried at 12:30, sent only when a forecast peak reaches
báo động I (1.40 m) or a day's rain reaches 50 mm. A missing or stale
bulletin fails the push instead of reading as no risk.

The thoitiet module is renamed to weather; the /thoitiet* commands keep
their names.

BREAKING CHANGE: the module key is now "weather". A MODULES list that
names thoitiet fails at startup and must name weather instead.
2026-10-02 13:06:07 +07:00
tiennm99 59c74de9a9 refactor(lol): move the subscriber list and daily push fan-out into a shared package
The subscriber store, the once-per-day claim, the terminal-error
classifier and the throttled fan-out with dead-chat pruning now live in
internal/modules/util/subscription so another module can offer an
opt-in daily push. Stored document shapes and keys are unchanged.
2026-10-02 13:06:07 +07:00
tiennm99 53f387c576 feat(random): add an unlisted /genshin meteor wish command
/genshin takes the same input as /gacha and sends the Genshin-style
meteor wish that wheelofnames serves on /api/genshin, as a 7-second
640x360 MP4. It stays out of the command menu and /help.
2026-10-01 23:38:21 +07:00
tiennm99 59f597016b feat(genshin): serve the Remotion meteor wish on /api/genshin
Brings back the Genshin-style meteor wish that /api/gacha rendered before pack-cards, under genshin names. It takes the same request as /api/gacha and returns the 7-second landscape MP4.
2026-10-01 23:37:27 +07:00
tiennm99 b7a4336a7b chore(gacha): drop the last references to the retired beta route
Removes the test that checked /api/gachabeta answers 404 and the journal for the old beta animation, and points the research reports at /api/gacha.
2026-10-01 23:27:56 +07:00
tiennm99 48a30863c3 fix(gacha): stop the card text moving after the card settles
Three things moved the text once the card looked still:

- The spin's last degrees: Chrome draws text on a slightly tilted card
  differently from a flat one, so glyphs hopped as the spin ran out. The
  spin now lands flat a moment before the card stops gliding.
- The landing: the card flew at its rounded size scaled to a fractional
  layout size, then was laid out again, shifting the text. The card's
  size is now whole pixels.
- The last flight frames: a sub-pixel creep at almost-identity scale drew
  the text differently from the card at rest. The flight now reaches its
  final pose slightly early.
2026-10-01 23:22:51 +07:00
tiennm99 949e5e57ec revert(gacha): restore the original pack tear and drop motion
Only the card rising out of the pack keeps its exponential easing; the scripted tear and the pack falling away move as pack-cards drew them.
2026-10-01 23:22:51 +07:00
tiennm99 76c96464a8 feat(gacha): ease the tear, pack drop, and card rise exponentially
The scripted drag across the seal, the torn pack falling away, and the
card rising out of it now ease exponentially in and out. The pack keeps
pack-cards' 1.35s drop and hold, and the card still clears the pack when
the spin starts.
2026-10-01 22:52:52 +07:00
tiennm99 9e3cf9d78a fix(gacha): park the pointer off the card after tearing the pack
The scripted drag released the mouse over where the card lands. Once the
card arrived under it, pack-cards tilted the hovered card toward the
pointer over its .22s transition, so the face kept moving after the spin
stopped. The pointer now moves to the corner when the drag ends.
2026-10-01 22:43:05 +07:00
tiennm99 9d5a207f54 fix(gacha): keep the polychrome engraving on the card while it flies 2026-10-01 22:34:05 +07:00
tiennm99 6a4d0a1abb feat(gacha): draw the polychrome on the card engravings only
The sliding rainbow sheen over the whole face and the spectrum foil are
gone. 4★ cards are now engraved with pack-cards' epic contour and 5★ cards
with the legendary rings or facets, and those engraved lines carry a still
rainbow; 3★ cards stay plain.

The warm-up also renders a 4★ wish, since the first card with the contour
engraving otherwise rendered about seven seconds slower.
2026-10-01 22:26:37 +07:00
tiennm99 9665a3b47b docs: describe the shipped miti99bot wheel integration 2026-10-01 22:15:17 +07:00
tiennm99 fafe61b28b feat(random)!: send /gacha as the portrait card-pack wish
wheelofnames now renders /api/gacha as the 6-second portrait card-pack
wish and no longer serves /api/gachabeta. /gacha sends it at 360x640, and
the unlisted /gachabeta command and the per-style renderer settings are
removed.

BREAKING CHANGE: /gachabeta is gone; /gacha needs a wheelofnames build that
renders the portrait wish.
2026-10-01 22:13:59 +07:00
tiennm99 7ca1468a43 feat(gacha)!: render /api/gacha as the pack-cards wish
/api/gacha now renders the 6-second portrait pack-cards opening that was
served on /api/gachabeta. The Remotion meteor wish, its timeline, and the
beta route are removed, and the pack-cards renderer and page take the plain
gacha names.

BREAKING CHANGE: /api/gachabeta is gone, and /api/gacha returns a 6-second
portrait video (360x640 for width 640, 480x854 for width 854) instead of the
7-second landscape one.
2026-10-01 22:12:25 +07:00
tiennm99 d657c49c83 feat(gacha): give the beta card a polychrome rainbow foil 2026-10-01 22:07:12 +07:00
tiennm99 eb4208f1df feat(gacha): spin the beta card after it clears the pack, with sparkles
The card now rises out of the pack at pack-cards' own speed and only starts spinning once it is clear. The rest of the flight is stretched so three and a half turns ease out smoothly before the card lands face up, and glowing sparkles in the rarity colour burst around it while it spins. pack-cards is unchanged; the page reshapes its flight animations.
2026-10-01 19:03:31 +07:00
tiennm99 6f7fff5aab feat(random): send /gachabeta clips as 360x640 portrait animations
Each gacha style now carries the frame size it renders, so Telegram receives the beta wish's portrait size while /gacha stays 640x360.
2026-10-01 18:45:08 +07:00
tiennm99 fc3c7ae0c0 feat(gacha): render the beta wish in portrait
/api/gachabeta now returns a portrait video with the requested width as its long edge: 640 renders 360x640 and 854 renders 480x854. The request body is unchanged, and /api/gacha stays landscape. The pack sits higher so it stays in frame while the card rises.
2026-10-01 18:45:06 +07:00
tiennm99 b73347bb4f feat(gacha): spin the beta card one and a half turns as it is revealed
pack-cards reveals the card with a half turn from rotateY(180deg) to 0deg. The page starts that one animation at 540deg, so the card spins further before landing face up, without changing the library.
2026-10-01 18:45:06 +07:00
tiennm99 f9526c4613 fix(random): match the /gachabeta clip length to the 6-second beta wish 2026-10-01 18:22:32 +07:00
tiennm99 f44b8564eb docs(plans): record research on open-source gacha animation projects 2026-10-01 18:22:30 +07:00
tiennm99 8c06fdf498 feat(gacha): render the beta wish as a pack-cards pack opening
/api/gachabeta now tears open a pack-cards collectible pack whose card
shows the request's label, the same B/A/S rank as /api/gacha, and one
star per rarity level; rarity also picks the card material and the pack
colour. pack-cards animates on the browser clock, so the route drives a
shared headless Chrome in deterministic mode over a debugging pipe,
steps virtual time frame by frame, tears the pack with a scripted drag,
and encodes the captured frames with Remotion's bundled ffmpeg. The
page and package are served from disk with all other requests blocked.

The browser stays alive per process and is warmed at start-up, because
the first render compiles the pack's WebGL shaders in software. The
Remotion beta composition and its scene code are removed. pack-cards is
installed from a GitHub tarball pinned to a commit, since it has no npm
release and a moving URL would break npm ci.
2026-10-01 18:22:30 +07:00
tiennm99 25eb1414d4 fix(random): describe /gachabeta generically and match its new length
The beta renderer changed style, so /gachabeta now reads "Gacha (beta
version)" in its command description, package doc, README row, and deploy
guide instead of naming one look. The clip length sent to Telegram is 11
seconds to match the renderer's 10.5-second beta animation.
2026-10-01 16:44:16 +07:00
tiennm99 f75c0e560b fix(gacha): show the requested rank and stars on the beta reveal
The beta splash card used a fixed SSS rank and six stars. It now uses the
same rarity info as /api/gacha: the rank letter (B, A, or S) on the emblem
and name plate, and one star per rarity level beside the caller's label.
2026-10-01 16:36:22 +07:00
tiennm99 081ab00f2e feat(gacha): restyle the beta wish as a six-star wish sequence
The beta wish now cuts like an anime gacha game's six-star wish over 10.5
seconds: a dive through a painterly cloud vortex, a beam pass, a meteor
that stops inside a rainbow halo and bursts, a red flash, falling crystal
comets joined by a red meteor, a black emblem silhouette shedding shards
on a red disc, and a splash card with a name plate and six stars.

Cloud layers are painted once per roll onto a canvas with a seeded noise
displacement, so frames only move the baked image instead of running a
live SVG filter. The /api/gachabeta contract is unchanged.
2026-10-01 16:32:30 +07:00
tiennm99 b8d5544fd6 feat(gacha): rebuild the beta wish as a meteor shower reveal
The beta wish now renders an 8-second night sky over a snow-rimmed
mountain ridge, where coloured meteors glide down the diagonal. A hero
meteor in the rarity colour slows and burns out at the centre, where the
label appears under rank SSS. The toon cloud, comet, and perspective
camera are removed. The /api/gachabeta contract is unchanged.
2026-10-01 16:07:46 +07:00
tiennm99 adea684a2f feat(gacha): refine beta wish animation and SSS reveal 2026-10-01 15:42:54 +07:00
tiennm99 11f458fee5 feat(random): group the random pickers into a random module and add /gachabeta
/random, /wheelofnames, and /gacha move from misc into a new random module
with unchanged command names, so usage stats carry over. The new unlisted
/gachabeta takes the same input as /gacha and renders the toon-shaded beta
wish from /api/gachabeta on the same service, with the same text fallback.
2026-10-01 15:17:11 +07:00
tiennm99 577c0e3a70 feat(modules): add an unlisted command visibility
Unlisted commands can be run by anyone like public ones but never appear
in /help or the Telegram command menu.
2026-10-01 15:17:11 +07:00
tiennm99 d2f09ecb33 feat(gacha): add /api/gachabeta toon-shaded wish style
The beta wish renders an 8-second astrology-themed night sky in toon
shading through a perspective action camera: it dollies toward a hero
cloud, a comet lights the cloud's rim from behind and bursts through it,
the camera chases the comet as its flare builds to a starburst, and the
label appears with an S, SS, or SSS rank. It shares the /api/gacha request
contract and render slots.
2026-10-01 15:07:56 +07:00
tiennm99 dccd524f9d feat(gacha): fly into the rank emblem with a rainbow sunburst and twinkling sky
The meteor now flies in from the upper left and lands on the rank emblem,
so the impact burst becomes the badge reveal. The 5-star rainbow ring is
replaced by a rainbow sunburst of counter-rotating rays. Sky stars twinkle
with short bright flares and cross glints, and each roll draws a fresh
layout from a per-request seed that the route picks when the caller sends
none.
2026-10-01 14:43:46 +07:00
tiennm99 9b93c53647 revert: restore the gacha animation to bd314b4
Reverts be27792 (measured straight glide) and 028ad08 (gravity arc), going
back to the B/A/S tier-escalation version with the original curved fall.
2026-10-01 14:26:33 +07:00
tiennm99 e158319981 docs(plans): record the measured meteor curve from reference footage 2026-10-01 14:12:03 +07:00
tiennm99 dc8f7f6ae7 feat(gacha): match the meteor glide to the source game's motion
Measured from reference footage, the meteor sweeps in along a straight line
from the upper left at about 23 degrees and eases out exponentially to a
hover above the horizon, replacing the gravity arc. The trail is now a soft
beam that narrows into the head with ribbons fanning along it, every tier
gets the horizontal lens flare, and sparkles drift above the beam.
2026-10-01 14:12:01 +07:00
tiennm99 4fa0e10384 docs(plans): record gacha research and the rarity prefix format 2026-10-01 13:59:12 +07:00
tiennm99 5561c33068 feat(misc): default /gacha options to 5 stars with a 3*/4* prefix
Rarity is now a leading prefix such as "4* Pho" or "3* Rice"; options
without one are 5 stars. This replaces the trailing "*5" tag and its
3-star default.
2026-10-01 13:59:12 +07:00
tiennm99 004325d947 feat(gacha): make the meteor fall under real gravity
The meteor now follows a ballistic parabola: constant horizontal speed and
vertical speed growing under gravity solved from the launch and impact
points, so it climbs slightly, bends over, and dives while speeding up. The
trail samples past positions in time so it lengthens with speed, the head
stretches along its velocity, and sparks inherit the meteor's velocity and
fall under their own gravity.
2026-10-01 13:58:38 +07:00
tiennm99 adc2b94ae9 feat(gacha): escalate the wish animation by rarity with B/A/S rank emblems
Every beat now scales from a per-tier effects table: 4-star adds a bigger
meteor, lens flare, impact shake, and double shockwave; 5-star adds a
rainbow halo before landing, a gold sky flood, a starburst, counter-rotating
rays, falling sparkles, and an emblem sheen. The emblem shows the rank
letter instead of the label's first character.
2026-10-01 13:25:06 +07:00
tiennm99 b3c46a2f2f docs(plans): note the switch to uniform gacha odds 2026-10-01 13:09:08 +07:00
tiennm99 4816f71fe3 feat(misc): give every /gacha option an equal chance
The rarity tag now only styles the wish animation and the result text, so
/gacha picks like /random.
2026-10-01 13:09:08 +07:00
tiennm99 070f303b35 docs(plans): record the gacha wish command plan 2026-10-01 13:02:46 +07:00
tiennm99 858eaa066b feat(misc): add /gacha Genshin-style wish picker
Options take an optional *4 or *5 rarity tag; untagged options are 3 stars.
A pick rolls a tier at Genshin base rates over the tiers present, then an
option uniformly within it, so untagged input matches /random. The wish
animation renders as MP4 on the wheelofnames service at /api/gacha, with a
text fallback when the renderer is unset or fails.
2026-10-01 13:02:46 +07:00
tiennm99 53ca1e59b3 refactor(misc): share the renderer POST and placeholder helpers 2026-10-01 13:02:46 +07:00
tiennm99 97d8aeaadf feat(gacha): add /api/gacha wish animation rendered as MP4
A meteor coloured by rarity falls across a night sky, lands in a white
flash, and the label is revealed with its stars popping in. The caller
picks the result and rarity; the route only draws it. Renders as silent
H.264 so Telegram plays it as an animation without GIF banding, and
shares the render slots with /api/gif.
2026-10-01 11:43:42 +07:00
tiennm99 61d0e74709 refactor(render): share the render core and bearer check across routes 2026-10-01 11:43:42 +07:00
tiennm99 2fd362dd01 feat(thoitiet): make /thoitiet an hourly forecast for the next 6 hours 2026-10-01 10:34:03 +07:00
tiennm99 f748d34095 feat(thoitiet): add today, tomorrow, and 7-day weather commands 2026-10-01 10:28:10 +07:00
tiennm99 4607ae5f14 feat(help): split /help into several messages past Telegram's length limit 2026-10-01 10:28:09 +07:00
tiennm99 7cc0ead54f refactor: remove the unused gold spot-price chain and lol ErrEmptyResult 2026-09-30 14:20:38 +07:00
tiennm99 897e37ba80 fix(storage): return Mongo List keys in key order like the memory store 2026-09-30 14:20:27 +07:00
tiennm99 00eccd5f5b fix(stats): clear a moved username from its previous holder's rows 2026-09-30 14:20:08 +07:00
tiennm99 fdd8f6af44 fix(server): wait for polling and the final metrics flush on shutdown 2026-09-30 14:19:49 +07:00
tiennm99 1d42887d6b fix(server): return 500 when a handler panics before writing 2026-09-30 14:19:39 +07:00
tiennm99 83a5b8135f fix(gold): switch to a managed VNAppMob key when the env key is rejected 2026-09-30 14:19:30 +07:00
tiennm99 72b2b21912 fix(wordle): stop giveup from recording a second loss on exhausted rounds 2026-09-30 14:19:20 +07:00