Commit Graph
255 Commits
Author SHA1 Message Date
tiennm99 d2f6e97fb5 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 0ddc6b3ccc 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 26509e1420 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 37edb0f108 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 4a19bf50bc docs(lol): add leaguepedia score enrichment research and plan
Research: after lolesports began marking matches completed without
publishing results, evaluated independent score sources. Established
that wrappers of lolesports (lolesportsapi.com, Pupix, Apify) inherit
the gap, and that getCompletedEvents/getStandings are equally stale, so
only a source with its own ingestion helps. Leaguepedia is the only free
candidate; commercial providers run $2k-10k/mo.

Plan: 4 phases for filling missing gameWins/outcome from Leaguepedia's
Cargo API. lolesports stays authoritative for schedule and for any score
it does publish. Additive throughout - every failure path degrades to the
existing "score pending" line.

Joins on team name plus date rather than team codes: Riot already sends
Team.Name matching Leaguepedia's Team1/Team2, which removes the code
mapping table and the per-split OverviewPage handling that the research
had flagged as the main maintenance burden.

Phase 1 is a hard gate. Fandom rate-limited and then refused the
connection during research, so it was never confirmed that Leaguepedia
actually holds the scores Riot is missing. That check can cancel the plan.

Plan is pre-validation. A verification pass found three items not yet
recorded in the files: slices.ContainsFunc in phase 4 has no precedent in
this repo (explicit loops are the house style), the TTL index citation
should read startup.go:35-52, and the Bo2 tie handling in phase 3 is
unnecessary - all 26 allowlisted-league events are Bo3 with zero ties.
2026-07-26 12:57:56 +07:00
tiennm99 570ee94d65 fix(lol): omit score when a finished match has none published
lolesports drives event.state off the broadcast timeline but fills
result.outcome and result.gameWins from a separate per-game ingestion
path. A match therefore reads as "completed" for hours before gaining a
score, and in that window every team carries {"outcome": null,
"gameWins": 0}.

The renderer used `Result != nil` as its has-a-score test, which cannot
catch this: the result object is present, only its contents are empty.
The absent gameWins fell through to Go's zero value and printed as a
literal 0, so an unscored series was reported as a definitive draw
("MKOI 0-0 KC") with neither side bolded.

Gate the score on a declared outcome instead, and render unscored
finished matches as the matchup alone with a pending marker. An outcome
on either side is enough to trust the score - it proves the ingestion
ran. inProgress keeps rendering 0-0, which is truthful for a live series
that has not resolved its first game.

