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);