# @rmdes/indiekit-endpoint-youtube [![npm version](https://img.shields.io/npm/v/@rmdes/indiekit-endpoint-youtube.svg)](https://www.npmjs.com/package/@rmdes/indiekit-endpoint-youtube) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) YouTube channel endpoint for [Indiekit](https://getindiekit.com/). Display latest videos and live streaming status from any YouTube channel (or multiple channels) on your IndieWeb site. ## Installation Install from npm: ```bash npm install @rmdes/indiekit-endpoint-youtube ``` ## Features - **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 - **Public JSON API** - For integration with static site generators like Eleventy - **Quota Efficient** - Uses YouTube API efficiently (playlist method vs search) - **Smart Caching** - Respects API rate limits while staying current ## Configuration ### Single Channel Add to your `indiekit.config.js`: ```javascript import YouTubeEndpoint from "@rmdes/indiekit-endpoint-youtube"; export default { plugins: [ new YouTubeEndpoint({ mountPath: "/youtube", apiKey: process.env.YOUTUBE_API_KEY, channelId: process.env.YOUTUBE_CHANNEL_ID, // OR use channel handle instead: // channelHandle: "@YourChannel", cacheTtl: 300_000, // 5 minutes liveCacheTtl: 60_000, // 1 minute for live status limits: { videos: 10, }, }), ], }; ``` ### 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 | |----------|----------|-------------| | `YOUTUBE_API_KEY` | Yes | YouTube Data API v3 key | | `YOUTUBE_CHANNEL_ID` | Yes* | Channel ID (starts with `UC...`) | | `YOUTUBE_CHANNEL_HANDLE` | Yes* | Channel handle (e.g., `@YourChannel`) | *Either `channelId` or `channelHandle` is required for single-channel mode. In multi-channel mode, use the `channels` array instead. ### Getting a YouTube API Key 1. Go to [Google Cloud Console](https://console.cloud.google.com/) 2. Create a new project or select an existing one 3. Enable the "YouTube Data API v3" 4. Go to Credentials > Create Credentials > API Key 5. (Optional) Restrict the key to YouTube Data API only ### Finding Your Channel ID - Go to your YouTube channel - The URL will be `youtube.com/channel/UC...` - the `UC...` part is your channel ID - Or use a tool like [Comment Picker](https://commentpicker.com/youtube-channel-id.php) ## Routes ### Admin Routes (require authentication) | Route | Description | |-------|-------------| | `GET /youtube/` | Dashboard with channel info, live status, latest videos | | `POST /youtube/refresh` | Clear cache and refresh data (returns JSON) | ### Public API Routes (JSON) | Route | Description | |-------|-------------| | `GET /youtube/api/videos` | Latest videos (supports `?limit=N`) | | `GET /youtube/api/channel` | Channel information | | `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 ```javascript // _data/youtube.js import EleventyFetch from "@11ty/eleventy-fetch"; export default async function() { const baseUrl = process.env.SITE_URL || "https://example.com"; const [channel, videos, live] = await Promise.all([ EleventyFetch(`${baseUrl}/youtube/api/channel`, { duration: "15m", type: "json" }), EleventyFetch(`${baseUrl}/youtube/api/videos?limit=6`, { duration: "5m", type: "json" }), EleventyFetch(`${baseUrl}/youtube/api/live`, { duration: "1m", type: "json" }), ]); return { channel: channel.channel, videos: videos.videos, isLive: live.isLive, liveStream: live.stream, }; } ``` ## API Response Examples ### GET /youtube/api/live **Single channel:** ```json { "isLive": true, "isUpcoming": false, "stream": { "videoId": "abc123", "title": "Live Stream Title", "thumbnail": "https://i.ytimg.com/vi/abc123/mqdefault.jpg", "url": "https://www.youtube.com/watch?v=abc123" }, "cached": true } ``` **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": [ { "id": "abc123", "title": "Video Title", "thumbnail": "https://i.ytimg.com/vi/abc123/mqdefault.jpg", "duration": 3661, "durationFormatted": "1:01:01", "viewCount": 12345, "publishedAt": "2024-01-15T10:00:00Z", "url": "https://www.youtube.com/watch?v=abc123", "isLive": false } ], "count": 10, "cached": true } ``` **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...) - 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 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. ## YouTube Likes Sync Sync your YouTube liked videos as "like" posts on your IndieWeb blog. Each liked video becomes a like post with the video URL, title, channel name, and thumbnail. ### Setup 1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials) 2. Create an **OAuth 2.0 Client ID** (Application type: Web application) 3. Add an authorized redirect URI: `https://yourdomain.com/youtube/likes/callback` 4. Make sure **YouTube Data API v3** is enabled for the project 5. Set the environment variables: | Variable | Required | Description | |----------|----------|-------------| | `YOUTUBE_OAUTH_CLIENT_ID` | Yes | OAuth 2.0 client ID | | `YOUTUBE_OAUTH_CLIENT_SECRET` | Yes | OAuth 2.0 client secret | 6. Add the OAuth config to your Indiekit configuration: ```javascript "@rmdes/indiekit-endpoint-youtube": { mountPath: "/youtube", apiKey: process.env.YOUTUBE_API_KEY, channelId: process.env.YOUTUBE_CHANNEL_ID, oauth: { clientId: process.env.YOUTUBE_OAUTH_CLIENT_ID, clientSecret: process.env.YOUTUBE_OAUTH_CLIENT_SECRET, }, likes: { syncInterval: 3_600_000, // 1 hour (default) maxPages: 3, // 50 likes per page, up to 150 per sync autoSync: true, // enable background sync }, }, ``` 7. Visit `/youtube/likes` in the Indiekit admin panel and click **Connect YouTube Account** 8. Authorize access — your refresh token is stored in MongoDB and persists across restarts ### Likes Options | Option | Default | Description | |--------|---------|-------------| | `oauth.clientId` | - | Google OAuth 2.0 client ID | | `oauth.clientSecret` | - | Google OAuth 2.0 client secret | | `likes.syncInterval` | `3600000` | Background sync interval in ms (1 hour) | | `likes.maxPages` | `3` | Max pages per sync (50 likes/page) | | `likes.autoSync` | `true` | Enable background periodic sync | ### Likes Routes #### Admin Routes (require authentication) | Route | Description | |-------|-------------| | `GET /youtube/likes` | OAuth status, sync info, and controls | | `GET /youtube/likes/connect` | Start OAuth flow (redirects to Google) | | `POST /youtube/likes/disconnect` | Remove stored tokens | | `POST /youtube/likes/sync` | Trigger manual sync | #### Public Routes | Route | Description | |-------|-------------| | `GET /youtube/likes/callback` | OAuth callback (Google redirects here) | | `GET /youtube/api/likes` | JSON API for synced likes (`?limit=N&offset=N`) | ### Likes API Response #### GET /youtube/api/likes ```json { "likes": [ { "post-type": "like", "like-of": "https://www.youtube.com/watch?v=abc123", "name": "Liked \"Video Title\" by Channel Name", "published": "2024-01-15T10:00:00Z", "url": "https://yourdomain.com/likes/yt-like-abc123/", "youtube-video-id": "abc123", "youtube-channel": "Channel Name", "youtube-thumbnail": "https://i.ytimg.com/vi/abc123/mqdefault.jpg" } ], "count": 20, "total": 142, "offset": 0 } ``` ### Likes Quota Usage Fetching liked videos uses `videos.list` with `myRating=like` — **1 quota unit per page** (50 videos). With default settings (3 pages per sync, hourly), that's ~72 units/day. ### Eleventy Integration for Likes ```javascript // _data/youtubeLikes.js import EleventyFetch from "@11ty/eleventy-fetch"; export default async function() { const baseUrl = process.env.SITE_URL || "https://example.com"; const data = await EleventyFetch( `${baseUrl}/youtube/api/likes?limit=50`, { duration: "15m", type: "json" } ); return data.likes; } ``` ## Quota Efficiency YouTube Data API has a daily quota (10,000 units by default). This plugin is optimized: | Operation | Quota Cost | Method | |-----------|------------|--------| | Get videos | 2 units | Uses uploads playlist (not search) | | Get channel | 1 unit | Cached for 24 hours | | Check live status (efficient) | 2 units | Checks recent videos | | Check live status (full) | 100 units | Only when explicitly requested | **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 MIT