Node.jsでYouTube Data APIを叩いてみる

##  この記事は
この記事は YouTube Data APIの `Video:list`, `Channel:list`, `Search:list` をNode.jsで叩いてみたメモ。

## (余談)記事制作の背景
余談だが、次のサービスを作るのにAPIを叩く必要があった。
特に生放送の開始時間、終了時間、配信継続時間(動画の再生長)が欲しかった。

https://hardcore-timetable.netlify.app/

...このサービスの内容を説明するのは記事の趣旨ではないので団長の思いで割愛する。

## 前提
Google Cloud Platform(GCP) でAPIキーを発行する必要があるが、このAPIキーは取得できているものとする。
いくつか記事を読んだが、 [こちら](https://qiita.com/koki_develop/items/4cd7de3898dae2c33f20)の記事がわかりやすかった。

## Videos:list
[Videos:list](https://developers.google.com/youtube/v3/docs/videos/list?hl=ja)は、動画情報のリストを取得するAPI。
ドキュメントでは３つのユースケースが説明されているが、今回は動画IDを指定して情報を取得する。

```node.js

const {google} = require("googleapis");
const youtube = google.youtube({
    version: "v3",
    auth: "**** API_KEY ***",
});
// ↑ 以降省略

(async () => {
    const result = await youtube.videos.list({
        part: ['id','snippet', 'contentDetails', 'liveStreamingDetails'],
        id: ['**** 動画ID ****', '**** 動画ID ****'],
    });
    console.log(result);
})();
```

### 時間系
次のjsonは、レスポンスのvideoの配列から自分が欲しかった、時間系のプロパティだけ抜き取ったもの。
これを得るには、 `part` に `['snippet', 'contentDetails', 'liveStreamingDetails']` を指定する必要がある...という寸法。

リソースの構造や説明は、[動画の概要ドキュメントのリソースの項目](https://developers.google.com/youtube/v3/docs/videos?hl=ja#resource)にある。

```json
[
  {
    "snippet": {
      "publishedAt": "2024-05-19T12:00:06Z",
    },
    "contentDetails": {
      "duration": "PT18M46S",
    },
    "liveStreamingDetails": {
      "actualStartTime": "2024-05-19T12:00:06Z",
      "actualEndTime": "2024-05-19T12:20:52Z",
      "scheduledStartTime": "2024-05-19T12:00:00Z"
    }
  }
]
```

|プロパティ|意味|
|---|---|
|publishedAt|アップロード動画の場合は公開日時。生放送の場合は枠を立てた（枠を公開した？）時刻
|duration|動画の再生長。 `PT#H#M#S` で表現されている
|actualStartTime|実際の配信開始日時
|actualEndTime|実際の配信終了日時
|scheduledStartTime|配信開始予定時刻。実際の配信開始時間ではない

`publishedAt` が実際の配信開始時刻と同じだと思いこんでいて少し悩んだ。
ちなみに...時刻は日本時間ではなくUTCなので9時間ズレている。


## Search:list
[Search:list](https://developers.google.com/youtube/v3/docs/search/list?hl=ja) は動画、チャンネル、プレイリストを検索するAPI。

今回は[ホロライブの所属タレント](https://hololive.hololivepro.com/talents/) のYouTubeチャンネルごとに、「マインクラフト」に類するワードを含む動画・配信の情報を取得する。

```node.js

(async () => {
	const result = await youtube.search.list({
        part: ['id', 'snippet', 'contentDetails', 'liveStreamingDetails'],
        q: 'MINECRAFT | minecraft | マインクラフト | マイクラ',
        type: ['video'],
        channelId,
        maxResults: 20,
        publishedAfter: '2024-05-12T00:00:00Z'
    });
    console.log(result.data.items);
})();

```


## Channels:list
[Channels:list](https://developers.google.com/youtube/v3/docs/channels/list?hl=ja)

アイコン画像など、チャンネルの情報を取得。

```node.js
(async () => {
    const result = await youtube.channels.list({
        part: [
            "id",
            "snippet",
        ],
        id: ['UC1DCedRgGHBdm81E1llLhOQ'],
    });
    console.log(result.data.items);
})();
```

## 再生長のパース
`PT18M46S` みたいな再生長で使われている書式の文字列をパースする処理かいたのでメモしとく。
[ISO 8601の継続時間(Durations)](https://ja.wikipedia.org/wiki/ISO_8601#%E7%B6%99%E7%B6%9A%E6%99%82%E9%96%93)という定義らしく、実際には年月日まであるらしい。
うまくいかないケースもあるだろうが、とりあえず手元で動かした分には問題なかった。
てかライブラリないのかな・・・。

```node.js
const parseDuration = (_s) => {
    const re = String(_s).match(/^PT((\d+)H)?((\d+)M)?((\d+)S)?$/);
    if (!re) return _s;

    const h = parseInt(re[2], 10) || 0;
    const m = parseInt(re[4], 10) || 0;
    const s = parseInt(re[6], 10) || 0;

    return {
        h,
        m,
        s,
    }
}
```