YouTube Liveの配信予定を見逃したくない人のための記事【非エンジニア・初心者向け】【詳細版】


## この記事を読むと出来るようになること
- 記事中の**コードをコピペするなど**して、**YouTube Liveの枠立ての通知をGoogleカレンダーを通じて自動で受け取れる**ようになる。
- YouTube Liveの**配信予定をカレンダー上でいつでも確認出来る**ようになる。
- ひと手間加えれば**Appleカレンダーなど他のカレンダーアプリ経由でも通知を受け取れる**。

::: note info
基本的に**プログラミングの知識は不要です。**
:::

::: note info
VTuberや実況者、その他YouTubeで活動されている配信者を追っている方にオススメです
:::

# 0. 前提
## 0-1. 目標
- **YouTube Liveでの配信予定を見逃したくない**。
- <strong>枠立ての通知</strong>を事前に**自動で**受け取りたい。
- 配信予定を**カレンダー上でいつでも確認したい**。

## 0-2. 筆者が採った手法

1. <strong>GAS (Google Apps Script)</strong> 上で **YouTube Data API** と **Google Calendar API** を利用出来るように準備する。
2. インターネット上の情報を参考にしながら、ChatGPTと一緒にコーディングをする。
3. <strong> 配信予定（枠立て）を通知したいYouTubeチャンネルのチャンネルID</strong> を控えておく。
4. トリガーの基本的な設定をする。
5. 配信枠がGoogleカレンダーに正しく予定として追加されていることを確認する。

## 0-3. この記事の簡易版について
noteで**この記事の簡易版を公開しています**。
**難しいことやカスタマイズなどは抜きにしてまず使いたい**という方は、そちらをご覧いただくことも可能です。

https://note.com/s_note_01/n/n836ac489e0ff


# 1. まず最初にすること

## 1-1. Googleドライブに飛んでファイルを作成する。
![vivaldi_bHlI9d2Q3n_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/d3d0ad44-d1cc-a055-da44-11d9626f291e.png)
Googleドライブを開いたら適当なフォルダ上で「+ 新規」ボタンをクリック。
![vivaldi_GPk39eZahE_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/8177a9cc-4d52-4bce-7c2b-1081062ee9dd.png)
「その他」→「Google Apps Script」からGAS（Google Apps Script）ファイルを作成します。

## 1-2. コードをコピペする
![vivaldi_O6OkiTMR6V_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/2c0daf82-03b6-21d4-a07d-b26ad9341bac.png)
作成したファイルを開くと「myFunction……」というコードが書かれているので削除してしまいましょう。
削除した部分に**以下のコードをコピペ**してください。

~~~
var apiKey = 'YOUR_API_KEY_HERE'; // ここにAPIキーを格納

// main関数の前回の実行時間を格納する変数
var lastMainFunctionExecutionTime = null;

function setTrigger() {
  const now = new Date();
  const currentHour = now.getHours();

  // 実行スケジュールを設定
  var triggerTime;
  if (currentHour >= 1 && currentHour < 11) {
    // 午前1時から午前11時までは2時間おき
    triggerTime = 2; // 2時間を時間単位で表現
  } else if (currentHour >= 11 && currentHour < 19) {
    // 午前11時から19時までは1時間おき
    triggerTime = 1; // 1時間を時間単位で表現
  } else if (currentHour >= 21 && currentHour < 24) {
    // 21時から24時までは15分おき
    triggerTime = 15; // 15分を分単位で表現
  }else {
    // それ以外では30分おき
    triggerTime = 30; // 30分を分単位で表現
  }

  // 前回のmain関数の実行時間が設定されている場合、実行頻度の変更を確認
  if (lastMainFunctionExecutionTime !== null) {
    var timeSinceLastExecution = now - lastMainFunctionExecutionTime;
    var minutesSinceLastExecution = timeSinceLastExecution / (1000 * 60); // ミリ秒を分に変換
    // 実行頻度が変更されていない場合、トリガーを削除しない
    if (minutesSinceLastExecution === triggerTime) {
      return;
    }
  }

  // 既存のトリガーを取得
  var existingTriggers = ScriptApp.getProjectTriggers();

  // main関数によるトリガーを削除
  for (var i = 0; i < existingTriggers.length; i++) {
    if (existingTriggers[i].getHandlerFunction() === 'main') {
      ScriptApp.deleteTrigger(existingTriggers[i]);
    }
  }

  // 新しいトリガーを設定
  var triggerBuilder = ScriptApp.newTrigger('main').timeBased();

  if (triggerTime >= 1 && triggerTime <= 14) {
    // 1から14の間ならeveryHoursを使う
    triggerBuilder.everyHours(triggerTime);
  } else {
    // それ以外の場合はeveryMinutesを使う
    triggerBuilder.everyMinutes(triggerTime);
  }

  triggerBuilder.create();
}

