mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-09-12 14:19:39 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user