From de625aebf562f028e039473869da8e66fd784b58 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 21 Apr 2026 10:38:37 +0700 Subject: [PATCH] =?UTF-8?q?refactor(lolschedule):=20post-subscribe=20revie?= =?UTF-8?q?w=20=E2=80=94=20docs=20and=20stale=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- src/modules/lolschedule/README.md | 9 +++++++-- src/modules/lolschedule/format.js | 16 ++++++---------- src/modules/lolschedule/handlers.js | 4 ++-- tests/modules/lolschedule/format.test.js | 6 +----- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/modules/lolschedule/README.md b/src/modules/lolschedule/README.md index d487a4a..51b5692 100644 --- a/src/modules/lolschedule/README.md +++ b/src/modules/lolschedule/README.md @@ -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 diff --git a/src/modules/lolschedule/format.js b/src/modules/lolschedule/format.js index 68248a5..5004d18 100644 --- a/src/modules/lolschedule/format.js +++ b/src/modules/lolschedule/format.js @@ -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" ? `${t1Label}` : t1Label; const r = teams[1]?.result?.outcome === "win" ? `${t2Label}` : 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}`; } /** diff --git a/src/modules/lolschedule/handlers.js b/src/modules/lolschedule/handlers.js index d4577a1..4a6c63c 100644 --- a/src/modules/lolschedule/handlers.js +++ b/src/modules/lolschedule/handlers.js @@ -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"; diff --git a/tests/modules/lolschedule/format.test.js b/tests/modules/lolschedule/format.test.js index ffc81f0..89cec78 100644 --- a/tests/modules/lolschedule/format.test.js +++ b/tests/modules/lolschedule/format.test.js @@ -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);