function delTrigger() {
  // すべてのトリガーを削除
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    ScriptApp.deleteTrigger(triggers[i]);
  }
}

function youtube_video_details(video_id) {
  try {
    // YouTube Data APIからビデオの詳細情報を取得するためのURLを構築
    var apiUrl = 'https://www.googleapis.com/youtube/v3/videos?part=snippet,liveStreamingDetails&id=' + video_id + '&key=' + apiKey;
    var response = UrlFetchApp.fetch(apiUrl);
    var data = response.getContentText();
    var videoData = JSON.parse(data);
    
    if (videoData.items && videoData.items.length > 0) {
      return videoData.items[0];
    } else {
      return null;
    }
  } catch (e) {
    console.error(e);
    return null;
  }
}

function youtube_search(channel_ids_str, max_results) {
  // YouTube Data APIを使用して、指定したチャンネルIDから動画情報を検索する処理
  var channel_ids = channel_ids_str.split(',');  // カンマで区切られたチャンネルIDをリストに変換
  var items = [];  // 検索結果を格納するリスト

  // 実行日時を取得
  var now = new Date();
  var formattedDate = now.toISOString();

  // 各チャンネルごとに動画を検索
  for (var i = 0; i < channel_ids.length; i++) {
    var channel_id = channel_ids[i];
    var search_response = YouTube.Search.list('id', {
      channelId: channel_id,
      part: 'id',
      order: 'date',
      maxResults: max_results,
      publishedAfter: formattedDate // 実行日時以降の動画を検索
    });
    items = items.concat(search_response.items);
  }

  return items;
}

function create_calendar_event(start_time, end_time, title, event_location, description, calendar_id) {
  // Googleカレンダーにイベントを追加する処理
  var calendar = CalendarApp.getCalendarById(calendar_id);
  if (!calendar) {
    throw new Error('Calendar not found');
  }
  
  var event = calendar.createEvent(title, new Date(start_time), new Date(end_time), {
    description: description,
    location: event_location,
    timeZone: 'Asia/Tokyo'
  });
  
  return event;
}

function main() {
  var channel_ids_str = 'channel_id_01,channel_id_02,channel_id_03'; // チャンネルIDをコンマ区切りで追加
  var max_results = 10;
  var targetCalendarId = 'YOUR_CALENDAR_ID_HERE'; // カレンダーIDを入力

  // 既存のカレンダーイベントを取得
  var existingEvents = CalendarApp.getCalendarById(targetCalendarId).getEvents(new Date(), new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000)); // 1週間分の既存のイベントを取得

  var items = youtube_search(channel_ids_str, max_results);

  // 重複をチェックするためにビデオIDをトラッキング
  var trackedVideoIds = [];

  for (var i = 0; i < items.length; i++) {
    var video_id = items[i].id.videoId;

    // 既にトラッキングされたビデオIDかどうかを確認
    if (trackedVideoIds.includes(video_id)) {
      continue; // 既にトラッキングされていればスキップ
    }

    var details = youtube_video_details(video_id);
    if (!details) {
      continue;
    }

    if (details.liveStreamingDetails) {
      var scheduled_start_time_utc = new Date(details.liveStreamingDetails.scheduledStartTime);
      var scheduled_end_time_utc = new Date(scheduled_start_time_utc);
      scheduled_end_time_utc.setHours(scheduled_end_time_utc.getHours() + 2);

      // 既存のイベントと重複するか確認
      var isDuplicate = false;
      for (var j = 0; j < existingEvents.length; j++) {
        var existingEvent = existingEvents[j];
        if (existingEvent.getTitle() === details.snippet.title && existingEvent.getStartTime().getTime() === scheduled_start_time_utc.getTime()) {
          isDuplicate = true;
          break;
        }
      }

      if (!isDuplicate) {
        var video_title = details.snippet.title;
        var channel_title = details.snippet.channelTitle;
        var event_summary = video_title;
        var event_location = channel_title;
        var event_description = 'https://www.youtube.com/watch?v=' + video_id;

        create_calendar_event(scheduled_start_time_utc, scheduled_end_time_utc, event_summary, event_location, event_description, targetCalendarId);
      }

      // ビデオIDをトラッキングリストに追加
      trackedVideoIds.push(video_id);
    }
  }

  // main関数の実行完了後にlastMainFunctionExecutionTimeを更新
  lastMainFunctionExecutionTime = new Date();
}
~~~