Also collapses the score extraction both branches duplicated into
shared helpers.
2026-07-26 09:16:39 +07:00
tiennm99 4989a77489 docs(stock): record stock info delivery 2026-07-23 12:27:12 +07:00
tiennm99 f697910855 feat(stock): add detailed stock info command 2026-07-23 12:27:12 +07:00
tiennm99 5379e9b484 docs(stock): finalize stock-events delivery 2026-07-23 10:08:47 +07:00
tiennm99 973e450d31 docs(stock): add stock-events plan artifacts 2026-07-23 10:05:44 +07:00
tiennm99 1b4f470fd3 feat(stock): add stock events lookup 2026-07-23 10:03:41 +07:00
tiennm99 b63dc73b33 docs(stock): record dividend retirement workflow 2026-07-22 18:56:07 +07:00
tiennm99 81443fd080 fix(stock): retire dividend command and migrate stats 2026-07-22 18:55:57 +07:00
tiennm99 20e8a6b126 docs(portfolio): document mobile column layout 2026-07-22 17:36:29 +07:00
tiennm99 ac0582ddf9 fix(portfolio): tighten mobile column formatting 2026-07-22 17:36:29 +07:00
tiennm99 67bfb63a02 feat(stock): persist per-user dividend history 2026-07-22 16:17:29 +07:00
tiennm99 d9a35ec203 feat: format compact portfolio numbers 2026-07-22 12:27:52 +07:00
tiennm99 6733ea7369 fix(stock): note currency in portfolio title 2026-07-22 10:59:40 +07:00
tiennm99 f95b15f6f3 docs: remove completed implementation records 2026-07-21 19:13:34 +07:00
tiennm99 5c4cf7c70e docs(deploy): refresh persisted storage layout 2026-07-21 19:13:28 +07:00
tiennm99 00b62321a1 refactor(coin): retire completed startup cleanup 2026-07-21 19:13:23 +07:00
tiennm99 cffde669ec refactor(metrics): remove unused AI counters 2026-07-21 19:13:15 +07:00
tiennm99 9f2710569b chore(stock): log dividend event checks 2026-07-21 18:50:48 +07:00
tiennm99 b8617d0a41 fix(coin): remove stale dividend cursor fields 2026-07-21 18:50:30 +07:00
tiennm99 f9f7c0f766 docs(stock): document dividend event buttons 2026-07-21 18:20:14 +07:00
tiennm99 77e8ca05d6 feat(stock): add dividend event buttons 2026-07-21 18:19:50 +07:00
tiennm99 139bf46f08 docs(stock): research dividend event APIs 2026-07-21 17:13:43 +07:00
tiennm99 acc291e9cc refactor(portfolio): retire completed migrations 2026-07-21 17:13:19 +07:00
tiennm99 3a8da63211 feat(portfolio): nest asset positions 2026-07-21 16:42:33 +07:00
tiennm99 fbe0d8ec53 docs(portfolio): document cost basis and P&L 2026-07-21 15:52:30 +07:00
tiennm99 218b6ce2b8 feat(portfolio): track stock and coin cost basis 2026-07-21 15:52:05 +07:00
tiennm99 6cd89e768c refactor(commands): standardize parameter conventions 2026-07-21 14:51:28 +07:00
tiennm99 d31b3d6b20 fix(commands): remove examples from command discovery 2026-07-21 13:52:53 +07:00
tiennm99 5d46057101 fix(commands): render examples conditionally with inline help code 2026-07-21 13:21:01 +07:00
tiennm99 0866ab4ed3 feat(commands): improve discovery and normalize parameters 2026-07-21 11:45:20 +07:00
tiennm99 55c4520673 test: provision MongoDB 8 with Testcontainers 2026-07-21 09:39:06 +07:00
tiennm99 1f767c596a refactor: adopt cross-platform Go development workflow 2026-07-21 09:05:20 +07:00
tiennm99 6c29d89200 docs(stock): document dividend commands 2026-07-20 17:33:10 +07:00
tiennm99 df92536a34 feat(stock): add ratio dividend commands 2026-07-20 17:32:37 +07:00
tiennm99 6ef9c2c4cf 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 de76a74c32 feat(misc): add protected /ff giving-up template
Replies with a fixed Vietnamese copypasta for when T1 starts losing, built
from the local community's running jokes. Protected, so it stays out of the
public command menu like the other gated commands.
2026-07-19 20:09:22 +07:00
tiennm99 c71b17b772 feat(modules): match commands case-insensitively
Mobile keyboards autocapitalize, so a typed /Ping silently did nothing and
read as the bot being broken. Registered names are lowercase-only per
validateCommand, so folding case cannot make two commands collide, and the
canonical Command.Name still drives stats, metrics, hooks, and logs.
2026-07-19 20:09:16 +07:00
tiennm99 2a6c41e6ba build: update Go toolchain and crypto dependency 2026-07-09 18:18:01 +07:00
tiennm99 268938d6b7 refactor(stats): remove completed startup migrations 2026-07-09 15:44:23 +07:00
tiennm99 f67aa7cb7e fix(misc): fall back wheelofnames to text reply 2026-07-09 14:38:04 +07:00
tiennm99 0bd7d71513 feat(misc): promote wheelofnames gif command 2026-07-09 13:14:11 +07:00
tiennm99 38d1db3054 refactor(misc): share disclaimer template 2026-07-09 11:34:53 +07:00
tiennm99 5f95f711f3 feat(misc): add VNG disclaimer aliases 2026-07-07 11:19:27 +07:00
tiennm99 5844f08539 docs(plans): remove plan artifacts 2026-07-07 10:19:51 +07:00
tiennm99 cfa9beba8c feat(coin): allow upstream-supported tickers 2026-07-07 10:12:29 +07:00