82 Commits
Author SHA1 Message Date
tiennm99 b6ea0059ce feat(amlich): add Vietnamese lunar calendar conversion module
/amlich converts duong lich to am lich (defaults to today, Asia/Saigon);
/duonglich converts am lich to duong lich with a nhuan flag for leap
months. Dates accept d, d/m, or d/m/yyyy - missing parts fill from today
in the input's calendar. Conversion is a dependency-free port of Ho Ngoc
Duc's algorithm at UTC+7, with rejection of impossible lunar inputs,
anchored by known Tet/leap-month dates and a 1950-2050 round-trip test.
2026-08-08 22:40:03 +07:00
tiennm99 1a7d327c90 feat(monkeyd): add /monkeyd_tags to report a novel's tags as hashtags
Replies with a hashtag line led by #MonkeyD, then a blank line and the novel
URL, sent as a code block so it can be copied in one tap. Each genre label
becomes one hashtag with spaces and punctuation stripped and every word
capitalised, since Telegram ends a hashtag at the first character that is not a
letter, digit, or underscore. Diacritics survive; a label with no letters is
dropped rather than emitted as a bare hash.

The command costs one request and runs inline under a short timeout rather than
in the background: handlers are dispatched one at a time, so a stalled fetch
would block every other command. It shares the export page cache.

Advance the submodule to the tag parser, which matches itemprop="genre"
microdata so the site-wide genre navigation stays out of the result.
2026-07-30 00:52:10 +07:00
tiennm99 365ee281d4 feat(monkeyd): accept an optional font size argument
/monkeyd_crawl <url> [font_size] sets the body text size in points, half points
included, bounded to 6-24. Omitting it sends no size at all so the crawler's
default applies, rather than defining a second default here that could drift.
The document caption reports the size used.

Also advance the submodule to the lower 10pt default: on the 90x160mm phone
page that fits about 43 characters per line instead of 36.
2026-07-30 00:16:57 +07:00
tiennm99 84ef519fb1 fix(monkeyd): make /monkeyd_crawl public and fix PDF font resolution
Advance the crawler submodule to the fix for the production failure "stat
usr/share/fonts/...: no such file or directory": the PDF writer was handing
fpdf a font path, and fpdf rewrote the absolute path into a
working-directory-relative one. Font data is now passed as bytes, with a
fallback font compiled into the binary when the host has none.

The runtime image therefore no longer installs DejaVuSans, which also removes
the font layer from the builder stage.

/monkeyd_crawl becomes public. The host allowlist and the single in-flight
export were already the controls that bound its cost; they now carry that job
alone, so both are load-bearing.
2026-07-29 23:46:33 +07:00
tiennm99 e05b0f5be0 feat(monkeyd): export monkeydd.com novels as PDF via /monkeyd_crawl
Add the monkeyd module, which crawls a novel and sends the rendered PDF back
as a Telegram document. Crawling and rendering come from the monkeyd-crawler
submodule, resolved through a go.mod replace directive.

The command is admin-only and restricted to monkeydd.com: one run makes
hundreds of outbound requests over minutes, and the extractor only understands
that site. Exports run one at a time and on a detached goroutine, because
handlers are dispatched synchronously and an inline crawl would block every
other command.

The runtime image gains DejaVuSans; font discovery probes system paths and the
distroless base ships none, so PDF rendering would otherwise fail in
production. CI checks out submodules and the builder copies the submodule
go.mod before go mod download, which needs it to resolve the build list.
2026-07-29 23:07:23 +07:00
tiennm99 26b969b5ef feat(stock): add detailed stock info command 2026-07-23 12:27:12 +07:00
tiennm99 7123ea77c3 feat(stock): add stock events lookup 2026-07-23 10:03:41 +07:00
tiennm99 27820970d9 fix(stock): retire dividend command and migrate stats 2026-07-22 18:55:57 +07:00
tiennm99 039780749a feat(stock): persist per-user dividend history 2026-07-22 16:17:29 +07:00
tiennm99 1cc7e4cae6 refactor(coin): retire completed startup cleanup 2026-07-21 19:13:23 +07:00
tiennm99 de498f92b9 fix(coin): remove stale dividend cursor fields 2026-07-21 18:50:30 +07:00
tiennm99 dbe0c9ca70 refactor(portfolio): retire completed migrations 2026-07-21 17:13:19 +07:00
tiennm99 6fe91f0e4e feat(portfolio): nest asset positions 2026-07-21 16:42:33 +07:00
tiennm99 e5dc898fde feat(portfolio): track stock and coin cost basis 2026-07-21 15:52:05 +07:00
tiennm99 ad31801c32 refactor(commands): standardize parameter conventions 2026-07-21 14:51:28 +07:00
tiennm99 2180c2a59f fix(commands): remove examples from command discovery 2026-07-21 13:52:53 +07:00
tiennm99 a809646828 fix(commands): render examples conditionally with inline help code 2026-07-21 13:21:01 +07:00
tiennm99 341255c766 feat(commands): improve discovery and normalize parameters 2026-07-21 11:45:20 +07:00
tiennm99 af85886521 refactor: adopt cross-platform Go development workflow 2026-07-21 09:05:20 +07:00
tiennm99 ed0ef83601 feat(stock): add ratio dividend commands 2026-07-20 17:32:37 +07:00
tiennm99 2ec0ae119a feat(modules): remove world cup module
The 2026 tournament has ended, so the schedule and daily digest commands
have no upcoming matches to report. Drops the module, its catalog entry,
command menu entries, and the WC_FOOTBALL_DATA_TOKEN env var.