なお、コーディングにあたっては以下のページを参考にさせていただきました。

https://zenn.dev/meihei/articles/1021b1a3f8c226

https://dev.classmethod.jp/articles/google-calendar-api-create-schedule/

https://tonari-it.com/gas-trigger-set/

https://qiita.com/electronics_diy721/items/167d6c61dfd73180fdca



## 1-3. サービスを追加する
![vivaldi_Tkv7pE8aee_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/e6cb14e5-7f52-b2ff-2db3-5ed81c9389bc.png)
左側の「サービス」の項目から追加していきます。
![無題544_20230927165147.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/d0a1c78b-6fcf-fda7-0fd8-c53baaad8891.png)
一覧から **「Google Calendar API」** と **「YouTube Data API」** を探し出して、それぞれ「追加」をクリックします。

# 2. APIキーの取得など

## 2-1. YouTube Data APIのAPIキーを取得する

以下の記事を参考にキーを取得してください。

https://qiita.com/shinkai_/items/10a400c25de270cb02e4

## 2-2. APIキーをコードに貼り付ける
![vivaldi_8OffvCCrGe_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/2906159c-456c-384f-b47a-1cc7eb82647e.png)
先程取得したキーを**コードの1行目**、**「YOUR_API_KE_HERE」**の部分に貼り付けてください。

# 3. カレンダーIDの取得など

## 3-1. カレンダーIDの取得

以下の記事を参考に、**配信通知を予定として組み込みたいカレンダー**の**カレンダーID**を取得してください。

https://blog-and-destroy.com/41932

## 3-2. カレンダーIDの貼り付け
![vivaldi_VQ9H6b0uF8_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/d9f281e1-76ca-b98c-e16f-105ea313845e.png)
コードの**131行目**、<strong>「YOUR_CALENDAR_ID_HERE」</strong> の部分に先程取得したカレンダーIDを貼り付けてください。

# 4. チャンネルIDの取得など

## 4-1. チャンネルIDの取得

以下の記事を参考に、**通知させたいYouTubeチャンネル**の**チャンネルID**をそれぞれ取得してください。

::: note warn
**数はいくつでも構いません**が、APIのクォータ上限の都合上**なるべく少なく抑えると正常に動作しやすい**です。
参考までに、私は3つのチャンネルのみ取得させるようにしています。
:::

https://ilr.jp/tech/485/

## 4-2. チャンネルIDの貼り付け
![vivaldi_BAOIBIbbnC_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/b138f43d-0ae6-c395-5e1f-92816ad10bcc.png)
コードの**129行目**に、**コンマ区切りでチャンネルIDをそれぞれ入力していきます**。


# 5. 動作の確認
# ![CNXlpPAvbr_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/b3d671bc-34c7-dbd7-24ca-78b87e252e41.png)
GAS画面上部のボタンをクリックし、**「setTrigger」関数**および<strong>「main」関数</strong>をそれぞれ「実行」していきます。
クリックする順番は上記の画像を参考にしてください。

ここでエラーが起きなければ問題ありません。

