Google Drive/Slides API さわってみた
1. はじめに
社内メンバーと一緒に提案書自動生成アプリの開発を試みた際、Google Drive API や Google Slide API を使って、Google Drive上のファイルの読み書きやGoogleスライドの作成や編集を行ったことがありました。
コーディングAIにほとんど書かせていましたが、意外と引っ掛かってばかりだったので、備忘録として残しておきます。
なお、アプリ開発については以下の記事をご覧ください。(執筆中)
2. Google Drive API 等の認証について
最初に必要となるのがOAuth2.0認証です。この章では、認証の仕組みから実装方法まで、実際のコード例を交えながら解説します。
OAuth2.0の簡単な流れ
セキュリティ確保のため、OAuth2.0という認証プロトコルが使用されています。簡単に言うと、以下のような流れで認証が行われます:
- アプリケーションがGoogleに認証リクエストを送る
- ユーザーがブラウザでGoogleアカウントにログインし、アプリケーションへの権限を許可
- Googleがアクセストークンとリフレッシュトークンを発行
- アプリケーションがこのトークンを使ってAPIにアクセス
重要なポイントは、アプリケーションがユーザーのパスワードを直接扱わないことです。代わりに、トークンという一時的な認証情報を使用します。
環境構築
プロジェクトをセットアップしておきます。
# プロジェクトディレクトリを作成して移動
mkdir google-api-project
cd google-api-project
package.jsonを作成
{
"name": "google-api-project",
"version": "1.0.0",
"type": "module",
"scripts": {},
"devDependencies": {}
}
# TypeScriptとts-nodeをインストール
$ npm install -D typescript tsx @types/node
# TypeScriptの設定ファイルを作成
$ npx tsc --init
# 必要なパッケージをインストール
$ npm install googleapis @google-cloud/local-auth google-auth-library
認証情報の取得方法(Google Cloud Console)
Google Drive API 等を使うには、まずGoogle Cloud Consoleで認証情報を作成する必要があります。
手順
-
Google Cloud Consoleにアクセス
- Google Cloud Consoleにアクセス
-
プロジェクトを作成
- 新しいプロジェクトを作成(または既存のプロジェクトを選択)
-
APIを有効化
- 「APIとサービス」→「ライブラリ」から以下のAPIを検索して有効化:
- Google Drive API
- Google Slides API
- 「APIとサービス」→「ライブラリ」から以下のAPIを検索して有効化:
-
OAuth同意画面を設定
- 「APIとサービス」→「OAuth同意画面」
- ユーザータイプを選択(内部)
- アプリケーション名などの基本情報を入力
-
認証情報を作成
- 「APIとサービス」→「認証情報」→「認証情報を作成」
- 「OAuthクライアントID」を選択
- アプリケーションの種類:「デスクトップアプリ」
- 作成後、JSONファイルをダウンロード(これが
credentials.json)
トークンの取得と保存
認証情報(credentials.json)を取得したら、次は実際にトークンを取得してみましょう。
ディレクトリ構成
google-api-project/
├── package.json
├── package-lock.json
├── tsconfig.json
├── credentials.json # Google Cloud Consoleからダウンロードした認証情報
├── token.json # 生成されるトークンファイル(初回実行後)
├── getToken.ts # トークン取得スクリプト(この章で作成)
├── utils.ts # トークン読み込み関数(この章で作成、全章で使用)
├── searchAndReadPdfFiles.ts #(3章で作成)
├── createPresentationInFolder.ts #(4章で作成)
└── createPresentation.ts #(4章で作成)
トークン取得スクリプトの実装
import { promises as fs } from "fs";
import type { Credentials } from "google-auth-library";
import { authenticate } from "@google-cloud/local-auth";
// アプリケーションがアクセスできる範囲を定義。
// 必要最小限のスコープを指定することがセキュリティのベストプラクティスだが
// 本記事全体では簡単のために全権限を付与。
const SCOPES = [
"https://www.googleapis.com/auth/drive", // Google Drive の全権限
"https://www.googleapis.com/auth/presentations" // Google Slides の全権限
];
// 認証情報のファイルパス(プロジェクトルートに配置)
const CREDENTIALS_PATH = "./credentials.json";
const TOKEN_PATH = "./token.json";
/**
* 認証情報を保存する
* 認証情報のファイルはtoken.jsonに保存する
*/
async function saveCredentials(credentials: Credentials): Promise<void> {
const content = await fs.readFile(CREDENTIALS_PATH);
const keys = JSON.parse(content.toString());
const key = keys.installed; // OAuthクライアントはデスクトップアプリ設定の想定
const payload = JSON.stringify({
type: "authorized_user",
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: credentials.refresh_token,
});
await fs.writeFile(TOKEN_PATH, payload);
console.log("Token saved to:", TOKEN_PATH);
}
/**
* 認証情報のファイルを使用してOAuth2認証を行う
* 認証情報のファイルはcredentials.jsonを使用する
* 認証情報のファイルはtoken.jsonに保存する
*/
async function main(): Promise<void> {
console.log("新しい認証を開始します...");
console.log("ブラウザが自動で開きます。認証を完了してください。");
// 認証情報のファイルを使用してOAuth2認証を行い、トークンを取得する
const client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
// 認証情報
const credentials = client.credentials;
// 認証情報が存在する場合は保存する
if (credentials) {
console.log("認証が完了しました。トークンを保存します...");
await saveCredentials(credentials);
} else {
console.log("認証に失敗しました - credentialsが空です");
}
}
main();
実行方法
# トークン取得スクリプトを実行
$ npx tsx getToken.ts
実行すると、自動的にブラウザが開き、Googleアカウントでのログインと権限の許可を求められます。許可すると、token.jsonが生成されます。
なお、credentials.jsonとtoken.jsonには機密情報が含まれているため、Gitのコミットや公開リポジトリへのアップロードは絶対にしないようにしてください。
保存したトークンの読み込み
一度トークンを取得したら、次回以降はtoken.jsonを読み込むだけでAPIを使用できます。
import { promises as fs } from "fs";
import { google, Auth } from "googleapis";
// 認証情報のファイルパス(プロジェクトルートに配置)
const TOKEN_PATH = "./token.json";
/**
* token.jsonを読み込む
*/
export async function loadSavedCredentials(): Promise<Auth.OAuth2Client> {
try {
const content = await fs.readFile(TOKEN_PATH, "utf-8");
const credentials = JSON.parse(content);
console.log("トークンを読み込みました");
return google.auth.fromJSON(credentials) as Auth.OAuth2Client;
} catch (err) {
throw new Error("トークンが見つかりません。新しい認証が必要です。");
}
}
使用イメージ
import { loadSavedCredentials } from "./utils";
import { google } from "googleapis";
async function useDriveAPI() {
// 保存したトークンを読み込む
const auth = await loadSavedCredentials();
// Drive APIクライアントを初期化
const drive = google.drive({ version: "v3", auth });
// APIを使用する
const res = await drive.files.list({
pageSize: 10,
});
console.log("Files:", res.data.files);
}
3. Google Drive API
この章では、Google Drive APIを使ったファイル操作をまとめています。PDFファイルの検索とダウンロード処理を中心に、共有ドライブの扱い方についても触れています。
3.1 PDFファイルの検索とダウンロード
以下は共有ドライブからPDFファイルを検索してダウンロードする実装の例です。
import { google, Auth } from "googleapis";
import { Readable } from "stream";
import { loadSavedCredentials } from "./utils.js";
// 共有ドライブID(https://drive.google.com/drive/folders/XXXXX のXXXXXがID)
const FOLDER_ID = "your_shared_drive_id_here";
// 検索キーワード
const SEARCH_TERM = "検索したいキーワード";
/**
* PDFファイルを検索してダウンロードする
*/
async function searchAndReadPdfFiles(
oAuth2Client: Auth.OAuth2Client,
searchTerm: string
): Promise<Buffer[]> {
// 認証済みのOAuth2クライアントを使用してDrive APIを初期化
const drive = google.drive({ version: "v3", auth: oAuth2Client });
// PDFファイルを検索するクエリ
// ファイル名または内容に searchTerm を含むPDFを検索。
// `fullText`での検索は、テキストベースのPDFでのみ機能する。
// 画像のみのPDFではテキストが抽出できないため、検索にヒットしないようだ。
const query = `
trashed = false and
mimeType="application/pdf" and
(name contains "${searchTerm}" or fullText contains "${searchTerm}")
`;
// 共有ドライブ対応のファイル検索
const response = await drive.files.list({
q: query, // 検索条件
pageSize: 5, // 取得ファイル件数
fields: "nextPageToken, files(id, name, mimeType, createdTime, size)",
// --- 共有ドライブ対応の必須フラグ ---
// 検索対象を「特定の共有ドライブ内のみ」に限定
// 他の選択肢: "user"(マイドライブのみ)、"allDrives"(すべてのドライブ)
corpora: "drive",
// 共有ドライブ内のアイテムを検索結果に含める(デフォルトは false)
includeItemsFromAllDrives: true,
// 共有ドライブを使うことを明示(共有ドライブのファイル操作時は必須)
supportsAllDrives: true,
// 検索対象の共有ドライブID(corpora: "drive" の場合は必須)
driveId: FOLDER_ID,
});
const files = response.data.files;
const bufferList: Buffer[] = [];
if (!files || files.length === 0) {
console.log(`検索語 "${searchTerm}" に一致するPDFファイルが見つかりませんでした。`);
return bufferList;
}
console.log(`検索結果: ${files.length}件のPDFファイルが見つかりました`);
// 各ファイルをダウンロード
for (const file of files) {
if (file.id && file.mimeType === "application/pdf") {
try {
console.log(`PDFファイルをダウンロード中: ${file.name}`);
// ストリーム形式でファイルをダウンロード
const downloadResponse = await drive.files.get({
fileId: file.id,
alt: "media", // メタデータではなく実際のコンテンツを取得
supportsAllDrives: true, // 共有ドライブ対応
}, {
// 大きなファイルをダウンロードする際は、
// ストリームを使うことで一度にすべてをメモリに読み込まず、
// 小さな塊ごとに処理できる。
// これによりメモリ不足を防げる。
responseType: "stream" // ストリームとして受け取る(メモリ効率向上)
});
// ストリームをバッファに変換
const chunks: Buffer[] = [];
const stream = downloadResponse.data as Readable;
for await (const chunk of stream) {
chunks.push(chunk);
}
bufferList.push(Buffer.concat(chunks));
} catch (error) {
console.error(`ダウンロードエラー (${file.name}):`, error);
}
}
}
return bufferList;
}
/**
* メイン処理
*/
async function main() {
// OAuth2クライアントを取得(utils.tsのloadSavedCredentials関数を使用)
const oAuth2Client = await loadSavedCredentials();
// PDFファイルを検索してダウンロード
const buffers = await searchAndReadPdfFiles(oAuth2Client, SEARCH_TERM);
console.log(`${buffers.length}件のPDFファイルをダウンロードしました`);
}
main();
実行方法
- コード内の定数を設定:
// 共有ドライブIDを実際のIDに変更
const FOLDER_ID = "your_shared_drive_id_here";
// 検索キーワードを設定
const SEARCH_TERM = "検索したいキーワード";
- 実行:
$ npx tsx searchAndReadPdfFiles.ts
トークンを読み込みました
検索結果: 1件のPDFファイルが見つかりました
PDFファイルをダウンロード中: sample.pdf
1件のPDFファイルをダウンロードしました
3.2 補足:検索クエリのバリエーション
Google Drive APIでは、クエリ言語を使って柔軟にファイルを検索できます。
// ゴミ箱に入っていないファイル
const query1 = `trashed = false`;
// PDFファイルのみ
const query2 = `mimeType="application/pdf"`;
// ファイル名に特定の文字列を含む
const query3 = `name contains "提案書"`;
// 複数条件を組み合わせる
const query4 = `
trashed = false and
mimeType="application/pdf" and
(name contains "提案書" or fullText contains "提案書")
`;
// 特定の日付以降に作成されたファイル
const query5 = `createdTime >= "2024-01-01T00:00:00"`;
4. Google Slides API
この章では、Google Slides APIを使ったプレゼンテーション作成について説明します。Drive APIとの連携、スライドの作成、テキストの挿入など、基本的な操作を実践します。
4.1 プレゼンテーションの作成
プレゼンテーションを作成する際は、Slides APIではなくDrive APIを使用します(Slides APIではフォルダを指定して作成できない)。これにより、Google Drive上の特定のフォルダにプレゼンテーションを作成できます。
import { google, Auth } from "googleapis";
/**
* Google Driveにプレゼンテーションを作成し、IDを返す関数
*/
export async function createPresentationInFolder(
oAuth2Client: Auth.OAuth2Client,
title: string,
folderId: string
): Promise<string> {
// Google Drive APIを初期化する
const drive = google.drive({ version: "v3", auth: oAuth2Client });
// Google Drive上にプレゼンテーションを作成する
const response = await drive.files.create({
requestBody: {
name: title,
// Google スライドのファイルとして作成
mimeType: "application/vnd.google-apps.presentation",
parents: [folderId],
},
fields: "id", // レスポンスに含める属性
// 共有フォルダも含めて検索・操作の対象にする。
// 共有ドライブを使う場合は必須。
supportsAllDrives: true,
});
// 作成したプレゼンテーションのIDを取得
const presentationId = response.data.id;
if (!presentationId) {
throw new Error("指定したフォルダにプレゼンテーションを作成できませんでした");
}
// プレゼンテーションを作成すると、1ページ目のタイトルスライドが自動的に作成される
console.log(`プレゼンテーションを作成しました: ${presentationId}`);
return presentationId;
}
4.2 スライド作成・テキスト挿入
プレゼンテーションの作成からスライド追加、テキスト挿入までの実装例です。
import { google, slides_v1 } from "googleapis";
import { loadSavedCredentials } from "./utils.js";
import { createPresentationInFolder } from "./createPresentationInFolder.js";
// Google DriveのフォルダID(https://drive.google.com/drive/folders/XXXXX のXXXXXがID)
const FOLDER_ID = "yout-folder-id";
/**
* メイン関数
*/
async function main(): Promise<void> {
// 認証情報を読み込む(2章を参照)
const oAuth2Client = await loadSavedCredentials();
// プレゼンテーションを作成
const presentationId = await createPresentationInFolder(
oAuth2Client,
"サンプルプレゼンテーション",
FOLDER_ID
);
// Google Slides APIの初期化
const slides = google.slides({ version: "v1", auth: oAuth2Client });
// プレゼンテーションを取得する
const presentation = await slides.presentations.get({ presentationId });
// Google スライドには複数のレイアウトテンプレートが用意されている。
// 新しいスライドを追加する際は、これらのレイアウトを指定する。
// 利用可能なレイアウト名(displayName)の例:
// "タイトルと本文"
// "タイトルのみ"
// "セクション ヘッダー"
// など
const slideLayout = presentation.data.layouts?.find(
(layout) => layout.layoutProperties?.displayName === "タイトルと本文"
);
if (!slideLayout || !slideLayout.objectId) {
throw new Error("スライドレイアウトが見つかりませんでした");
}
const layoutId = slideLayout.objectId;
// スライドの内容を定義
const slideContents = [
{ title: "タイトルスライド", content: "プレゼンテーションの概要" },
{ title: "第1章", content: "内容1\n内容2\n内容3" },
{ title: "第2章", content: "内容A\n内容B\n内容C" },
];
// ステップ1: 必要な数だけスライドを一括作成
// 1ページ目は自動作成されているので、2ページ目以降を作成
const createSlideRequests: slides_v1.Schema$Request[] = slideContents.slice(1).map((_, index) => ({
createSlide: {
insertionIndex: index + 1,
slideLayoutReference: {
layoutId: layoutId,
},
},
}));
await slides.presentations.batchUpdate({
presentationId,
requestBody: {
requests: createSlideRequests,
},
});
console.log(`${slideContents.length}枚のスライドを作成しました`);
// ステップ2: 再度getして最新の状態を取得
// 重要: batchUpdate後は再度getしないと、新しく作成されたスライドのobjectIdが取得できない
const updatedPresentation = await slides.presentations.get({ presentationId });
// ステップ3: 各スライドにテキストを挿入
for (let i = 0; i < slideContents.length; i++) {
const slide = updatedPresentation.data.slides?.[i];
if (!slide || !slide.pageElements) {
throw new Error(`${i + 1}ページ目のスライドの取得に失敗しました`);
}
const pageElements = slide.pageElements;
// pageElementsは、各スライド内の要素(テキストボックス、図形など)の配列。
// 「タイトルと本文」レイアウトの場合:
// pageElements[0]: タイトル用のテキストボックス
// pageElements[1]: 本文用のテキストボックス
// 各要素にはobjectIdという一意のIDが割り当てられており、
// このIDを使ってテキストを挿入したり要素を編集したりする。
const titleObjectId = pageElements[0]?.objectId;
const bodyObjectId = pageElements[1]?.objectId;
if (!titleObjectId || !bodyObjectId) {
throw new Error(`スライド ${i + 1} のテキストボックスIDが見つかりません`);
}
const textInsertRequests = [
// タイトルを挿入
{
insertText: {
objectId: titleObjectId, // タイトル用テキストボックスのID
text: slideContents[i]?.title ?? "",
},
},
// 本文を挿入
{
insertText: {
objectId: bodyObjectId, // 本文用テキストボックスのID
text: slideContents[i]?.content ?? "",
},
},
];
await slides.presentations.batchUpdate({
presentationId,
requestBody: {
requests: textInsertRequests,
},
});
}
console.log("すべてのスライドにテキストを挿入しました");
console.log("プレゼンテーションの作成が完了しました");
console.log(`URL: https://docs.google.com/presentation/d/${presentationId}/edit`);
}
main();
実行方法
- コード内の定数を設定:
// 共有ドライブIDを実際のIDに変更
const FOLDER_ID = "yout-folder-id";
- 実行:
$ npx tsx createPresentation.ts
トークンを読み込みました
プレゼンテーションを作成しました: 1Oa......8AU
3枚のスライドを作成しました
すべてのスライドにテキストを挿入しました
プレゼンテーションの作成が完了しました
URL: https://docs.google.com/presentation/d/1Oa......8AU/edit
以下のようなスライドが作成されるはずです。


5. さいごに
ここまでご覧いただきありがとうございました。
Google Drive API や Google Slide API を使ってみたい人やエラーばかりで技術検証が進まない方の参考になれば幸いです。
参考
OAuth 2.0 認証
- Using OAuth 2.0 to Access Google APIs - OAuth 2.0の基本と実装方法
認証情報の作成
- Create access credentials - Google Cloud Consoleでの認証情報作成手順
Google Drive API
- Google Drive API Overview - Drive APIの概要とガイド
- Google Drive API Reference (v3) - APIリファレンス
Google Slides API
- Google Slides API Overview - Slides APIの概要とガイド
- Google Slides API Reference - APIリファレンス
NCDC株式会社( ncdc.co.jp/ )のテックブログです。 主にエンジニアチームのメンバーが投稿します。 募集中のエンジニアのポジションや、採用している技術スタックの紹介などはこちら( github.com/ncdcdev/recruitment )をご覧ください!
Discussion