feat: modular league adapters (NBA + WNBA) #2

Merged
freemasen merged 4 commits from ada/nba-calendar-utility:feat/modular-leagues into main 2026-05-20 00:01:56 +00:00
Contributor

What

Breaks the monolithic index.mjs into swapable modules so you can add new leagues without touching the calendar or config logic.

New structure

index.mjs              Entry point — reads config, picks adapter, runs the pipeline
src/
  game.js              Normalized Game class + toCalendarEvent()
  calendar.js          Google auth, clearEvents, saveEvents
  config.js            readConfig() with league field
  leagues/
    index.js           Re-exports all adapters
    nba.js             NBA adapter (cdn.nba.com)
    wnba.js            WNBA adapter (content-api-prod.nba.com)

NBA vs WNBA API differences

The NBA uses a nested leagueSchedule.gameDates[].games[] structure with homeTeam/awayTeam objects. The WNBA uses a flat results.schedule[] array with home/visitor objects. The adapter pattern normalizes both into Game instances.

Config

{ "league": "wnba", "teamCode": "NYL", "calendarId": "...", "year": 2026 }

The league field defaults to "nba" for backward compatibility.

Adding a league

Implement the adapter interface (see README) and register it in src/leagues/index.js and the LEAGUES map in index.mjs.

Also fixes

  • Replaced custom https.request fetch with node-fetch (already a dependency — the old one returned raw IncomingMessage so res.json() would not have worked)
  • Replaced Promise.sleep monkey-patch with local sleep()
  • Replaced bare throw "string" with throw new Error()
  • Added rate limiting on calendar inserts (was only on deletes before)
  • Added browser-like headers on NBA/WNBA API calls (CDN blocks non-browser UAs)
## What Breaks the monolithic `index.mjs` into swapable modules so you can add new leagues without touching the calendar or config logic. ### New structure ``` index.mjs Entry point — reads config, picks adapter, runs the pipeline src/ game.js Normalized Game class + toCalendarEvent() calendar.js Google auth, clearEvents, saveEvents config.js readConfig() with league field leagues/ index.js Re-exports all adapters nba.js NBA adapter (cdn.nba.com) wnba.js WNBA adapter (content-api-prod.nba.com) ``` ### NBA vs WNBA API differences The NBA uses a nested `leagueSchedule.gameDates[].games[]` structure with `homeTeam`/`awayTeam` objects. The WNBA uses a flat `results.schedule[]` array with `home`/`visitor` objects. The adapter pattern normalizes both into `Game` instances. ### Config ```json { "league": "wnba", "teamCode": "NYL", "calendarId": "...", "year": 2026 } ``` The `league` field defaults to `"nba"` for backward compatibility. ### Adding a league Implement the adapter interface (see README) and register it in `src/leagues/index.js` and the `LEAGUES` map in `index.mjs`. ### Also fixes - Replaced custom `https.request` fetch with `node-fetch` (already a dependency — the old one returned raw `IncomingMessage` so `res.json()` would not have worked) - Replaced `Promise.sleep` monkey-patch with local `sleep()` - Replaced bare `throw "string"` with `throw new Error()` - Added rate limiting on calendar inserts (was only on deletes before) - Added browser-like headers on NBA/WNBA API calls (CDN blocks non-browser UAs)
Break the monolithic index.mjs into separate modules:

- src/game.js: Normalized Game class with toCalendarEvent()
- src/calendar.js: Google auth, clearEvents, saveEvents
- src/config.js: readConfig() with league support
- src/leagues/nba.js: NBA adapter (cdn.nba.com schedule API)
- src/leagues/wnba.js: WNBA adapter (content-api-prod.nba.com)
- src/leagues/index.js: Re-exports all adapters

The WNBA API has a completely different URL and response shape
(flat game list with visitor/home objects) compared to the NBA API
(nested gameDates/games with homeTeam/awayTeam). The adapter pattern
normalizes both into Game instances so the calendar layer doesn't
need to know the difference.

Config now supports a 'league' field ('nba' or 'wnba'). The teamCode
and year fields work the same way. Adding a new league requires
implementing the adapter interface and registering it.

Also fixes: replaced custom https.request fetch with node-fetch
(the existing dependency), replaced Promise.sleep monkey-patch
with local sleep(), replaced bare throw strings with Error objects,
added rate limiting on calendar inserts.
The calendarId field now accepts:
- 'primary' (default calendar)
- An email address (e.g. ab12cd34@group.calendar.google.com)
- A full Google Calendar URL — we extract the ID from the URL so
  users can just paste what's in their browser

Supports embed URLs (?src=...), settings URLs, and calendar view URLs.
Restores the original approach of using Node's built-in http/https
modules instead of the node-fetch package. The custom fetch is extracted
into src/fetch.js so both league adapters share it. It supports custom
headers (needed for the NBA/WNBA CDN which blocks non-browser UAs) and
returns a minimal Response-like object with ok, status, json(), text().

node-fetch removed from package.json dependencies.
- Remove 'Legacy' and 'Multi-league' config format docs from config.js
- Remove unused seasonKey property from NBA/WNBA adapters and README
- Update config.json.example with real team code (NYK) instead of placeholder
- Clean up README config section to match the single config format
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
freemasen/nba-calendar-utility!2
No description provided.