::: note alert
何か異常が発生した場合、**ここまでの手順に間違いがないか、スキップしてしまった箇所はないか確認してください。**
:::


::: note warn
万が一それでも解決しない場合、以下のTwitter（X）アカウントにお気軽に連絡ください。
可能な限り対応させていただきます。
:::

https://twitter.com/RealSh4wn


# 6. トリガーの設定

いよいよ最終局面です。

## 6-1. 「トリガー」画面への移動
![vivaldi_wysJvP4yc9_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/e0f6b1f0-4507-5308-a792-82f86daa6907.png)
GASの画面左側、目覚まし時計のアイコンをクリックし **「トリガー」画面** に移行します。
その後、画面右下の**青い「トリガーを追加」ボタン**をクリックします。

## 6-2. トリガー（定期実行）の設定
![vivaldi_o1B0Qimyl9_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3543935/9975f079-f7b4-2d41-30cd-9f6889853e75.png)
トリガーの設定画面に移行したら、

- <strong>「実行する関数」</strong>を<strong>「setTrigger」</strong>に
- <strong>「時間ベースのトリガーのタイプ」</strong>を<strong>「分ベース」</strong>に
- <strong>「時間の間隔」</strong>を<strong>「15分おき」</strong>に

順番に設定していきます。
その後、「保存」を押して終了です。

# 7. その他

## 7-1. 経過観察

これで設定は完了です。あとは放置してスクリプトが正しく動いていくか確認することになります。
具体的には、GASの画面右側「実行数」タブでステータスが「失敗しました」などとなっていなければ問題ありません。
問題が発生するならば大抵はAPIのクォータ上限に引っかかっているがゆえのことだと思いますので、通知するチャンネルの数を減らすなどして調整してください。

## 7-2. 予定は追加されるが通知が届かない場合

以下のページを参考にカレンダーの通知設定を見直してください。

https://support.google.com/calendar/answer/37242?hl=ja&co=GENIE.Platform%3DDesktop#zippy=%2C%E9%80%9A%E7%9F%A5%E3%82%92%E3%82%AA%E3%83%B3%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%AA%E3%83%95%E3%81%AB%E3%81%99%E3%82%8B

## 7-3. 他のカレンダーを通じての通知/予定の受け取り

**Appleカレンダー**や**Windows（Windows 10, Windows 11）標準カレンダー**と**Googleカレンダーを同期**させることで、**それぞれのアプリやソフトで予定や通知を確認することができます**。
以下の記事などを参考に操作を行ってください。

# 8. カスタマイズなどについて

以下はやや込み入った話になります。

## 8-1. 時間帯別・配信予定チェック頻度の変更について

setTrigger関数の「実行スケジュールを設定」以下、つまるところ**13行目以下の内容を調整**することで**時間帯別の配信予定チェック頻度を変更することができます**。

::: note alert
分単位では**1分、5分，10分，15分、30分ごと**。
時間単位では**1時間、2時間、4時間、6時間、8時間、12時間ごと**というユニットでしか設定できません。
:::

## 8-2. YouTube Data APIのクォータ上限について
正直目の上のたんこぶです。
追っている配信者が多い方ほど困るだろうと思います。
一応Google Cloud Platform経由でAPIキーを取得させているので、以下の記事などを参考にして上限のアップを申し込んでも良いのかもしれません。

https://techblog.booklista.co.jp/entry/2023/03/06/114207

https://qiita.com/Pumila/items/3ae377b8c27a40690333#:~:text=%E3%82%AF%E3%82%A9%E3%83%BC%E3%82%BF%E5%88%B6%E9%99%90%E3%81%AF%201%20%E6%97%A5%E5%BD%93%E3%81%9F%E3%82%8A%2010%2C000%20%E5%8D%98%E4%BD%8D%E3%81%A8%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82

## 8-3. そもそもの話
そもそも筆者自身が非エンジニアで殆ど勘と他の言語の経験値でコーディングした代物です。
**動作はしますが無茶苦茶な部分や意味不明な部分が多かろう**とは思いますので、気になった点があれば下記のTwitter（X）アカウントなどへ連絡いただけますと幸いです。

https://twitter.com/RealSh4wn