mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-08-14 00:23:06 +00:00
fix(lolschedule): group week by league → day instead of day → league
Users expect to scan a single region's upcoming week rather than a daily fixture board. Reverses the nesting: /lolschedule_week now lists each major league as a top-level section, then italicised ICT date headers with that league's matches beneath.
This commit is contained in:
@@ -7,7 +7,7 @@ LoL esports match schedule via the **lolesports.com** esports-api (the data feed
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `/lolschedule_today` | Today's matches (ICT), grouped by league. Scores for played + live, times for upcoming. |
|
||||
| `/lolschedule_week` | Next 7 days, grouped by day → league. |
|
||||
| `/lolschedule_week` | Next 7 days, grouped by league → day. |
|
||||
| `/lolschedule_subscribe` | Opt the current chat into the daily 08:00 ICT digest. |
|
||||
| `/lolschedule_unsubscribe` | Stop receiving the digest. |
|
||||
|
||||
@@ -63,7 +63,7 @@ Cache-first with KV. Key is `matches:{fromIso}:{toIso}`.
|
||||
## Grouping
|
||||
|
||||
- `/lolschedule_today` — one section per league (header + match lines).
|
||||
- `/lolschedule_week` — one section per ICT day; within each day, leagues are sub-grouped.
|
||||
- `/lolschedule_week` — one section per league; within each league, matches are sub-grouped by ICT day (italic date headers).
|
||||
- League ordering follows `LEAGUE_ORDER` in `format.js` (worlds / msi / first_stand first, then LCK / LPL / LEC / LCS, then the rest).
|
||||
|
||||
## Subscribers
|
||||
|
||||
@@ -151,7 +151,9 @@ export function renderToday(events, day) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render week reply — grouped by ICT day → league.
|
||||
* Render week reply — grouped by league → ICT day. The league ordering is
|
||||
* determined by LEAGUE_ORDER; within each league, days appear chronologically
|
||||
* and each day section lists its matches.
|
||||
*
|
||||
* @param {ScheduleEvent[]} events
|
||||
* @param {Date} from
|
||||
@@ -164,24 +166,25 @@ export function renderWeek(events, from, to) {
|
||||
const header = `<b>LoL — ${fromLbl} → ${toLbl}</b> (ICT)`;
|
||||
if (events.length === 0) return `${header}\nNo matches this week.`;
|
||||
|
||||
/** @type {Map<string, { label: string, events: ScheduleEvent[] }>} */
|
||||
const days = new Map();
|
||||
for (const event of events) {
|
||||
const d = new Date(event.startTime);
|
||||
const key = ictDayKey(d);
|
||||
let g = days.get(key);
|
||||
if (!g) {
|
||||
g = { label: formatIctDayLabel(d), events: [] };
|
||||
days.set(key, g);
|
||||
const leagueBlocks = groupByLeague(events).map((league) => {
|
||||
/** @type {Map<string, { label: string, lines: string[] }>} */
|
||||
const daysInLeague = new Map();
|
||||
for (const event of league.events) {
|
||||
const d = new Date(event.startTime);
|
||||
const key = ictDayKey(d);
|
||||
let g = daysInLeague.get(key);
|
||||
if (!g) {
|
||||
g = { label: formatIctDayLabel(d), lines: [] };
|
||||
daysInLeague.set(key, g);
|
||||
}
|
||||
g.lines.push(formatEventLine(event));
|
||||
}
|
||||
g.events.push(event);
|
||||
}
|
||||
const daySections = [...daysInLeague.keys()].sort().map((key) => {
|
||||
const day = daysInLeague.get(key);
|
||||
return `<i>${escapeHtml(day.label)}</i>\n${day.lines.join("\n")}`;
|
||||
});
|
||||
return `<b>${escapeHtml(league.name)}</b>\n${daySections.join("\n")}`;
|
||||
});
|
||||
|
||||
const dayBlocks = [];
|
||||
for (const key of [...days.keys()].sort()) {
|
||||
const day = days.get(key);
|
||||
const leagueSections = groupByLeague(day.events).map(renderLeagueSection);
|
||||
dayBlocks.push(`<b>${escapeHtml(day.label)}</b>\n${leagueSections.join("\n")}`);
|
||||
}
|
||||
return `${header}\n\n${dayBlocks.join("\n\n")}`;
|
||||
return `${header}\n\n${leagueBlocks.join("\n\n")}`;
|
||||
}
|
||||
|
||||
@@ -153,12 +153,8 @@ describe("renderWeek", () => {
|
||||
).toContain("No matches this week.");
|
||||
});
|
||||
|
||||
it("nests leagues under each ICT day in chronological order", () => {
|
||||
it("groups by league first then nests days chronologically", () => {
|
||||
const events = [
|
||||
evt({
|
||||
startTime: "2026-04-21T09:00:00Z",
|
||||
league: { name: "LCK", slug: "lck" },
|
||||
}),
|
||||
evt({
|
||||
startTime: "2026-04-22T09:00:00Z",
|
||||
league: { name: "LPL", slug: "lpl" },
|
||||
@@ -167,15 +163,22 @@ describe("renderWeek", () => {
|
||||
startTime: "2026-04-22T11:00:00Z",
|
||||
league: { name: "LCK", slug: "lck" },
|
||||
}),
|
||||
evt({
|
||||
startTime: "2026-04-21T09:00:00Z",
|
||||
league: { name: "LCK", slug: "lck" },
|
||||
}),
|
||||
];
|
||||
const out = renderWeek(
|
||||
events,
|
||||
new Date("2026-04-21T00:00:00Z"),
|
||||
new Date("2026-04-28T00:00:00Z"),
|
||||
);
|
||||
expect(out.indexOf("Apr 21")).toBeLessThan(out.indexOf("Apr 22"));
|
||||
// Apr 22: LCK section appears before LPL (per LEAGUE_ORDER)
|
||||
const apr22Block = out.split("Apr 22")[1] || "";
|
||||
expect(apr22Block.indexOf("<b>LCK</b>")).toBeLessThan(apr22Block.indexOf("<b>LPL</b>"));
|
||||
// Top-level: LCK section appears before LPL (per LEAGUE_ORDER).
|
||||
expect(out.indexOf("<b>LCK</b>")).toBeLessThan(out.indexOf("<b>LPL</b>"));
|
||||
// Inside LCK: Apr 21 before Apr 22.
|
||||
const lckBlock = out.split("<b>LCK</b>")[1].split("<b>LPL</b>")[0];
|
||||
expect(lckBlock.indexOf("Apr 21")).toBeLessThan(lckBlock.indexOf("Apr 22"));
|
||||
// Day labels use italic inside league sections (distinct from league bold).
|
||||
expect(lckBlock).toContain("<i>Tue Apr 21</i>");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user