refactor(lolschedule): post-subscribe review — docs and stale option

- handlers.js header now reflects fan-out to subscribers, not a single chat.
- README "Time zone" references the correct command names and gains a
  Subscribers section; Files section lists subscribers.js.
- formatEventLine's showLeague option is dead in production (renderToday
  and renderWeek always group under a league header), so drop it and the
  test that covered only the option toggle.
This commit is contained in:
2026-04-21 10:38:59 +07:00
committed by tiennm99
parent 58e8995968
commit f46cd59e2c
4 changed files with 16 additions and 19 deletions
+7 -2
View File
@@ -66,13 +66,18 @@ Cache-first with KV. Key is `matches:{fromIso}:{toIso}`.
- `/lolschedule_week` — one section per ICT day; within each day, leagues are sub-grouped.
- League ordering follows `LEAGUE_ORDER` in `format.js` (worlds / msi / first_stand first, then LCK / LPL / LEC / LCS, then the rest).
## Subscribers
`/lolschedule_subscribe` adds `ctx.chat.id` to the module's `subscribers` KV key (JSON array). `/lolschedule_unsubscribe` removes it. Both are idempotent and reply with the new state. The daily cron reads this list; empty list means the cron skips cleanly.
## Time zone
All rendering is in **ICT (UTC+7)**. `startTime` is UTC ISO; day boundaries for the `/lol_today` and `/lol_week` windows are anchored to ICT midnight.
All rendering is in **ICT (UTC+7)**. `startTime` is UTC ISO; day boundaries for the `/lolschedule_today` and `/lolschedule_week` windows are anchored to ICT midnight.
## Files
- `index.js` — module contract
- `api-client.js` — getSchedule client with pagination + cache
- `format.js` — pure renderers (`formatEventLine`, `renderToday`, `renderWeek`)
- `handlers.js` — grammY command handlers + ICT day-boundary helpers
- `handlers.js` — grammY command handlers, ICT day boundaries, cron fan-out
- `subscribers.js` — KV-backed add/list/remove for the daily-push list
+6 -10
View File
@@ -73,38 +73,34 @@ function teamLabel(team) {
}
/**
* Render one event line (no leading newline). By default the league name is
* omitted because events are rendered under a league header; pass
* `{ showLeague: true }` to include it for flat lists.
* Render one event line (no leading newline). The league name is omitted
* because events render under a league header.
*
* @param {ScheduleEvent} event
* @param {{ showLeague?: boolean }} [opts]
* @returns {string} escaped HTML
*/
export function formatEventLine(event, { showLeague = false } = {}) {
export function formatEventLine(event) {
const teams = event?.match?.teams || [];
const t1Label = escapeHtml(teamLabel(teams[0]));
const t2Label = escapeHtml(teamLabel(teams[1]));
const block = event?.blockName ? ` (${escapeHtml(event.blockName)})` : "";
const bestOf = event?.match?.strategy?.count;
const bo = bestOf ? ` · Bo${bestOf}` : "";
const leagueSuffix =
showLeague && event?.league?.name ? ` · ${escapeHtml(event.league.name)}` : "";
if (event?.state === "completed") {
const w1 = teams[0]?.result?.gameWins ?? 0;
const w2 = teams[1]?.result?.gameWins ?? 0;
const l = teams[0]?.result?.outcome === "win" ? `<b>${t1Label}</b>` : t1Label;
const r = teams[1]?.result?.outcome === "win" ? `<b>${t2Label}</b>` : t2Label;
return `${l} ${w1}${w2} ${r}${bo}${leagueSuffix}${block}`;
return `${l} ${w1}${w2} ${r}${bo}${block}`;
}
if (event?.state === "inProgress") {
const w1 = teams[0]?.result?.gameWins ?? 0;
const w2 = teams[1]?.result?.gameWins ?? 0;
return `🔴 LIVE ${t1Label} ${w1}${w2} ${t2Label}${bo}${leagueSuffix}${block}`;
return `🔴 LIVE ${t1Label} ${w1}${w2} ${t2Label}${bo}${block}`;
}
const time = formatIctTime(new Date(event.startTime));
return `🕒 ${time} ${t1Label} vs ${t2Label}${bo}${leagueSuffix}${block}`;
return `🕒 ${time} ${t1Label} vs ${t2Label}${bo}${block}`;
}
/**
+2 -2
View File
@@ -3,8 +3,8 @@
*
* Day boundaries are defined in ICT (UTC+7). Data comes from lolesports.com
* via a cache-first fetcher; no cron pre-warm is needed because the upstream
* API is rate-limit friendly. A daily cron also pushes today's schedule to a
* configured chat.
* API is rate-limit friendly. A daily cron fans today's schedule out to every
* chat opted in via /lolschedule_subscribe.
*/
import { getEventsCached } from "./api-client.js";
+1 -5
View File
@@ -62,14 +62,10 @@ describe("formatIctTime / formatIctDayLabel", () => {
});
describe("formatEventLine", () => {
it("omits league name by default (renders under league header)", () => {
it("omits league name renders under a league header", () => {
expect(formatEventLine(evt())).not.toContain("LCK");
});
it("includes league name when showLeague is true", () => {
expect(formatEventLine(evt(), { showLeague: true })).toContain("LCK");
});
it("renders completed with bolded winner + score", () => {
const line = formatEventLine(completed);
expect(line.startsWith("✅")).toBe(true);