npm package: @rmdes/indiekit-endpoint-youtube
Description: YouTube channel endpoint for Indiekit. Display latest videos and live status from any YouTube channel.
Author: Ricardo Mendes
Latest version: 1.2.3

# @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.

## 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