好きなYoutuberがLive配信を予約したら、Googleカレンダーに自動で予定を入れるスクリプト


更新

2021/09/09更新:配信予約のタイトル・概要変更に対応
2021/08/03更新:配信予約のキャンセルに対応
2021/08/01更新:配信予約の時間変更に対応
2021/02/23更新:重複チェックする期間を半年先から3年先(配信予約の上限)まで延長(長期間先の配信予約をフリーチャットとして使うケースに対応するため)

概要

Vtuberにハマっていますが、好きなVtuberのLive配信の予定をGoogle カレンダーに自動で登録できたら、そのために予定を調整するのに便利だよなぁ、と思ってスクリプトを作ってみました。

Vtuberさんに協力を仰がなくても自動でGoogle カレンダーに登録できるものの、早めに配信枠を予約するVtuberさんじゃないと有用じゃないかも。

前提条件

Googleのアカウントを持っている
Google カレンダーの利用を開始している
Youtubeの利用を開始している
(たぶんPCのブラウザでしか使えないです)

機能

・任意のYouTubeのチャンネルがLive配信を予約したら、任意のGoogleカレンダーへ予定を登録する。
・カレンダーの予定の場所には配信のURL、メモには概要欄の内容を登録する。
・URLが同じ配信予約は重複して予定登録されない。(毎回全予定を削除して登録しなおす形にすると、手入力した予定も消えますし、新規予定登録に通知を設定している人には通知欄がカオスになるので避けました)
・後から配信予約の内容(時間、タイトル、概要)が変更された場合、予定に変更を反映する。
・後から配信予約がキャンセルされた場合、予定もキャンセルする。

できない機能

・プレミア公開は予定に反映されません
・メンバー限定配信は予定に組み込まれません

スクリプト

let scheduleUpcomingLive=()=> {
  let results = YouTube.Search.list(
    'snippet', {
    channelId: 'CHANNEL ID', //CHANNEL IDを、予定を取りたいYoutubeのチャンネルのChannel IDに書き換えてください(URLから取れます)
    safeSearch: 'none',
    eventType:'upcoming',
    type:'video'
    }
    );

  let calendarId = 'YOUR CALENDAR ID';//YOUR CALENDAR IDを、予定を登録したいカレンダーのカレンダーIDに書き換えてください
  let now = new Date()
  let optionalArgs = {
    timeMax: (new Date(now.getTime() + (1095 * 24 * 60 * 60 * 1000))).toISOString(),
    timeMin: (new Date(now.getTime() - ( 7 * 24 * 60 * 60 * 1000))).toISOString(),
    showDeleted: false,
    singleEvents: true
    };
  let events = Calendar.Events.list(calendarId, optionalArgs).items;

  let arrayOfLocation = []
  for (let n in events) {
    arrayOfLocation.splice(n,0,events[n].getLocation())
    }

  let arrayOfDescription =[]
  let arrayOfURL =[]
  let VIdIs = "videoID is "

  for(let i in results.items){
    let resultsVideo = YouTube.Videos.list(
      'snippet,liveStreamingDetails',{
        id : results.items[i].id.videoId
        }
      )
    let VideoId = results.items[i].id.videoId

    let URLofStream = 'https://www.youtube.com/watch?v=' + VideoId;
    let descriptionV = resultsVideo.items[0].snippet.description+"\n\n"+"videoID is "+VideoId
    let title = resultsVideo.items[0].snippet.title;
    let startTime = new Date(resultsVideo.items[0].liveStreamingDetails.scheduledStartTime);
    let endTime = new Date(resultsVideo.items[0].liveStreamingDetails.scheduledStartTime);
    endTime.setHours(endTime.getHours() + 1)
    let option = {
      description: descriptionV,
      location: URLofStream
      }

    if (arrayOfLocation.indexOf(URLofStream) < 0 ) {
      CalendarApp.getCalendarById(calendarId).createEvent(title, startTime, endTime, option);

    } else {
      let eventsToUpdate = Calendar.Events.list(
        calendarId, {
          timeMax: (new Date(now.getTime() + (1095 * 24 * 60 * 60 * 1000))).toISOString(),
          timeMin: (new Date(now.getTime() - ( 7 * 24 * 60 * 60 * 1000))).toISOString(),
          maxResults:1,
          singleEvents: true,
          q:VideoId
          }
        );
      let eventId = eventsToUpdate.items[0].id;
      let startTimeVideoISO = new Date(startTime).toISOString()
      let startTimeEventISO = new Date(eventsToUpdate.items[0].start.dateTime).toISOString()

      if (startTimeVideoISO !== startTimeEventISO) {
        CalendarApp.getCalendarById(calendarId).getEventById(eventId).setTime(startTime, endTime).setTitle(title).setDescription(descriptionV)
      }
      if (title !== eventsToUpdate.items[0].summary) {
        CalendarApp.getCalendarById(calendarId).getEventById(eventId).setTitle(title).setDescription(descriptionV)
      }
      if (descriptionV !== eventsToUpdate.items[0].description) {
        CalendarApp.getCalendarById(calendarId).getEventById(eventId).setDescription(descriptionV)
      }
    }
    arrayOfDescription.splice(i,0,descriptionV)
    arrayOfURL.splice(i,0,URLofStream)
  }
  optionalArgs = {
    timeMax: (new Date(now.getTime() + (1095 * 24 * 60 * 60 * 1000))).toISOString(),
    timeMin: (new Date(now.getTime() + (60 * 60 * 1000))).toISOString(),
    showDeleted: false,
    singleEvents: true
    };
  let eventsRemove = Calendar.Events.list(calendarId, optionalArgs).items;

  for (let o in eventsRemove) {
    let descriptionE = eventsRemove[o].getDescription() + "to avoid undef"
    let locationE = eventsRemove[o].getLocation()
    if(descriptionE.indexOf(VIdIs) >= 0){
      if (arrayOfURL.indexOf(locationE) < 0 ) {
        Calendar.Events.remove(calendarId,eventsRemove[o].id)
      }
    }
  }
}

