バッチ処理だけどYoutube Auth認証が必要だったので楽に何とかする

# はじめに

趣味でこんなものを作りました。
https://www.youtube.com/channel/UCArxaN-Q-rsW3TCgW3NhW2A/playlists

Youtubeのプレイリストを自動生成するスクリプトをcron的な感じで走らせているのですが、プレイリスト作成のためにはAuth認証してAPIを叩く必要がありました。

自分のYoutubeアカウントで認証したいだけで不特定多数のユーザが認証するわけでは無いので、真面目にAuth認証を作るのは面倒だなと思い、いい方法無いかなと思っていたらいい方法があったのでそのメモです。

# 前知識

- Youtube Data API
    - read系の操作はユーザ認証は不要
    - write系の操作はユーザ認証が必要
    - https://developers.google.com/youtube/v3/getting-started?hl=ja#supported-operations
- 一度refresh_tokenさえ取得してしまえば以後バッチ処理は動かせるので、それだけ手動でとってくる

# やったこと

サンプルコードはjsですが別になんでも良いです。

## その1: client_id, client_secretを生成する

<img width="1284" alt="認証情報_-_nijisanji-playlist_-_Google_API_コンソール.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7694/839dcd8a-2e29-1400-6a93-3801b6d9011d.png">

↑画像のような `タイプ: ウェブアプリケーション, OAuth2.0クライアント` のIDを発行しておきます。

## その2: 認証URLを叩く

```js
//
// script1.js
//

// その1で取得したclient_id
const clientId = "xxx.apps.googleusercontent.com";

// redirectUrlは実際にサーバを立てる必要はない。localhostならまあなんでも良い
const redirectUrl = "http://localhost:8000";

const scope = "https://www.googleapis.com/auth/youtube";

const url = [
  "https://accounts.google.com/o/oauth2/auth?",
  `client_id=${clientId}&`,
  `redirect_uri=${encodeURIComponent(redirectUrl)}&`,
  `scope=${scope}&`,
  "response_type=code&",
  "access_type=offline"
].join("");

console.log(url);
```

```
$ node script1.js
https://accounts.google.com/o/oauth2/auth?client_id=xxx.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8000&scope=https://www.googleapis.com/auth/youtube&response_type=code&access_type=offline
```

↑このURLをブラウザで開く。

## その3: refresh_tokenを取得する

その2で生成したURLを叩いてYoutube認証すると、下記のようなlocalhostにリダイレクトされます。

`http://localhost:8000/?code=aaa&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube#`

サーバは立ててないのでブラウザのエラー画面になりますが特に問題ないです。
URLバーに含まれる`code`をコピーして、下記のスクリプトを走らせます。

```js
//
// script2.js
//

const body = {
  code: "aaa", // 先程取得したcode
  client_id: "xxx.apps.googleusercontent.com", // その1で取得したclient_id
  client_secret: "yyy",  // その1で取得したclient_secret
  redirect_uri: "http://localhost:8000", // その2と同じくサーバを立てる必要はない
  grant_type: "authorization_code"
};

const command = [
  "curl -X POST",
  '-H "Content-Type: application/json"',
  "-d",
  `'${JSON.stringify(body)}'`,
  "https://accounts.google.com/o/oauth2/token"
].join(" ");

console.log(command);
```

```
$ node script2.js
curl -X POST -H "Content-Type: application/json" -d '{"code":"aaa","client_id":"xxx.apps.googleusercontent.com","client_secret":"yyy","redirect_uri":"http://localhost:8000","grant_type":"authorization_code"}' https://accounts.google.com/o/oauth2/token

# ↑このコマンドをコピペして実行すると下記のレスポンスが返ってくる

{
  "access_token": "xxx",
  "expires_in": 3600,
  "refresh_token": "zzz", // これが欲しかった！！
  "scope": "https://www.googleapis.com/auth/youtube",
  "token_type": "Bearer"
}%

```

## その4: refresh_tokenを使って認証が必要なAPIを叩く

```js
//
// script3.js
//

import { google } from "googleapis";

const getAuthorizedYoutubeClient = () => {
  const OAuth2 = google.auth.OAuth2;
  const clientId = "xxx.apps.googleusercontent.com";
  const clientSecret = "yyy";
  const refreshToken = "zzz";
  const authClient = new OAuth2(
    clientId,
    clientSecret,
    "http://localhost:8000"
  );

  authClient.setCredentials({ refresh_token: refreshToken });
  return google.youtube({
    version: "v3",
    auth: authClient
  });
};

const client = getAuthorizedYoutubeClient();
client.playlists.insert({
  part: "snippet,status",
  requestBody: {
    snippet: {
      title: "playlist title",
      description: "playlist description"
    },
    status: {
      privacyStatus: "public"
    }
  }
}).then(result => {
  console.log(result);
});
```

これで勝手にrefresh_tokenを使って認証してAPIを叩いてくれるようになります。
このライブラリとても便利ですね。 :pray: 

# その他感想

- バッチ処理用にserver_key的なtokenを発行してくれたら楽なんだけど、そういう仕組みは無さそうだったので、今回みたいな方法をとった
- Youtube Data APIのクォータの使用量の制限がシビアすぎて泣ける
    - https://developers.google.com/youtube/v3/getting-started?hl=ja#quota
    - 処理がバグっててプレイリスト間違えて作っちゃった → 消す → 処理直して再度プレイリスト生成する → 使用量オーバーのエラー
        - こんな感じで、動作確認しようとしたら翌日まで待つ羽目になった
- 根本的な問題だけどrefresh_tokenのexpireがどれくらいなのか不明
    - https://stackoverflow.com/questions/8953983/do-google-refresh-tokens-expire
    - ↑このやりとりでは 「expireしない」 「大量にrefresh_tokenを発行すると古いものからinvalidになる」 という記述がある
- 記事書いてから気づいたけど、本家にも似たようなドキュメントがあった
    - https://developers.google.com/youtube/v3/guides/moving_to_oauth?hl=ja