VTuberなどで活動をしていると「YouTubeに自分の動画を投稿したらDiscordのチャンネルに自動的で通知して欲しいな」といった考えが及んでここを読んでいる人もいるかと思いますが、そういった人のために少しでもお役立てられたらいいなと思い手順を以下に示します。
なお、ここではGoogleのスプレッドシートを活用してGASと連携させたAPIスクリプトを用意します。
eventType は completed, live, upcoming のいずれかです。"none" を指定すると意図しない挙動になるか、パラメータが無視されます。通常の動画のみを取得したい場合はフィルタリングロジックをJS側で行うのが安全のため。
sheet.getRange("A2:A").getValues() はシートの最終行(デフォルトで1000行以上)まで空白を含めて読み込むため、処理が重くなります。データがある行まで (getLastRow) に限定するよう変更。
複数の動画が同時に見つかった場合、ループで一瞬でPOSTするとDiscord側でレート制限(Rate Limit)にかかる可能性があります。Utilities.sleep(1000) などで少し待機時間を挟むのがマナーのために変更。
APIは「新しい順」で返ってきますが、ループでそのまま処理すると「最新の動画」が先に通知され、その後に「その前の動画」が通知されます。Discord上では時系列順(古い→新しい)に並んだほうが自然なため、配列を逆順にするか、後ろから処理することをお勧めします。
まずは最初にYouTubeに投稿された動画情報をリスト化し蓄積するためのスプレッドシートをGoogleドライブの任意場所に作成します。このリストが無いと過去に投稿された内容かどうかGASが判断できなくなり、毎回決められた時刻または間隔でDiscordに投稿されてしまいます。
名前は「getYouTubeData」など分かりやすいタイトルで付けましょう。
シート名は「SentVideos_001」などで付けましょう。
SentVideos_001のシートの1行目に「VideoID」というヘッダーを追加します。
先ほどの手順1で用意した動画リストのスプレッドシート「getYouTubeData」を開きメニューから「拡張機能 / Apps Script」を選択します。
Apps Script が作成されたら「コード」のボタンから任意のタイトルを付けましょう。
例えば「getyoutube」などです。
YoutubeのAPIを呼び出すため「サービス」のボタンから「YouTube Data API v3」を選択してサービスを追加しましょう。
GASからDiscordに情報を投稿できるようウェブフックを作成しましょう。
Discordのサーバー設定から「連携サービス / ウェブフック」を開き「新しいウェブフック」を押します。すると「アイコン」「お名前」「チャンネル」がそれぞれ出てきますので、任意の設定と入力をしましょう。Botが自動的に入力していく事になりますので、チャンネルは通知Bot用などで用意してあげるとよいかもしれません。
一通り設定出来たら「ウェブフックURLをコピー」を押下してGASのコードに入力する準備をします。
GASに中核となるコードを作成しましょう。コードは以下に用意しました。
const CHANNEL_ID = "ここにチャンネルIDを記載";
const WEBHOOK_URL_ARRAY = ["ここにDiscordのウェブフックを指定"];
function checkYouTubeAndNotify() {
const startTime = new Date();
// 1. YouTube Data API (Activities.list) を使用 (コスト: 1)
// アップロードされた動画を取得
let results;
try {
results = YouTube.Activities.list('contentDetails,snippet', {
'channelId': CHANNEL_ID,
'maxResults': 5,
'type': 'upload' // アップロードのみに限定
});
} catch (e) {
Logger.log(`Error fetching YouTube data: ${e.message}`);
return;
}
if (!results || !results.items || results.items.length === 0) {
Logger.log('No activities found.');
return;
}
processVideos(results.items);
}
function processVideos(items) {
let sheet;
try {
sheet = getOrSetupSheet();
} catch (e) {
Logger.log(`Spreadsheet Error: ${e.message}`);
return;
}
// スプレッドシートから既存のIDを取得(データがある範囲のみ)
const lastRow = sheet.getLastRow();
let sentVideoIds = new Set();
if (lastRow > 1) {
// A2から最終行までを取得
const data = sheet.getRange(2, 1, lastRow - 1, 1).getValues().flat();
sentVideoIds = new Set(data.filter(String));
}
// 通知順序を「古い動画 → 新しい動画」にするために配列を反転
const reversedItems = items.reverse();
for (const item of reversedItems) {
let videoId;
// データ構造を確認:contentDetails と upload が存在する場合のみIDを取得
if (item.contentDetails && item.contentDetails.upload) {
videoId = item.contentDetails.upload.videoId;
} else {
// データが欠けている場合はログを出してスキップ(エラーで止まらないようにする)
Logger.log(`Skipping item (invalid structure): ${item.snippet ? item.snippet.title : 'Unknown Title'}`);
continue;
}
// すでに通知済みならスキップ
if (sentVideoIds.has(videoId)) continue;
// 通知内容の作成
const videoTitle = item.snippet.title;
const videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
const payload = {
"username": "YouTube Notification",
"content": `${videoTitle}\n${videoUrl}`
};
// Discordへ送信
const success = postDiscord(payload);
if (success) {
// 送信成功時のみシートに書き込み
sheet.appendRow([videoId, videoTitle, new Date()]);
// 連投制限対策(1秒待機)
Utilities.sleep(1000);
}
}
}
// シート取得・初期化用関数
function getOrSetupSheet() {
const SHEET_NAME = 'SentVideos_001';
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(SHEET_NAME);
sheet.appendRow(['VideoID', 'Title', 'Date']); // ヘッダー
}
return sheet;
}
function postDiscord(payload) {
let allSuccess = true;
for (const webhookUrl of WEBHOOK_URL_ARRAY) {
try {
const options = {
"method": "post",
"contentType": "application/json",
"payload": JSON.stringify(payload),
"muteHttpExceptions": true
};
const response = UrlFetchApp.fetch(webhookUrl, options);
const responseCode = response.getResponseCode();
if (responseCode < 200 || responseCode >= 300) {
Logger.log(`Discord Error ${responseCode}: ${response.getContentText()}`);
allSuccess = false;
}
} catch (e) {
Logger.log(`Discord Exception: ${e.message}`);
allSuccess = false;
}
}
return allSuccess;
}
コードには3つの要素として「チャンネルID」と「ウェブフックID」、そして「シート名」を指定する必要があります。このコードではシート名が不明な場合には紐づけが出来るシートを自動で追加する機能を実装しています。
では早速ウェブフックIDをコードに追加しましょう。ウェブフックのIDについては、先ほどの手順5で取得できたかと思いますので、コードの2行目にウェブフックをペーストします。
YouTubeのIDの取得方法はYouTubeチャンネルの概要欄から「チャンネルを共有」のボタンを押下します。「チャンネルIDをコピー」が選択できるようになりますので押下して取得します。コピーができたらGASのコード入力に戻ってチャンネルIDをペーストします。
例えば、私のチャンネルIDであれば「UC7ig1kLH30YH7cEfxE83ZWA」というのがチャンネルIDになります。
コードの1行目にチャンネルIDをペーストしてください。
コードの入力が完了したら「プロジェクトの保存」をしてください。
ここまで入力出来たら実際に実行してテストしてみましょう。
実行が成功すると右の図のようにDiscordに投稿が成功したことが分かります。
作成したAPIは実行したときのみに動作しますので、これを繰り返し確認させるためのトリガーを設定してあげましょう。
Apps Script のメニューから目覚まし時計のアイコン「トリガー」を押下。少しすると右下に「+トリガーを追加」というボタンを押下します。
そうすると様々な設定ができる項目が出てきます。例えば15分ごとに確認させたい場合は「時間ペースのトリガーのタイプを選択」から「分ペースのタイマー」を選び「時間の感覚を選択」から15分を選んでください。
このようにトリガーの設定は非常に手軽ですので、自分のチャンネルに合った使い方でDiscordを充実させていきましょう!
最後に「プロジェクトの保存」をしてください。
すべての内容に問題が無ければ「保存」して閉じてください。
お疲れさまでした。これでYouTube通知Botの構築は完了です!
YouTube Data APIは1日あたりの使用量は10,000まで利用できます。
ここで紹介している仕様ではスプレッドシートに情報が累積されていくため、長期間の運用や検索先のVideoIDが増えていくにつれて、追加されていくレコードが増えていきます。これによって動画情報の重複を検索する際に1件1件検索しています。
この場合ではVideoIdのカラムは配列に格納し、indexOfで検索することで処理を効率化することが出来ます。
このエラーが出る原因は、「スクリプト内の関数名を変更(または削除)したのに、古い関数名を実行する『トリガー』が残っている」ためです。
修正版のスクリプトにて関数名を getYouTubeData から checkYouTubeAndNotify に変更しました。しかし、設定済みのトリガー(自動実行の設定)はまだ getYouTubeData を探しているため、「実行しようとしたが見つからない(削除された)」というエラーになっています。
以下の手順でトリガーを再設定すれば直ります。
修正手順
トリガー画面を開く
Apps Scriptエディタの左側にあるメニューから「時計のマーク(トリガー)」をクリックします。
古いトリガーを削除
リストの中に getYouTubeData という関数を実行する設定があるはずです。
行の右端にある「︙(三点リーダー)」をクリックし、「トリガーを削除」を選択します。
新しいトリガーを作成
右下の「トリガーを追加」ボタンをクリックします。
実行する関数を選択: checkYouTubeAndNotify (修正版スクリプトのメイン関数名)を選択してください。
イベントのソース: 「時間主導型」
トリガーのタイプ: 「分ベースのタイマー」
間隔: 「5分おき」(または10分、15分などお好みで)
「保存」をクリックします。
これでエラーは解消され、新しいスクリプトが定期的に動くようになります。