diff --git a/CLAUDE.md b/CLAUDE.md index 091d744..0bc5edf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is an Indiekit plugin that adds a YouTube channel endpoint. It displays latest videos and live streaming status from a YouTube channel, with both an admin dashboard and public JSON API endpoints. +This is an Indiekit plugin that adds a YouTube channel endpoint. It displays latest videos and live streaming status from YouTube channels (supports both single and multiple channels), with an admin dashboard and public JSON API endpoints. ## Development @@ -18,22 +18,61 @@ No test suite is configured. Testing requires a running Indiekit instance with v - Exports a `YouTubeEndpoint` class that Indiekit loads as a plugin - Registers protected routes (admin dashboard) and public routes (JSON API) - Stores configuration in `Indiekit.config.application.youtubeConfig` for controller access +- Registers navigation items and shortcuts in Indiekit's UI +- Supports both single channel (backward compatible) and multi-channel mode **YouTube API Client** (`lib/youtube-client.js`): - Handles all YouTube Data API v3 interactions - Implements in-memory caching with configurable TTL - Uses uploads playlist method for quota efficiency (2 units) instead of search (100 units) - Channel info cached for 24 hours; videos cached per `cacheTtl` option +- Two live status methods: `getLiveStatus()` (expensive, 100 units) and `getLiveStatusEfficient()` (cheap, 2 units) +- Channel resolution: accepts either `channelId` (UC...) or `channelHandle` (@username) + - `channelHandle` is resolved via `forHandle` API parameter (removes @ prefix automatically) **Controllers** (`lib/controllers/`): -- `dashboard.js` - Admin page rendering, cache refresh -- `videos.js` - `/api/videos` JSON endpoint -- `channel.js` - `/api/channel` JSON endpoint -- `live.js` - `/api/live` JSON endpoint with efficient vs full search modes +- `dashboard.js` - Admin page rendering, multi-channel display, cache refresh via POST +- `videos.js` - `/api/videos` JSON endpoint (supports multi-channel aggregation) +- `channel.js` - `/api/channel` JSON endpoint (returns array for multi-channel mode) +- `live.js` - `/api/live` JSON endpoint with efficient vs full search modes (`?full=true`) +- All controllers support both single-channel (backward compatible) and multi-channel modes +- Multi-channel responses include both aggregated data and per-channel breakdowns **Views/Templates**: -- `views/youtube.njk` - Admin dashboard template (Nunjucks) -- `includes/@indiekit-endpoint-youtube-widget.njk` - Widget component +- `views/youtube.njk` - Admin dashboard template with multi-channel support +- `includes/@indiekit-endpoint-youtube-widget.njk` - Homepage widget for Indiekit admin +- `includes/@indiekit-endpoint-youtube-live.njk` - Reusable live status partial +- `includes/@indiekit-endpoint-youtube-videos.njk` - Reusable video list partial (compact) + +**Locales**: +- `locales/en.json` - English translations for all UI strings + +## Configuration Modes + +### Single Channel (Backward Compatible) +```javascript +{ + channelId: "UC...", + // OR + channelHandle: "@username" +} +``` + +### Multi-Channel +```javascript +{ + channels: [ + { id: "UC...", name: "Channel 1" }, + { handle: "@username", name: "Channel 2" } + ] +} +``` + +## API Response Shapes + +**Single channel mode**: Flat responses (e.g., `{ videos: [...] }`) + +**Multi-channel mode**: Includes both aggregated flat data (for backward compat) and per-channel breakdowns (e.g., `{ videos: [...], videosByChannel: {...} }`) ## API Quota Considerations @@ -41,7 +80,18 @@ YouTube Data API has a 10,000 units/day default quota: - `channels.list`, `playlistItems.list`, `videos.list`: 1 unit each - `search.list`: 100 units (used only for full live status check with `?full=true`) -The plugin uses the playlist method by default for quota efficiency. +The plugin uses the playlist method by default for quota efficiency. With default caching (5 min), single channel uses ~600 units/day. Multi-channel multiplies by number of channels. + +## Routes + +**Protected (require auth)**: +- `GET /youtube/` - Dashboard +- `POST /youtube/refresh` - Clear cache and refetch data (returns JSON) + +**Public JSON API**: +- `GET /youtube/api/videos?limit=N` - Latest videos +- `GET /youtube/api/channel` - Channel info +- `GET /youtube/api/live?full=true` - Live status (efficient by default, add `?full=true` for search API) ## Workspace Context diff --git a/README.md b/README.md index 0746984..c0d59d5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ YouTube channel endpoint for [Indiekit](https://getindiekit.com/). -Display latest videos and live streaming status from any YouTube channel on your IndieWeb site. +Display latest videos and live streaming status from any YouTube channel (or multiple channels) on your IndieWeb site. ## Installation @@ -17,7 +17,8 @@ npm install @rmdes/indiekit-endpoint-youtube ## Features -- **Admin Dashboard** - Overview of channel with latest videos in Indiekit's admin UI +- **Single or Multi-Channel** - Monitor one channel or aggregate multiple channels +- **Admin Dashboard** - Overview of channel(s) with latest videos in Indiekit's admin UI - **Live Status** - Shows when channel is live streaming (with animated badge) - **Upcoming Streams** - Display scheduled upcoming live streams - **Latest Videos** - Grid of recent uploads with thumbnails, duration, view counts @@ -27,6 +28,8 @@ npm install @rmdes/indiekit-endpoint-youtube ## Configuration +### Single Channel + Add to your `indiekit.config.js`: ```javascript @@ -50,6 +53,39 @@ export default { }; ``` +### Multiple Channels + +Monitor multiple YouTube channels simultaneously: + +```javascript +import YouTubeEndpoint from "@rmdes/indiekit-endpoint-youtube"; + +export default { + plugins: [ + new YouTubeEndpoint({ + mountPath: "/youtube", + apiKey: process.env.YOUTUBE_API_KEY, + channels: [ + { id: "UC...", name: "Main Channel" }, + { handle: "@SecondChannel", name: "Second Channel" }, + { id: "UC...", name: "Third Channel" }, + ], + cacheTtl: 300_000, + liveCacheTtl: 60_000, + limits: { + videos: 10, + }, + }), + ], +}; +``` + +In multi-channel mode: +- Dashboard shows all channels with separate sections +- API endpoints aggregate data from all channels +- Videos are sorted by date across all channels +- Live status shows any channel that is currently live + ## Environment Variables | Variable | Required | Description | @@ -58,7 +94,7 @@ export default { | `YOUTUBE_CHANNEL_ID` | Yes* | Channel ID (starts with `UC...`) | | `YOUTUBE_CHANNEL_HANDLE` | Yes* | Channel handle (e.g., `@YourChannel`) | -*Either `channelId` or `channelHandle` is required. +*Either `channelId` or `channelHandle` is required for single-channel mode. In multi-channel mode, use the `channels` array instead. ### Getting a YouTube API Key @@ -81,7 +117,7 @@ export default { | Route | Description | |-------|-------------| | `GET /youtube/` | Dashboard with channel info, live status, latest videos | -| `POST /youtube/refresh` | Clear cache and refresh data | +| `POST /youtube/refresh` | Clear cache and refresh data (returns JSON) | ### Public API Routes (JSON) @@ -89,7 +125,8 @@ export default { |-------|-------------| | `GET /youtube/api/videos` | Latest videos (supports `?limit=N`) | | `GET /youtube/api/channel` | Channel information | -| `GET /youtube/api/live` | Live streaming status | +| `GET /youtube/api/live` | Live streaming status (efficient by default) | +| `GET /youtube/api/live?full=true` | Live status using search API (more accurate, costs more quota) | ### Example: Eleventy Integration @@ -119,6 +156,7 @@ export default async function() { ### GET /youtube/api/live +**Single channel:** ```json { "isLive": true, @@ -133,8 +171,34 @@ export default async function() { } ``` +**Multi-channel:** +```json +{ + "isLive": true, + "isUpcoming": false, + "stream": { + "videoId": "abc123", + "title": "Live Stream Title" + }, + "liveStatuses": [ + { + "channelConfigName": "Main Channel", + "isLive": true, + "stream": { "videoId": "abc123" } + }, + { + "channelConfigName": "Second Channel", + "isLive": false, + "stream": null + } + ], + "cached": true +} +``` + ### GET /youtube/api/videos +**Single channel:** ```json { "videos": [ @@ -155,17 +219,75 @@ export default async function() { } ``` +**Multi-channel:** +```json +{ + "videos": [], + "videosByChannel": { + "Main Channel": [], + "Second Channel": [] + }, + "count": 20, + "cached": true +} +``` + +### GET /youtube/api/channel + +**Single channel:** +```json +{ + "channel": { + "id": "UC...", + "title": "Channel Name", + "description": "Channel description", + "thumbnail": "https://...", + "subscriberCount": 12345, + "videoCount": 100, + "viewCount": 999999 + }, + "cached": true +} +``` + +**Multi-channel:** +```json +{ + "channels": [ + { "id": "UC...", "title": "Channel 1", "configName": "Main Channel" }, + { "id": "UC...", "title": "Channel 2", "configName": "Second Channel" } + ], + "channel": {}, + "cached": true +} +``` + ## Options | Option | Default | Description | |--------|---------|-------------| | `mountPath` | `/youtube` | URL path for the endpoint | | `apiKey` | - | YouTube Data API key | -| `channelId` | - | Channel ID (UC...) | -| `channelHandle` | - | Channel handle (@...) | +| `channelId` | - | Channel ID (UC...) - single channel mode | +| `channelHandle` | - | Channel handle (@...) - single channel mode | +| `channels` | `null` | Array of channels for multi-channel mode | | `cacheTtl` | `300000` | Cache TTL in ms (5 min) | | `liveCacheTtl` | `60000` | Live status cache TTL in ms (1 min) | -| `limits.videos` | `10` | Number of videos to fetch | +| `limits.videos` | `10` | Number of videos to fetch per channel | + +### Channels Array Format + +For multi-channel mode, the `channels` option accepts an array of objects: + +```javascript +channels: [ + { id: "UC...", name: "Display Name" }, // Using channel ID + { handle: "@username", name: "Display Name" }, // Using handle + { id: "UC..." } // Name defaults to channel title +] +``` + +Either `id` or `handle` is required. The `name` field is optional and used for display purposes. ## Quota Efficiency @@ -175,16 +297,19 @@ YouTube Data API has a daily quota (10,000 units by default). This plugin is opt |-----------|------------|--------| | Get videos | 2 units | Uses uploads playlist (not search) | | Get channel | 1 unit | Cached for 24 hours | -| Check live status | 2 units | Checks recent videos (efficient) | -| Full live search | 100 units | Only when explicitly requested | +| Check live status (efficient) | 2 units | Checks recent videos | +| Check live status (full) | 100 units | Only when explicitly requested | -With default settings (5-min cache), you'll use ~600 units/day for video checks. +**Single channel:** With default settings (5-min cache), ~600 units/day. + +**Multi-channel:** Quota usage scales linearly. 3 channels = ~1,800 units/day. ## Requirements - Indiekit >= 1.0.0-beta.25 - YouTube Data API v3 enabled - Valid API key with YouTube Data API access +- Node.js >= 20 ## License