導入方法

さて、それでは上記のスクリプトを導入する方法をご紹介します。
便宜上、私がファンである黒乃くれはさんを例に説明します(ダイマ)。

黒乃くれはさんのチャンネル
https://www.youtube.com/channel/UCFVkfdFmaOh7BCtrqw8YvaA?sub_confirmation=1

Google Apps Scriptの準備

まずGoogle Apps Script (GAS)にアクセスします。
https://script.google.com/home

「新しいプロジェクト」をクリックします

プロジェクトが開くと、次のような画面が表示されるかと思います(旧エディター)

コードの内容を上記のスクリプトにすべて書き換えてください。

カレンダーIDと、YoutubeのチャンネルIDの部分を書き換えないといけないので、それぞれを取得する方法を記載します。

GoogleカレンダーIDを取得する。

(既存のカレンダーを利用したい場合は、次の項「新しくカレンダーを作りたい場合」は飛ばしてください)

Google カレンダーを開きます。
https://calendar.google.com/

新しくカレンダーを作りたい場合

左のメニューから、+ボタン(他のカレンダーを追加)をクリックします。

表示されたドロップダウンメニューから、「新しいカレンダーを作成」をクリックします。

名前をわかりやすいものに変更し、「カレンダーを作成」をクリックします。

ここから、既存のカレンダー、新しいカレンダーどちらも手順は同じです

トップメニューに戻り、画面の右上にある歯車マークをクリックします。
表示されたドロップダウンメニューから「設定」クリックします。

左に表示されたメニューから、先ほど作成したカレンダーをクリックします。

カレンダーIDをコピーします。

GASに戻って、スクリプトの部分にコピペします。

YouTubeチャンネルIDの取得

配信予約を取得したいYoutubeのチャンネルページにアクセスします。
黒乃くれはさんの場合、次のURLです。
https://www.youtube.com/channel/UCFVkfdFmaOh7BCtrqw8YvaA

URLの末尾の部分(~channel/以降の部分)がチャンネルIDですので、コピーします。
(場合によってはチャンネルIDに続けて?sub_confirmation=1などが付いていることがありますので、それは除いてください)

上記のスクリプトのチャンネルIDの部分に、チャンネルIDをペーストします。
(クォーテーションマーク ' ' は消さないでください)

APIの準備

旧エディターの場合

