[Go]GAEとローカルでYoutubeDataAPIを使用する

## はじめに

YoutubeDataAPIを使用するとYoutubeのチャンネルやアップロードされた動画の情報を取得することができます。
今回はYoutubeDataAPIをGAEとローカル環境で使用する方法について記載します。
使用する言語は**go**です。
YoutubeDataAPIを使用する際に認証方法としてよくAPIキーによる方法が使用されていますが、GAEで使用する場合は**GAEデフォルトのサービスアカウントを使用することもできます**。
この記事では認証にサービスアカウントを使用します。

## プロジェクト準備

適当なGCPプロジェクトを作成し、GAEにアプリケーションをデプロイします。
アプリケーションをデプロイすることでデフォルトのサービスアカウントが作成されるので内容は何でもいいのでデプロイしてください。

デプロイすると `IAMと管理 > サービスアカウント`に`App Engine default service account`という名前のサービスアカウントが作成されています。
このアカウントのキーを作成し、キーのタイプはJSONで保存してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42193/5b2d995e-d89b-2964-1909-f59ea1d349f2.png)

このキーはローカルでYoutubeDataAPIを使用する際に必要になります。


次にGCPコンソールの上部の検索ウィンドウから`Youtube`と検索し、でてきた`Youtube Data API v3`を有効化します。

これでプロジェクトの準備は完了です。

## YoutubeDataAPIを使用する

以下のコマンドで必要ライブラリをインストールします。

```sh
$ go get golang.org/x/oauth2/google
$ go get google.golang.org/api/youtube/v3
```

ローカルで使用する場合は環境変数`GOOGLE_APPLICATION_CREDENTIALS`にプロジェクト準備で取得したサービスアカウントのキーへのパスを設定します。
GAEで使用する場合は特に何もする必要はありません。

後は以下のコードで使用することができます。
詳細はコメントを参照してください。

```go:main.go
// 特定のチャンネルの動画を5件取得し、そのタイトル、投稿日時、長さを表示する
package main

import (
	"context"
	"log"
	"strings"

	"golang.org/x/oauth2/google"
	"google.golang.org/api/youtube/v3"
)

// video 取得した動画の情報を表す構造体
type video struct {
	id          string
	title       string
	publishedAt string
}

func hoge() {
	ctx := context.Background()
	// サービスアカウントを使用した認証用のhttpクライアントを作成
	// 環境変数`GOOGLE_APPLICATION_CREDENTIALS`が設定されている場合は自動で必要な設定をしてくれる
	client, err := google.DefaultClient(ctx, youtube.YoutubeReadonlyScope)
	if err != nil {
		log.Fatalf("Error creating google client: %v", err)
	}

	// youtubeAPIサービスの作成
	service, err := youtube.New(client)
	if err != nil {
		log.Fatalf("Error creating YouTube client: %v", err)
	}

	// 取得するチャンネルのID
	const channelID = "UCLhUvJ_wO9hOvv_yYENu4fQ"

	channelRes, err := service.Channels.List("contentDetails").Id(channelID).Do()
	if err != nil {
		log.Fatalf("Cannot get channel: %v", err)
	}

	channel := channelRes.Items[0]
	// アップロードされた動画から5件を取得する
	// 並び順は必ずしも公開日順とは限らないがだいたい公開日順に並んでいる
	// (最新の動画が先頭ではない可能性がある。アップロード順という説もあるが詳細不明。)
	// 最大50件まで同時に取得可能
	// 50件より多く取得したい場合はNextPageTokenを使用する
	itemRes, err := service.PlaylistItems.List("snippet").
		PlaylistId(channel.ContentDetails.RelatedPlaylists.Uploads).
		MaxResults(5).
		Do()
	if err != nil {
		log.Fatalf("Cannot get playlistitem: %v", err)
	}

	videos := make([]video, 0, len(itemRes.Items))
	videoIDs := make([]string, 0, len(itemRes.Items))
	for _, it := range itemRes.Items {
		// PlaylistItemsでは動画の長さがわからない
		v := video{
			id:          it.Snippet.ResourceId.VideoId,
			title:       it.Snippet.Title,
			publishedAt: it.Snippet.PublishedAt,
		}
		videos = append(videos, v)
		videoIDs = append(videoIDs, it.Snippet.ResourceId.VideoId)
	}

	// 動画の詳細情報を取得する
	videoIDsStr := strings.Join(videoIDs, ",")
	videoRes, err := service.Videos.List("contentDetails").Id(videoIDsStr).Do()
	durationMap := map[string]string{}
	for _, v := range videoRes.Items {
		durationMap[v.Id] = v.ContentDetails.Duration
	}

	// タイトル、投稿日時、長さを表示する
	for _, v := range videos {
		log.Printf("%v - %v - %v", v.title, v.publishedAt, durationMap[v.id])
	}
}
```

```text:実行結果
2020/02/02 21:57:46 ポケモン都市伝説 - 2020-02-02T10:00:13.000Z - PT5M34S
2020/02/02 21:57:46 今回はすごく欲求不満になりました…【アプリ実況】 - 2020-02-01T10:00:03.000Z - PT12M24S
2020/02/02 21:57:46 感情を縛られた結果、どうすることもできませんでした。ごめんなさい【#シロ生放送】 - 2020-01-30T12:30:00.000Z - PT1H21M24S
2020/02/02 21:57:46 同じ動画素材を全く違う編集にしてみたらどうなる？ - 2020-01-31T10:00:11.000Z - PT5M43S
2020/02/02 21:57:46 絶対に再生を止められない動画 - 2020-01-29T10:00:05.000Z - PT4M19S
```