Stored subscriber and match-cache documents are left in place.
2026-07-20 11:03:59 +07:00
tiennm99 5d460f8afe refactor(stats): remove completed startup migrations 2026-07-09 15:44:23 +07:00
tiennm99 a7642e45f6 fix(lol): rename next week command 2026-07-06 20:46:10 +07:00
tiennm99 cdf9eec59a refactor: remove completed migration logic 2026-07-01 16:20:30 +07:00
tiennm99 88ff620f89 feat(lol): expire match cache 2026-07-01 13:40:37 +07:00
tiennm99 a5b808ebeb feat: refine command names and behavior 2026-07-01 13:19:42 +07:00
tiennm99 3ec608c9a1 refactor: remove completed startup migrations 2026-07-01 11:42:59 +07:00
tiennm99 44bc598250 feat(commands): simplify portfolio command names 2026-07-01 10:53:52 +07:00
tiennm99 cee1daeea9 feat: rename wc schedule commands 2026-07-01 10:42:56 +07:00
tiennm99 2315618811 feat: rename lol schedule module 2026-07-01 10:38:49 +07:00
tiennm99 6fd65729ac feat(stats): use queryable Mongo usage records 2026-07-01 10:22:03 +07:00
tiennm99 632fec8885 refactor(modules): remove twentyq genai integration 2026-06-29 10:07:21 +07:00
tiennm99 0f5d82e5fd refactor(config): remove price URL env overrides 2026-06-29 09:23:20 +07:00
tiennm99 10ad9a09aa feat(server): register command menu on startup 2026-06-29 01:08:51 +07:00
tiennm99 559e636d89 feat(wc): add world cup schedule module 2026-06-29 00:59:30 +07:00
tiennm99 7a4f822e8d fix(deploynotify): preserve coolify source commit 2026-06-29 00:20:04 +07:00
tiennm99 5cabf4e1f4 refactor(deploy): remove retired aws support 2026-06-29 00:11:42 +07:00
tiennm99 ae6925be8b feat(deploynotify): notify owner on every startup, no SHA dedup
Match the store-scraper-bot behaviour: DM the owner once per boot regardless of
commit, and report an unknown SHA rather than staying silent. Drop the store-
based dedup and the empty-SHA skip; resolveCommitSHA defaults to "unknown".
2026-06-28 23:25:07 +07:00
tiennm99 fc24e39c01 fix(deploynotify): read commit SHA from Coolify SOURCE_COMMIT runtime env
Baking SOURCE_COMMIT as a Docker build arg never worked: Coolify exposes it
as a runtime env var, not a build arg, so gitSHA was always empty and the owner
DM was skipped. Read SOURCE_COMMIT from the container env at startup (falling
back to the ldflags-baked gitSHA for local builds), forward it via the compose
environment, and drop the dead build-arg baking.
2026-06-28 23:08:17 +07:00
tiennm99 2722db5429 refactor(server): delete webhook once on startup, no retry
The Bot API GET reliably clears the webhook, so the retry loop added for the
empty-body POST is no longer needed. One best-effort call; log on failure.
2026-06-28 22:52:55 +07:00
tiennm99 7d753a03cc fix(telegram): clear webhook via Bot API GET to avoid empty-body POST
go-telegram's DeleteWebhook posts an empty multipart form (DropPendingUpdates
is omitempty); some networks answer that bodyless POST with an empty response,
failing to parse and leaving the webhook active so getUpdates 409s forever.
Replace it with a plain GET /deleteWebhook, which sends no body and parses the
JSON reliably. Keeps pending updates for a lossless cutover.
2026-06-28 22:41:49 +07:00
tiennm99 54c5649663 fix(server): retry webhook clear on startup past transient empty-body error
Telegram's edge can return an empty body on the first request after a cold
container start, failing the single best-effort DeleteWebhook with 'unexpected
end of JSON input' and leaving a stale webhook that 409s getUpdates. Retry up
to 5 times with a 1s backoff so long polling is reliably unblocked.
2026-06-28 22:33:34 +07:00
tiennm99 bd7a7fcedf fix(server): clear Telegram webhook at startup before owner DM
Move DeleteWebhook ahead of the deploynotify owner DM and polling so the
webhook is cleared first thing once the bot is ready, instead of just before
getUpdates. DropPendingUpdates=false keeps the lossless cutover.
2026-06-28 22:18:28 +07:00
tiennm99 2a2416236c refactor(server): drop HTTP /cron route; run crons in-process only
The in-process scheduler is the sole cron trigger on self-host, so the
/cron/{name} HTTP endpoint and its CRON_SHARED_SECRET are dead surface.
Reduce the HTTP server to GET / (health) and remove the cron secret config,
SSM binding, and now-unused timeout.
2026-06-28 22:15:04 +07:00
tiennm99 b81b6501c5 refactor(storage): replace KVStore with generic typed DocStore[T]
Delete the byte-oriented KVStore/VersionedStore abstraction and the
DynamoDB/memory KV backends. Add a generic typed store (DocStore[T] with
Provider/Collection/Typed) persisting each value as a flattened native
Mongo document (storedDoc[T] via bson inline) — no value envelope.

