IFTTT + GASでYouTube視聴ログをとる

## やりたいこと
YouTubeをつい見すぎてしまうので、
- 視聴時間を記録して
- 毎日通知してもらう

システムを作ることで、レコーディングダイエットの要領で視聴時間を減らしたい。

## 実現できること
動画を高評価すると、スプシ上でこうやって記録されて、
![スクリーンショット 2023-07-16 12.00.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/4cd013ca-ff1b-00b3-3ade-0ecf24a1e51d.png)

毎日スマホにこういう通知が来ます。
![IMG_7873.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/f5decb16-da7a-f727-2bad-6ee675f0aa5e.jpeg)

### なぜ高評価なのか？
視聴履歴、再生リストに追加、OSの共有メニュー、PCならブラウザ拡張機能など、トリガーにできそうなものは他にもありますが、なぜ高評価を使うのか？　について言及しておきます。

#### 視聴画面から1タップorクリックでできる（どんな端末でも）
面倒くさがりな人間なので、とにかく少ないアクションでできる操作であることが何より重要です。
FireTVなどの場合はちょっと手間ですが、仕方ない。
私はFireTVを使うとすればタブレットからキャストするので、その場合はタブレットから高評価ボタンが押せます。

#### IFTTTがトリガーとして用意している
再生するだけではIFTTTはキャッチできません。プレイリストへの追加でも同様のようです。

#### 動画投稿者も喜ぶ
面白い動画を作ってくれているんだから、少しでもお返ししたい。向こうも「高評価お願いします」って言ってるし。
あまり面白くなかった動画は高評価しなかったり、まれに低評価を押したりするので記録されませんが、それも仕方ありません。
そもそもあまり面白くなさそうな動画は見ないようにすることです。運用でカバー。

### 端末のアプリ使用時間の記録（iOSならスクリーンタイム）ではダメなのか？
人によってはそれでも十分だと思います。
むしろその方が、飛ばし飛ばし見た動画、早送りした動画などの正確な視聴時間が分かるのでよさそうです。
私の場合は、AndroidでもWindowsでもiPhoneでも見る（見てしまう）ので、それでは不十分でした。

## 使うもの
- YouTube
- Google Spreadsheet（以下「スプシ」）
    - Google Drive上の適当な場所に作っておく
