GASでGoogle Drive動画をInstagramリールへ自動投稿!完全ガイド
Instagramでのリール投稿は、エンゲージメントを高めるために非常に有効な手段です。しかし、動画ファイルを一つ一つ手作業でアップロードし、キャプションを付けて投稿するのは手間がかかる作業です。特に、複数のアカウントを運用していたり、定期的に大量のコンテンツを投稿する必要がある場合、その負担は無視できません。
この記事では、Google Apps Script (GAS) を使用して、Google Driveに保存した動画ファイルを自動的にInstagramリールとして投稿する仕組みを構築する方法を解説します。提供されたスクリプトコードを基に、その事前準備、コードの仕組み、実行方法、そして応用例までを網羅的にご紹介します。
この自動化ツールを活用すれば、コンテンツ作成と投稿作業を分離し、大幅な効率化を実現できます。ぜひ最後までご覧いただき、ご自身のInstagram運用にお役立てください。
この仕組みの最大の目的は、Instagramリール投稿の自動化と効率化です。具体的には、以下のようなメリットが考えられます。
特に、動画コンテンツを大量に扱うクリエイターや、複数のアカウントを運用するビジネスにとって、この自動化は強力な武器となるでしょう。
この自動投稿システムを構築するためには、いくつかの準備が必要です。以下に必要なものをリストアップします。
instagram_content_publish
, instagram_basic
, pages_show_list
, pages_read_engagement
など)を設定します。これらの事前準備を整えることで、GASからInstagram Graph APIを呼び出すための「鍵」と「接続先情報」が手に入ります。
GASスクリプトからInstagram Graph APIを利用するためには、「アクセストークン」(API利用権限を証明するもの)と「InstagramビジネスアカウントID」(どのInstagramアカウントに投稿するかを指定するもの)が必要です。これらはMetaのグラフAPIエクスプローラを使用して取得します。
Meta for Developers にアクセスし、グラフAPIエクスプローラを開きます。
画面右側の「Metaアプリ」ドロップダウンから、事前準備で作成したMetaアプリを選択します。
instagram_content_publish
(リール投稿に必要)instagram_basic
(基本的なアカウント情報取得に必要)pages_show_list
(連携しているFacebookページ一覧を取得に必要)pages_read_engagement
(ページに関連するエンゲージメント読み取りに必要 – ID取得に間接的に必要となる場合あり)成功すると、グラフAPIエクスプローラの上部にユーザーアクセストークンが表示されます。このトークンを安全な場所にコピーしておきます。これがGASスクリプトの USER_ACCESS_TOKEN
になります。 このトークンには有効期限があるため、期限切れの場合は再取得が必要です。
Instagramビジネス/クリエイターアカウントは、Facebookページと連携している必要があります。API経由での操作はこの連携したFacebookページIDを起点に行うため、そのIDを取得します。
GET
me/accounts
data
配列の中に、あなたが管理するFacebookページの一覧が表示されます。id
の値を控えます。これが後のリクエストで使用するFacebookページIDです。// レスポンス例の一部
{
"data": [
{
"name": "あなたのFacebookページ名",
"id": "YOUR_FACEBOOK_PAGE_ID" // ★ このIDを控える ★
}
// ... 他のページ情報 ...
]
}
いよいよ、最終的な投稿先となるInstagramビジネスアカウントのIDを取得します。
GET
/{控えたFacebookページID}?fields=instagram_business_account
(例: /YOUR_FACEBOOK_PAGE_ID?fields=instagram_business_account
)instagram_business_account
オブジェクト内にある id
の値を取得します。これがGASスクリプトの INSTAGRAM_BUSINESS_ACCOUNT_ID
になります。// レスポンス例
{
"instagram_business_account": {
"id": "YOUR_INSTAGRAM_BUSINESS_ACCOUNT_ID" // ★ このIDを取得! ★
},
"id": "YOUR_FACEBOOK_PAGE_ID"
}
これで、GASスクリプトで必要となる認証情報とアカウント識別情報が全て揃いました。これらのIDとトークンは機密情報として取り扱い、漏洩しないように注意してください。
Google Drive上の動画ファイルは、Instagram Graph APIがインターネット経由でアクセスできる必要があります。以下の手順で準備します。
投稿したい動画ファイル(MP4またはMOV形式推奨)をGoogle Driveの任意のフォルダにアップロードします。リールに適した形式や長さ、アスペクト比であることを確認しましょう。
アップロードした動画ファイルが保存されているGoogle Driveフォルダを開きます。ブラウザのアドレスバーに表示されているURLから、フォルダIDをコピーします。
例: https://drive.google.com/drive/folders/THIS_IS_THE_FOLDER_ID
の THIS_IS_THE_FOLDER_ID
の部分です。これがGASスクリプトの TARGET_GOOGLE_DRIVE_FOLDER_ID
になります。
このステップが最も重要です。GASスクリプトが取得した動画ファイルのURLは、Google Driveの認証を伴わない直接ダウンロードURL(webContentLink
)となります。InstagramのサーバーはGoogleアカウントで認証できないため、このURLがインターネット上の誰でもアクセスできる状態である必要があります。
フォルダ内の投稿したい各動画ファイル、またはフォルダそのものに対して、「リンクを知っている全員が閲覧者」として共有設定を行います。
注意: この設定を行うと、そのファイル(またはフォルダ内の全てのファイル)のURLを知っていれば誰でもファイルの内容を閲覧できるようになります。公開されても問題ない動画ファイルのみをこのフォルダに配置するようにしてください。
事前準備とファイルの用意ができたら、いよいよGASプロジェクトを設定し、コードを貼り付けます。
Google Driveで「新規」 > 「その他」 > 「Google Apps Script」を選択して新しいプロジェクトを作成します。
GASからGoogle Driveのファイル情報を取得するために、Drive APIサービスを有効にする必要があります。
Drive
のままで構いません)以下のGASプログラムコードを、新しく作成したGASプロジェクトのスクリプトエディタに貼り付けます。既存のコード(myFunction
など)は削除して構いません。
// --------------------------------------------------------------------------------
// 設定項目: ここを編集してください
// --------------------------------------------------------------------------------
// 1. Facebookユーザーアクセストークン (ステップ1-3で取得したトークン)
const USER_ACCESS_TOKEN = "ここにステップ1-3で取得したユーザーアクセストークンを貼り付け";
// 2. 投稿対象のInstagramビジネスアカウントID (ステップ1-5で取得したID)
const INSTAGRAM_BUSINESS_ACCOUNT_ID = "ここにステップ1-5で取得したInstagramビジネスアカウントIDを貼り付け";
// 3. 投稿する動画が含まれるGoogle Drive上のフォルダID (ステップ2-2で取得したID)
const TARGET_GOOGLE_DRIVE_FOLDER_ID = "ここにGoogle DriveのフォルダIDを貼り付け";
// 4. リールのキャプション (投稿ごとに固定、または動画名などから動的に生成も可能)
const REEL_CAPTION = "GASからの自動投稿テスト! #リール #自動化";
// 5. フィードにも投稿するかどうか (true または false)
const SHARE_TO_FEED = true;
// 6. (任意) カスタムカバー画像の公開URL (指定しない場合は動画のフレームから自動選択)
// const COVER_IMAGE_URL = "https://example.com/your_public_cover_image.jpg";
// 7. (任意) サムネイルとして使用する動画の時点 (ミリ秒単位)。COVER_IMAGE_URLがない場合に考慮されます。
// const THUMB_OFFSET_MS = 1500; // 例: 1.5秒時点
// --------------------------------------------------------------------------------
// グローバル定数 (編集不要)
// --------------------------------------------------------------------------------
const FACEBOOK_GRAPH_API_VERSION = "v20.0"; // 安定しているAPIバージョンを推奨 (例: v19.0, v20.0, v21.0)
const GRAPH_API_BASE_URL = `https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}`;
// --------------------------------------------------------------------------------
// メイン関数: この関数を実行してリールを投稿します
// --------------------------------------------------------------------------------
function postReelFromGoogleDrive() {
try {
// 設定値のチェック
if (USER_ACCESS_TOKEN === "ここにステップ1-3で取得したユーザーアクセストークンを貼り付け" || !USER_ACCESS_TOKEN) {
Logger.log("エラー: `USER_ACCESS_TOKEN` を設定してください。");
return;
}
if (INSTAGRAM_BUSINESS_ACCOUNT_ID === "ここにステップ1-5で取得したInstagramビジネスアカウントIDを貼り付け" || !INSTAGRAM_BUSINESS_ACCOUNT_ID) {
Logger.log("エラー: `INSTAGRAM_BUSINESS_ACCOUNT_ID` を設定してください。");
return;
}
if (TARGET_GOOGLE_DRIVE_FOLDER_ID === "ここにGoogle DriveのフォルダIDを貼り付け" || !TARGET_GOOGLE_DRIVE_FOLDER_ID) {
Logger.log("エラー: `TARGET_GOOGLE_DRIVE_FOLDER_ID` を設定してください。");
return;
}
Logger.log(`使用するInstagramアカウントID: ${INSTAGRAM_BUSINESS_ACCOUNT_ID}`);
// 1. フォルダからランダムな動画ファイルIDを取得
Logger.log(`フォルダID '${TARGET_GOOGLE_DRIVE_FOLDER_ID}' からランダムな動画ファイルを選択中...`);
const selectedVideoFileId = getRandomVideoFileIdFromFolder(TARGET_GOOGLE_DRIVE_FOLDER_ID);
if (!selectedVideoFileId) {
Logger.log(`エラー: フォルダID '${TARGET_GOOGLE_DRIVE_FOLDER_ID}' 内に投稿可能な動画ファイルが見つかりませんでした。処理を終了します。`);
return;
}
Logger.log(`ランダムに選択された動画ファイルID: ${selectedVideoFileId}`);
// 2. Google Driveから動画の公開URLを取得
Logger.log(`Google Driveから動画ファイル (ID: ${selectedVideoFileId}) の情報を取得中...`);
let videoUrl;
let videoFileName;
try {
const file = Drive.Files.get(selectedVideoFileId, { fields: "webContentLink, mimeType, name" });
if (!file || !file.webContentLink) {
throw new Error(`動画ファイル (ID: ${selectedVideoFileId}) の webContentLink が取得できませんでした。ファイルが存在し、「リンクを知っている全員が閲覧者」として共有されているか確認してください。`);
}
videoUrl = file.webContentLink;
videoFileName = file.name;
Logger.log(`動画ファイル名: ${videoFileName}`);
Logger.log(`取得した動画URL: ${videoUrl}`);
Logger.log(`動画MIMEタイプ: ${file.mimeType}`);
if (file.mimeType !== "video/mp4" && file.mimeType !== "video/quicktime" && file.mimeType !== "video/mov") {
Logger.log(`警告: 動画のMIMEタイプが ${file.mimeType} です。InstagramはMP4またはMOV形式を推奨しています。問題が発生する可能性があります。`);
}
} catch (e) {
Logger.log(`Google Driveからの動画URL取得中にエラーが発生しました: ${e.message}`);
Logger.log("ヒント: GASプロジェクトでDrive APIサービスが有効になっているか、ファイルIDが正しいか、ファイルが「リンクを知っている全員が閲覧者」として共有されているか確認してください。");
return;
}
let dynamicCaption = REEL_CAPTION; // ここでは固定キャプションを使用。 `${videoFileName} を投稿!` なども可能。
// 3. メディアコンテナを作成
Logger.log("Instagramメディアコンテナを作成中...");
const creationId = createInstagramMediaContainer(INSTAGRAM_BUSINESS_ACCOUNT_ID, USER_ACCESS_TOKEN, videoUrl, dynamicCaption);
if (!creationId) {
Logger.log("メディアコンテナの作成に失敗しました。ログを確認してください。");
return;
}
Logger.log(`メディアコンテナID: ${creationId} を作成しました。`);
// 4. アップロードステータスを確認 (ポーリング)
Logger.log("動画のアップロードと処理ステータスを確認中 (最大約5分)...");
const uploadSuccessful = checkUploadStatus(creationId, USER_ACCESS_TOKEN);
if (!uploadSuccessful) {
Logger.log("動画のアップロードまたは処理に失敗しました。Instagram側の処理に時間がかかっているか、動画に問題がある可能性があります。ログを確認してください。");
return;
}
Logger.log("動画のアップロードと処理が正常に完了しました。");
// 5. メディアを公開
Logger.log("リール動画を公開中...");
const publishedMediaId = publishInstagramReel(INSTAGRAM_BUSINESS_ACCOUNT_ID, USER_ACCESS_TOKEN, creationId);
if (publishedMediaId) {
Logger.log(`リール動画が正常に投稿されました! 公開メディアID: ${publishedMediaId}`);
Logger.log(`投稿はInstagramアプリで確認してください。 (URL目安: https://www.instagram.com/reel/${publishedMediaId}/ )`);
} else {
Logger.log("リール動画の公開に失敗しました。ログを確認してください。");
}
} catch (error) {
Logger.log(`スクリプト全体でエラーが発生しました: ${error.toString()}\nスタックトレース: ${error.stack || 'N/A'}`);
}
}
/**
* 指定されたGoogle Driveフォルダ内からランダムに動画ファイルIDを1つ取得します。
*/
function getRandomVideoFileIdFromFolder(folderId) {
try {
let videoFiles = [];
let pageToken;
const videoMimeTypes = ["video/mp4", "video/quicktime", "video/mov"]; // Instagramがサポートする動画形式
const mimeTypeQuery = videoMimeTypes.map(mime => `mimeType='${mime}'`).join(' or ');
// Drive API を使用してファイルリストを取得
do {
// フォルダ内の指定されたMIMEタイプのファイルかつゴミ箱に入っていないものをクエリ
const query = `'${folderId}' in parents and (${mimeTypeQuery}) and trashed=false`;
const response = Drive.Files.list({
q: query,
fields: "nextPageToken, files(id, name, mimeType)",
pageToken: pageToken,
pageSize: 100 // 一度に取得するファイル数
});
if (response.files && response.files.length > 0) {
videoFiles = videoFiles.concat(response.files);
}
pageToken = response.nextPageToken;
} while (pageToken && videoFiles.length < 1000); // ページネーション処理と、念のため取得件数に上限を設定
if (videoFiles.length === 0) {
Logger.log(`フォルダID '${folderId}' 内に対象の動画ファイル (${videoMimeTypes.join(', ')}) が見つかりませんでした。`);
return null;
}
Logger.log(`フォルダ '${folderId}' 内に見つかった動画ファイル数: ${videoFiles.length}`);
videoFiles.forEach(file => Logger.log(` - ファイル名: ${file.name}, ID: ${file.id}, MIMEタイプ: ${file.mimeType}`));
// 見つかったファイルの中からランダムに1つを選択
const randomIndex = Math.floor(Math.random() * videoFiles.length);
const selectedFile = videoFiles[randomIndex];
Logger.log(`ランダムに選択された動画ファイル: ${selectedFile.name} (ID: ${selectedFile.id})`);
return selectedFile.id;
} catch (e) {
Logger.log(`フォルダ (ID: ${folderId}) からの動画ファイルID取得中にエラーが発生しました: ${e.toString()}`);
Logger.log(`スタックトレース: ${e.stack || 'N/A'}`);
return null;
}
}
/**
* Instagramメディアコンテナを作成します。
* Instagram Graph API の /media エンドポイントを使用します。
* @param {string} igUserId InstagramビジネスアカウントID
* @param {string} accessToken Facebookユーザーアクセストークン
* @param {string} videoUrl Google Driveの動画公開URL
* @param {string} caption リールのキャプション
* @returns {string|null} 作成されたメディアコンテナID または null (エラー時)
*/
function createInstagramMediaContainer(igUserId, accessToken, videoUrl, caption) {
const endpoint = `${GRAPH_API_BASE_URL}/${igUserId}/media`;
const payload = {
media_type: "REELS",
video_url: videoUrl,
caption: caption,
share_to_feed: SHARE_TO_FEED, // フィードにもシェアするか
access_token: accessToken
};
// オプションのカバー画像やサムネイルオフセットを追加
if (typeof COVER_IMAGE_URL !== 'undefined' && COVER_IMAGE_URL) payload.cover_url = COVER_IMAGE_URL;
else if (typeof THUMB_OFFSET_MS !== 'undefined' && THUMB_OFFSET_MS) payload.thumb_offset = THUMB_OFFSET_MS;
const options = {
method: "post",
payload: payload,
muteHttpExceptions: true // エラー時でもレスポンスを取得
};
try {
Logger.log(`メディアコンテナ作成リクエスト: POST ${endpoint} Payload: ${JSON.stringify(payload, null, 2).replace(accessToken, "[ACCESS_TOKEN_REDACTED]")}`);
const response = UrlFetchApp.fetch(endpoint, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
Logger.log(`メディアコンテナ作成レスポンス (${responseCode}): ${responseBody}`);
if (responseCode === 200) {
const jsonResponse = JSON.parse(responseBody);
// レスポンスから作成されたコンテナIDを取得
return jsonResponse.id;
} else {
// エラーが発生した場合、詳細をログ出力
logApiError("メディアコンテナ作成エラー", responseBody);
return null;
}
} catch (e) {
Logger.log(`メディアコンテナ作成リクエスト中に例外が発生: ${e.message}`);
return null;
}
}
/**
* メディアコンテナのアップロード/処理ステータスを確認します。
* Instagram APIは非同期で動画を処理するため、公開前にステータス確認が必要です。
* @param {string} creationId 確認するメディアコンテナID
* @param {string} accessToken Facebookユーザーアクセストークン
* @returns {boolean} 処理が成功したかどうか (FINISHED なら true)
*/
function checkUploadStatus(creationId, accessToken) {
const endpoint = `${GRAPH_API_BASE_URL}/${creationId}`;
const params = { fields: "status_code,status", access_token: accessToken };
const MAX_RETRIES = 30; // 最大試行回数 (30回)
const RETRY_INTERVAL_MS = 10000; // 試行間隔 (10秒) -> 合計で最大 30 * 10秒 = 300秒 (5分)
for (let i = 0; i < MAX_RETRIES; i++) {
Utilities.sleep(RETRY_INTERVAL_MS); // 指定時間待機
const queryString = Object.keys(params).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`).join('&');
const requestUrl = `${endpoint}?${queryString}`;
try {
Logger.log(`ステータス確認リクエスト (${i + 1}/${MAX_RETRIES}): GET ${requestUrl.replace(accessToken, "[ACCESS_TOKEN_REDACTED]")}`);
const response = UrlFetchApp.fetch(requestUrl, { muteHttpExceptions: true });
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
Logger.log(`ステータス確認レスポンス (${responseCode}): ${responseBody}`);
if (responseCode === 200) {
const jsonResponse = JSON.parse(responseBody);
const statusCode = jsonResponse.status_code;
Logger.log(`現在のコンテナステータス: ${statusCode} (詳細: ${jsonResponse.status || 'N/A'})`);
// ステータスコードを確認
if (statusCode === "FINISHED") {
// 処理完了
return true;
} else if (statusCode === "ERROR" || statusCode === "EXPIRED") {
// エラーまたは期限切れ
logApiError("コンテナステータスエラー", responseBody);
return false;
}
// IN_PROGRESS や QUEUED の場合はループを続けて待機
} else {
// APIからのエラーレスポンス
logApiError("ステータス確認APIエラー", responseBody);
return false; // APIエラーの場合はリトライしても無駄なので終了
}
} catch (e) {
// リクエスト自体に失敗した場合
Logger.log(`ステータス確認リクエスト中に例外が発生: ${e.message}`);
// 例外発生時はリトライせずに終了するか、例外の種類によってはリトライするか判断が必要だが、ここでは終了
return false;
}
}
// MAX_RETRIES 回試行しても FINISHED にならなかった場合
Logger.log("ステータス確認がタイムアウトしました。コンテナ処理が完了しませんでした。");
return false;
}
/**
* 処理済みのメディアコンテナをInstagramに公開します。
* Instagram Graph API の /media_publish エンドポイントを使用します。
* @param {string} igUserId InstagramビジネスアカウントID
* @param {string} accessToken Facebookユーザーアクセストークン
* @param {string} creationId 公開するメディアコンテナID
* @returns {string|null} 公開されたメディアのID (リールID) または null (エラー時)
*/
function publishInstagramReel(igUserId, accessToken, creationId) {
const endpoint = `${GRAPH_API_BASE_URL}/${igUserId}/media_publish`;
const payload = {
creation_id: creationId, // 公開したいメディアコンテナのIDを指定
access_token: accessToken
};
const options = {
method: "post",
payload: payload,
muteHttpExceptions: true
};
try {
Logger.log(`リール公開リクエスト: POST ${endpoint} Payload: ${JSON.stringify(payload).replace(accessToken, "[ACCESS_TOKEN_REDACTED]")}`);
const response = UrlFetchApp.fetch(endpoint, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
Logger.log(`リール公開レスポンス (${responseCode}): ${responseBody}`);
if (responseCode === 200) {
const jsonResponse = JSON.parse(responseBody);
// 公開成功時、投稿されたメディアのIDが返される
return jsonResponse.id;
} else {
logApiError("リール公開エラー", responseBody);
return null;
}
} catch (e) {
Logger.log(`リール公開リクエスト中に例外が発生: ${e.message}`);
return null;
}
}
/**
* APIエラーレスポンスを詳細にログ出力するためのヘルパー関数です。
* @param {string} context エラーが発生した処理のコンテキスト (例: "メディアコンテナ作成エラー")
* @param {string} responseBody APIからのレスポンスボディ (テキスト形式)
*/
function logApiError(context, responseBody) {
Logger.log(`${context}. レスポンスボディ: ${responseBody}`);
try {
const errorJson = JSON.parse(responseBody);
if (errorJson.error) {
const err = errorJson.error;
Logger.log(` API エラーメッセージ: ${err.message || 'N/A'}`);
Logger.log(` API エラータイプ: ${err.type || 'N/A'}`);
Logger.log(` API エラーコード: ${err.code || 'N/A'}`);
Logger.log(` API エラーサブコード: ${err.error_subcode || 'N/A'}`);
Logger.log(` API fbtrace_id: ${err.fbtrace_id || 'N/A'}`); // Metaサポートへの問い合わせに役立つID
}
} catch (e) {
Logger.log(` レスポンスボディのJSONパースに失敗しました。`);
}
}
貼り付けたコードの上部にある以下の設定項目を、ステップ1およびステップ2で取得したご自身の情報に置き換えます。
const USER_ACCESS_TOKEN = "ここにステップ1-3で取得したユーザーアクセストークンを貼り付け";
const INSTAGRAM_BUSINESS_ACCOUNT_ID = "ここにステップ1-5で取得したInstagramビジネスアカウントIDを貼り付け";
const TARGET_GOOGLE_DRIVE_FOLDER_ID = "ここにGoogle DriveのフォルダIDを貼り付け";
REEL_CAPTION
や SHARE_TO_FEED
、オプションの COVER_IMAGE_URL
, THUMB_OFFSET_MS
も必要に応じて調整してください。
スクリプトを保存します(ファイル > 保存、または Ctrl+S / Cmd+S)。プロジェクト名を分かりやすいものに変更しておくと良いでしょう。
提供されたGASスクリプトは、Instagram Graph APIを利用してリールを投稿するための一連の処理を実行します。主要な関数ごとにその役割とロジックを見ていきましょう。
postReelFromGoogleDrive()
この関数がスクリプトのエントリーポイントです。リール投稿全体の流れを制御します。
USER_ACCESS_TOKEN
, INSTAGRAM_BUSINESS_ACCOUNT_ID
, TARGET_GOOGLE_DRIVE_FOLDER_ID
)が正しく設定されているかを確認します。未設定の場合はエラーメッセージを出力して処理を終了します。getRandomVideoFileIdFromFolder
関数を呼び出し、指定されたGoogle Driveフォルダから投稿する動画ファイルをランダムに1つ選択します。Drive.Files.get
)からそのファイルの公開URL(webContentLink
)を取得します。ファイルが存在しない場合や、共有設定が正しくない場合はエラーとなります。createInstagramMediaContainer
関数を呼び出し、取得した動画URLとキャプション、その他の設定(フィードシェア、カバー画像など)を指定して、Instagram Graph APIにメディアコンテナの作成をリクエストします。この時点ではまだ投稿されず、Instagram側で動画ファイルのダウンロードと処理が始まります。checkUploadStatus
関数を呼び出し、作成したメディアコンテナの処理ステータスが完了するまでポーリング(定期的な確認)を行います。Instagram側での動画処理には時間がかかることがあるため、この待機処理が必要です。処理失敗やタイムアウトの場合はエラーとなります。publishInstagramReel
関数を呼び出し、処理済みのメディアコンテナを指定してInstagram Graph APIに公開をリクエストします。エラーが発生した場合は、try...catch
ブロックで捕捉し、詳細なエラー情報をログに出力するようになっています。
getRandomVideoFileIdFromFolder(folderId)
この関数は、指定されたGoogle Driveフォルダ内から投稿に適した動画ファイル(MP4, MOV)をランダムに1つ選び出す役割を担います。
Drive.Files.list()
メソッドを使用しています。これはGoogle Drive APIの一部で、GASの「サービス」でDrive APIを有効にすることで利用可能になります。q
パラメータで検索クエリを指定しています。'${folderId}' in parents
で指定フォルダ内を検索し、(${videoMimeTypes.join(' or ')})
でMP4またはMOV形式のファイルをフィルタリングしています。trashed=false
はゴミ箱に入っていないファイルを対象とします。fields
パラメータで取得するファイル情報(id
, name
, mimeType
)を絞り込むことで、不要な情報取得を避け、処理を効率化しています。nextPageToken
を使用したページネーション処理を行っています。Math.random()
を使ってランダムに1つを選択し、そのIDを返します。createInstagramMediaContainer
, checkUploadStatus
, publishInstagramReel
, logApiError
これらの関数は、Instagram Graph APIとの通信を担当します。GASの UrlFetchApp
サービスを使用して、HTTPリクエスト(POSTやGET)を送信し、レスポンスを処理します。
createInstagramMediaContainer
: /media
エンドポイントに対し、動画URLやキャプションなどのパラメータを付けてPOSTリクエストを送信します。成功すると、後続の処理で必要となる「メディアコンテナID」がレスポンスとして返されます。checkUploadStatus
: メディアコンテナIDに対し、/creation_id
エンドポイントにGETリクエストを繰り返し送信し、ステータスを確認します。status_code
が "FINISHED"
になるのを待ちます。Utilities.sleep()
は、APIへの連続アクセスを避け、サーバーに負荷をかけすぎないようにするための待機処理です。Instagram側での動画処理時間は動画のサイズなどによって変動するため、ある程度の待機とポーリングが必要になります。publishInstagramReel
: 処理が完了したメディアコンテナIDを /media_publish
エンドポイントにPOSTリクエストで送信し、リールとして公開します。成功すると、公開されたリールのメディアIDが返されます。logApiError
: APIからのレスポンスでエラーが発生した場合に、レスポンスボディの詳細(エラーメッセージ、エラーコードなど)をログに出力するための補助関数です。トラブルシューティングの際に非常に役立ちます。これらの関数では、APIのバージョン (FACEBOOK_GRAPH_API_VERSION
) を指定しており、Metaのドキュメントで推奨される安定版を使用することが推奨されます。
コードの設定が完了したら、実際にスクリプトを実行してみましょう。
postReelFromGoogleDrive
関数を選択します。スクリプトがGoogle Driveのファイルにアクセスしたり、外部サービス(Instagram Graph API)に接続したりするには、ユーザーの許可が必要です。
これで、スクリプトが実行されるための権限が付与されます。
スクリプトが実行されている間、または完了した後に、その処理の経過を確認できます。
Logger.log()
で出力されたメッセージが表示されます。処理の開始、各ステップの状況、取得したID、APIレスポンス、エラーメッセージなどが確認できます。ログを確認しながら、スクリプトが意図した通りに動作しているか、またはどこで問題が発生しているかを把握することができます。
スクリプトの実行中に問題が発生する可能性はゼロではありません。ここでは、よくあるトラブルとその原因、対処法をいくつか紹介します。
instagram_content_publish
など)が正しく選択されていなかった可能性があります。Meta for Developersのアプリ設定や、グラフAPIエクスプローラでのトークン再生成時に権限を再確認してください。FACEBOOK_GRAPH_API_VERSION
に指定したバージョンが古すぎるか、逆に最新すぎて不安定な可能性があります。Metaのドキュメントで推奨されている安定版バージョンを確認し、スクリプトのバージョンを更新してみてください。fbtrace_id
がログに含まれている場合: これはMeta側でリクエストを追跡するためのIDです。APIのエラーメッセージに含まれている場合、Metaの開発者サポートに問い合わせる際にこのIDを提供すると、原因特定の助けになることがあります。TARGET_GOOGLE_DRIVE_FOLDER_ID
が間違っている可能性があります。ブラウザのアドレスバーから正確なフォルダIDをコピーしたか確認してください。エラーログを注意深く読むことが、問題解決への第一歩です。特にAPIからのレスポンスボディには、エラーの原因に関する詳細な情報が含まれていることが多いです。
提供されたスクリプトは、Google Driveのフォルダからランダムに動画を選択して投稿する基本的な機能を提供します。しかし、GASの柔軟性を活かせば、さらに様々な応用が可能です。
これらの応用を実現するには、Google スプレッドシートサービスや他のGASサービス(MailApp
, SlackApp
など、SlackはGASの組み込みサービスではないため外部ライブラリ等が必要な場合あり)の使い方、さらには複雑なデータ構造の扱いなどを学ぶ必要があります。
この記事では、Google Apps ScriptとInstagram Graph APIを連携させ、Google Drive上の動画ファイルをInstagramリールとして自動投稿する仕組みの構築方法を解説しました。事前準備からAPIキーの取得、GASプロジェクトのセットアップ、コードの解説、実行方法、そしてトラブルシューティングまで、一連の流れを詳細に見てきました。
この自動化によって、これまで手作業で行っていたリール投稿の負担が大幅に軽減され、コンテンツ制作や他のマーケティング活動により時間を割けるようになります。
また、今回のスクリプトはGASと外部API連携の具体的な学習例としても非常に有用です。UrlFetchApp
を使ったHTTPリクエストの送信、JSON形式のデータの解析、APIドキュメントの読み方、エラーハンドリング、そしてGoogleサービス(Drive)との連携など、クラウドプラットフォーム上での自動化開発に必要な多くの要素が含まれています。
ぜひ、このガイドを参考に、ご自身のInstagram運用を効率化し、さらに進んだGAS開発に挑戦してみてください。自動化の可能性は無限大です。