- MongoDB is the only runtime backend; memory kept for tests/local.
- All modules + deploynotify use typed stores; persisted structs carry
  bson tags == json names (incl. nested lolschedule/wordle types).
- lolschedule wraps its array/scalar values in named structs.
- migrate-dynamo-to-mongo writes the flattened shape via Typed[bson.M]
  with wrap rules; Scan/--dry-run/--verify retained.

Verified: go vet/build clean; full go test green hermetically and
in-container vs real Mongo 7 + DynamoDB Local (storage integration +
migrator e2e).
2026-06-28 18:02:11 +07:00
tiennm99 a03770cf6b feat(storage): store mongo values as native BSON documents
Implement mongodb_value_codec to encode portfolio/gold/stock values as native
BSON documents instead of JSON-serialized byte strings. Reduces storage size,
improves query efficiency, and eliminates unmarshal overhead. Native BSON
encoding preserves int64 fidelity and enables future native-format queries.
Add NativeValueRepresentation and Int64Fidelity tests validating lossless
encoding. Update migration tool comments. Document native-value behavior
in deploy guide. Update phase-01 plan with implementation notes.
2026-06-28 12:52:48 +07:00
tiennm99 4b15d07d1b refactor(storage): remove legacy firestore backend
Delete Firestore provider implementation and tests (5 files). Extract shared
key helpers to new internal/storage/keys.go module. Remove Firestore case
from server bootstrap and storage factory. Drop cloud.google.com/go/firestore
and related dependencies from go.mod. Remove firestore-emulator test targets
from Makefile. Update README storage section to reflect MongoDB-only backend.
2026-06-28 12:52:40 +07:00
tiennm99 9e914204ce feat(migrate): add DynamoDB-to-MongoDB data migrator
Add standalone migrator tool with scan-and-transform logic for moving persisted
state from DynamoDB to MongoDB. Includes tests and README with usage instructions.
2026-06-28 09:58:28 +07:00
tiennm99 8048be8d8a feat(server): wire MongoDB + cron + polling; rename owner/admin envs
Wire MongoDB client and cron scheduler into server initialization. Rename
TELEGRAM_OWNER to TELEGRAM_ADMIN_ID and SLACK_OWNER to SLACK_ADMIN_ID
for clarity. Enable long-polling as default with cron scheduler for self-hosted.
2026-06-28 09:58:20 +07:00
tiennm99 a295337281 feat(gold): integrate VNAppMob SJC gold price with auto-refresh API key 2026-06-15 10:49:19 +07:00