- [IFTTT](https://ifttt.com/)
    - 要Pro+プラン（月5ドル）
- スマホ
    - 通知受け取り用
    - IFTTTアプリを入れておく


## 概要
イメージ図がこちら。
![空白の図.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/26a8b7a9-234e-3671-3819-5fc28f501fb6.jpeg)

大きく、記録する部分と通知する部分の2つに分割できます。

### 記録部分
![空白の図 (1).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/b20f9d8b-d4a2-ac8a-b297-18306c1eb95f.jpeg)

1. YouTubeで、動画を高評価する
1. 高評価をIFTTTがキャッチして、スプシの新しい行に書き込む
    1. A列に視聴日時（厳密には高評価を押したタイミング）
    1. B列にURL
1. スプシへの書き込みをトリガーに、Google Apps Script（以下GAS）が起動する
    1. YouTube Data API v3で動画情報を取得する
    1. 取得した情報をC列以降に書き込む

### 通知部分
![空白の図 (2).jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/43d23f32-6323-4149-b358-459934a11b4b.jpeg)

1. 日時（毎日xx時xx分）をトリガーにしてIFTTTが起動する
    1. スプシの、前日の視聴時間合計を計算しているセルの値を取得する
    1. スマホに通知する

## 実装
実装について説明します。
とはいっても8割ノーコードですが。

### 記録部分
#### スプシを作っておく
![スクリーンショット 2023-07-16 12.18.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/9f5e4d10-409f-3ff5-0cd5-58c80eb9a3e0.png)
まずは書き込む先のスプシがなければ始まりません。適当な場所に作っておきます。
私は`LifeLog/YouTube`というパスにしました。

ヘッダーは後からでも作れますが、今回はあらかじめ作っておきます。
私は月ごとにシートを分けて記録したいので、月初に自動でGASでシートを作成するようにしています。

#### IFTTTのAppletを作る
- [IFTTT](https://ifttt.com/)にログインして、右上の`Create`をクリック
- `If This`の部分に、`YouTube - New liked video`を選択
    - 記録したいYouTubeアカウントを紐つける
- `Then That`の部分に、`Google Sheets - Add row to spreadsheet`を選択
    - スプシの持ち主のGoogleアカウントを紐つける
    - `Formatted row`は別のところで設定するのでデフォルトのままでOK
    - `Spreadsheet name`、`Drive folder path`を設定
        - 私の場合は`Spreadsheet name`が`YouTube`、`Drive folder path`が`LifeLog`
- `If`と`Then`の間にある`+`をクリック → `Filter code`を追加
    - 以下のコードを入力して、右上の`Update filter`
```javascript
// YouTubeのトリガーからはシステム日付が取得できないので、ここで取得する
let current = Meta.currentUserTime.format("YYYY/MM/DD HH:mm");
// スプシのアクションのFormatted rowを上書きする
GoogleSheets.appendToGoogleSpreadsheet.setFormattedRow(
  `${current} ||| ${Youtube.newLikedVideo.Url} `
);
```

これで第一段階は完成です。以下のようになっていればOK。
![スクリーンショット 2023-07-16 12.50.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/1477ad7f-23eb-60e3-4454-f5e031fe1500.png)

`Continue`をクリックして、適当な名前をつけてFinishすれば作成完了です。
試しにYouTubeで適当な動画を高評価して、スプシに書き込まれるか確認してみましょう。
反映が遅いときは、`Check now`するとすぐに反映されます。
![スクリーンショット 2023-07-16 12.52.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361302/6f4337ff-801c-2966-2f05-ed92355ce08a.png)

#### 動画情報を取得するGASを作る
- スプシの画面で、`拡張機能 - Apps Script`
- 画面左の`ライブラリ`の+ボタン
    - スクリプトIDに`15hgNOjKHUG4UtyZl9clqBbl23sDvWMS8pfDJOyIapZk5RBqwL3i-rlCo`を入力して検索→Momentを追加
- 画面左の`サービス`の+ボタン
    - YouTube Data API v3を選択して追加
- 以下のコードを入力する
<details><summary>GASのコード</summary>


```javascript
moment = Moment.load();

const URL_COL_NUM = 2;
const URL_COL_ALPHA = "B"

function getVideoData(url) {
  // URLからビデオIDを取得
  const splitedUrlArray = url.split('?v=')
  const videoId = splitedUrlArray[splitedUrlArray.length - 1]

  // APIを叩いて情報を取得
  const results = YouTube.Videos.list('snippet,contentDetails', { id: videoId });
  const { snippet, contentDetails } = results.items[0]
  const { title, channelTitle, thumbnails } = snippet
  const thumbnailUrl = thumbnails["medium"].url
  const { duration } = contentDetails
  return { title, channelTitle, thumbnailUrl, duration }
}

function updateRow(sheet, cell) {
  if (cell.getColumn() !== URL_COL_NUM) {
    return
  }
  const url = cell.getValue();
  const {
    title,
    channelTitle,
    thumbnailUrl,
    duration,
  } = getVideoData(url);
  const row = cell.getRow(); // アクティブな行を取得

  // 値を反映
  sheet.getRange(row, 3).setValue(`=IMAGE("${thumbnailUrl}")`);
  sheet.getRange(row, 4).setValue(title);
  sheet.getRange(row, 5).setValue(channelTitle);
  const durationInMinutes = moment.duration(duration).asMinutes().toFixed(2);
  sheet.getRange(row, 6).setValue(durationInMinutes);
}

// 指定した列に値がある最後の行を見つける
function findLastCell(sheet, columnAlphabet) {
  var columnVals = sheet.getRange(`${columnAlphabet}:${columnAlphabet}`).getValues(); // URL列の全ての値を取得
  
  var lastRow = 0;
  for(var i = 0; i < columnVals.length; i++){
    if(columnVals[i][0] != ""){
      lastRow = i;
    }
  }

  // 行のインデックスは1から始まる
  lastRow = lastRow + 1;

  return sheet.getRange(lastRow, URL_COL_NUM);
}

function update() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const cell = findLastCell(sheet, URL_COL_ALPHA);
  Logger.log(`cell: ${cell.getColumn()}, ${cell.getRow()}`)

  updateRow(sheet, cell)

}
```
</details>
参考：

https://zenn.dev/sungvalley/articles/ccce17e076984f

APIから取得したいデータ、スプシに書き込みたいデータは人によって違うでしょうから、`getVideoData()`関数、`updateRow()`関数を適宜書き換えるといいでしょう。
APIからどんなデータが取れるかは[リファレンス](https://developers.google.com/youtube/v3/docs/videos?hl=ja#resource)を参照。

更新対象の指定方法も工夫の余地があると思います。
リアルタイムでの更新が必要でなければ、「スプシ上のボタンを押したら全ての行を更新する」という形もありです。
今回は「B列に値が入っている1番下のセル」を指定しています。

#### GASのトリガーを設定する
- GASのページ左側の`トリガー`
- `トリガーを追加`
    - 実行する関数：update
    - 実行するデプロイ：Head
    - イベントのソース：スプレッドシートから
    - イベントの種類：変更時
    - エラー通知設定：お好みで
- `保存`
    - 「承認されてない関数だけどいいですか」的な警告が出ますが、無視します
    - 参考：[【初心者向けGAS】スクリプト実行時の「承認」でびっくりしないために](https://tonari-it.com/gas-script-approval/#toc10)

これで、IFTTTによる書き込みをトリガーにして、C列以降に動画の詳細を自動で書き込んでくれるようになったはずです。
適当な動画を高評価してみましょう。

これで記録部分は完成です。

### 通知部分
記録部分よりずっとシンプルです。

#### スプシ側で、前日の合計視聴時間を算出するセルを置いておく
今回はD1セルに置きました。
A列に視聴日時、F列に動画の長さ（分）が入っている場合、以下の式になります。
```
=SUMIFS(F:F, ARRAYFORMULA(INT(A:A)), TEXT(TODAY()-1, "yyyy-mm-dd"))
```

#### IFTTTでAppletを作る
- `If This`の部分に、`Date & Time - Every day at`を選択
    - 通知したい時刻を設定（15分刻みで設定可）
- `If`と`Then`の間にある`+` → `Query - Google Sheets - Current value of a cell`を追加
    - セル名とスプシのURLを指定
- `Then That`の部分に、`Notifications - Send a notification from the IFTTT app`を選択
    - Messageに以下を設定
        - `昨日のYouTube視聴時間は{{GoogleSheets.cellValue.Value}}分でした。 `

<details><summary>（オプション）視聴時間に応じてメッセージを変えたい場合</summary>

- `With`と`Then`の間にある`+`→`Filter code`
    - 以下のコードを入力して、右上の`Update filter`
```javascript
const minutes = parseFloat(GoogleSheets.cellValue[0].Value);
var message = `昨日のYouTube視聴時間は${minutes}分でした。`;
if (minutes <= 30) {
  message += '素晴らしい！ 誘惑に見事打ち勝ちましたね！';
} else if (minutes <= 60) {
  message += 'ほどよい息抜きになったようですね。';
} else if (minutes <= 120) {
  message += 'たっぷりと楽しみましたね。今日は本を読む時間を増やしてみては？';
} else {
  message +=
    '誘惑に負けてしまっているようです。本当に面白いと思って見てますか？';
}

IfNotifications.sendNotification.setMessage(message);
```

</details>

これで完成です。
スマホにIFTTTアプリをインストールしてログインしておくと、毎日通知が来ます。

## おわりに
これで当初の目的通りYouTube依存度を減らせるかどうかは、また別の話。