GASでリソースのメニューから「Googleの拡張サービス」をクリックします。

初回だと、拡張サービスを利用するのに確認が入ります。
青字になっている「Cloud Console Terms of Service」をクリックしてください。

表示されたウェブページで、次のような確認が入りますので、規約に同意してよろしければチェックし、「同意して続行」をクリックします。

改めてGASに戻り、もう一度リソースのメニューから「Googleの拡張サービス」をクリックします。

APIの一覧が表示されます。

Calendar APIと、YouTube Data APIを有効にしてください。

新エディターの場合

新エディターの場合は、次のようなメニューになります。
左側のメニューの中にある「Service」の横の+ボタンをクリックしてください。

Google calendar APIと、Youtube Data APIをそれぞれAddしてください。

権限付与

保存ボタンをクリックしてください。

その後、実行ボタンをクリックしてください。

初めてスクリプトを動かすときは、次のような確認が入ります。
「許可を確認」をクリックしてください。

自分のアカウントを選択してください。

次のような警告が表示されます。
「詳細」をクリックしてください。

デベロッパーは筆者と作業をなさっているあなたご自身ですが、筆者・自分そしてGoogleを信頼できる場合、警告が「~(安全ではないページ)に移動」をクリックしてください。

次のようなメニューが表示されます。
問題なければ「許可」をクリックしてください。

GASの画面に戻り、再度実行ボタンをクリックしてください。

Google カレンダーを表示して、配信予約が適切にGoogle カレンダーに登録されていることを確認します。
(そもそも配信予約がない場合、表示されません)

トリガーの設定

一定時間ごとに配信予約が新たに登録されていないかチェックするように設定します。
下記の画像は15分おきにチェックするようにしていますが、30分おきがよいかもしれません。

チェックする時間の間隔は短くするほど素早くカレンダーに反映できますが、YouTube Data APIはアクセスできる回数に限度があるので、あまり細かくすると1日あたりの上限にすぐ到達してしまい、その日はそれ以降、チェックできなくなります。
(10分おきにしたら上限に達してしまいました)

手順

トリガーボタン(時計マーク)をクリックしてください。

新エディターの場合、次のように表示されるので、トリガーボタン(時計マーク)をクリックしてください。

次のようなメニューが表示されます。
右下のトリガーを追加をクリックしてください。

「イベントのソースを選択」の下のドロップダウンメニューを開きます。

「分ベースのタイマー」をクリックしてください。

次に「時間の間隔を選択(分)」の下のドロップダウンメニューを開きます。

「15分おき」をクリックしてください。
(慎重になる場合、30分おきにするのがよいかもしれません)

「保存」をクリックしてください。

以上で導入は完了です。

導入にあたってお願い

可能なら一般公開してほしい

前述の通り、このスクリプトを動かすと、Youtube Data APIで各アカウントに割り当てられた参照回数(quota)を消費します。

15分ごとにスクリプトを動かしたりすると、すぐ上限いっぱいまで消費されるため、たくさんのYoutuberの予定を同時に取得することは難しいです。

これを克服するため、このスクリプトを利用してカレンダーを作成した方は、後述のリスクを確認の上、一般公開して頂ければ、視聴者コミュニティ全体が助かるかと考えられます。

わざわざファン各自が同じYoutuberのカレンダーを作らなくてもよくなり、相互にカレンダーを使いあうことができます。

注意すべきリスク

ただし、カレンダーを一般公開すると、同時にメールアドレスも確認されるようになってしまいます。

そのため、公開するカレンダーを作成するときは、メールアドレスがバレても良いGoogleアカウント(いわゆる捨て垢)で作成するなど、リスクを下げる工夫が必要です。

このリスクを受け入れられる方のみ、一般公開して頂ければと存じます。

一般公開の方法

カレンダーを一般公開する方法は、次のヘルプを参考にしてください。
https://support.google.com/calendar/answer/37083?hl=ja

一般公開後、URLを公開することで他のユーザーが利用できるようになります。

URLの公開場所は、それぞれのVtuberのファンが集う場所が良いかと思われますが(そのコミュニティのルールを守った上で)、もしVtuberで公開場所に心当たりが無ければ、
私が管理しているVtuberのwikiに記載して頂ければと思います。