第5章(応用編):AIとGoogle Drive APIで議事録・資料整理を自動化する方法
■はじめに
応用編では、AIとAPIを連携させた業務自動化を扱います。
第1章(応用編):AIとAPIで問い合わせメールを管理表へ自動登録する方法
まだ第1章を読んでいない方は、先に第1章で全体像を確認してから本章を読むと理解しやすくなります。
本章は前章までの続きです。
第2章では、Gmail APIで問い合わせメールを取得し、AIで問い合わせ内容を分類する処理を作成しました。
第3章では、そのAI分類結果をGoogle Sheets APIで管理表へ自動登録する処理を作成しました。
第4章では、管理表の内容をもとにSlack APIで担当者通知を送る処理を作成しました。
まだ第2章から第4章を読んでいない方は、先に確認してから本章を読むと、処理のつながりが分かりやすくなります。
第2章(応用編):AIとGoogle Sheets APIで問い合わせ管理表を作る方法
第3章(応用編):AIとGoogle Sheets APIで管理表を自動作成する方法
第4章(応用編):AIとSlack APIで担当者通知を自動化する方法
この章では、Google Drive APIを使って、議事録や資料整理を自動化する方法を作ります。
会議後の議事録。問い合わせ対応に関連する資料。メールに添付されたファイル。作成した回答案。対応履歴として残したいメモ。
こうした情報は、保存場所がばらばらになると後から探しにくくなります。
・議事録をどこに保存したか分からない
・関連資料がメールやチャットに散らばっている
・フォルダ名が統一されていない
・ファイル名が人によって違う
・あとから探すときに時間がかかる
・対応記録と資料がつながっていない
このような状態では、せっかくAIで作成した議事録や整理した情報も、再利用しにくくなります。
Google Drive APIを使うと、フォルダ作成、ファイル作成、ファイル検索、資料保存を自動化できます。
AIを組み合わせると、議事録の要約、ファイル名作成、保存先分類、資料整理まで自動化できます。
この章では、AIで作成した議事録や整理結果を、Google Drive APIで保存し、後から探しやすい形に整理する処理を作ります。
■この章で作るもの
この章で作る処理は下記です。
AIで議事録や資料内容を整理
→ 保存用のファイル名をAIで作成
→ Google Drive APIで保存先フォルダを確認
→ 必要に応じてフォルダを作成
→ Google Drive APIで議事録ファイルを作成
→ 関連資料を指定フォルダへ保存
→ 保存先URLを取得
→ Google Sheetsの管理表へDrive URLを反映この章では、Google Drive APIを使って、議事録や資料を保存する仕組みを作ります。
問い合わせ対応の管理表と連携すれば、対応内容と関連資料をつなげて管理できます。
■Google Drive APIを使う理由
Google Driveは、議事録や資料の保管場所として使いやすいサービスです。
ただし、人が毎回フォルダを作り、ファイル名を考え、資料を保存していると手間がかかります。
Google Drive APIを使うと、プログラムから下記の操作ができます。
・フォルダを検索する
・フォルダを作成する
・ファイルを作成する
・ファイルを指定フォルダへ保存する
・ファイルURLを取得する
・保存済みファイルを検索するAIは、議事録や資料内容を整理します。
Google Drive APIは、その結果を保存します。
Google Sheets APIは、保存先URLを管理表へ登録します。
役割は下記です。
AI:議事録作成、要約、資料名作成、保存先分類
Google Drive API:フォルダ作成、ファイル作成、資料保存
Google Sheets API:保存先URLを管理表へ反映
Google Apps Script:AI API、Drive API、Sheets APIをつなぐこの役割分担にすると、作成した議事録や資料を、その場限りで終わらせずに整理できます。
・この章で作る処理の全体像
・Google Drive APIを使う理由
・Driveに保存する情報の考え方
・第4章までとのつながり
■Driveに保存する情報
この章では、Google Driveに下記のような情報を保存します。
・会議メモから作成した議事録
・問い合わせ対応に関連する調査メモ
・AIが作成した回答案
・対応時に参照した資料
・資料整理用の要約ファイル
・チーム共有用のナレッジ文書最初からすべてを自動化する必要はありません。
まずは、AIが作成した議事録や整理メモをGoogle Driveへ保存するところから始めます。
そのあと、Google Sheetsの管理表にDrive URLを登録します。
これにより、管理表から関連資料へすぐ移動できるようになります。
■保存ルールを決める
Google Driveを使うときは、保存ルールを決めておくことが重要です。
ルールがないまま自動保存すると、後から探しにくくなります。
この章では、下記のルールで進めます。
親フォルダ:AI業務自動化
用途別フォルダ:議事録、問い合わせ資料、回答案、ナレッジ
ファイル名:日付_分類_件名
保存後:Drive URLをGoogle Sheetsへ登録たとえば、問い合わせ対応の議事録や調査メモなら下記のような名前にします。
2026-05-13_技術問い合わせ_ログインできない件についてファイル名に日付、分類、件名を入れると、後から探しやすくなります。
AIにファイル名を作らせることで、人が毎回ファイル名を考える手間も減らせます。
■ここから解説する内容
・Google Drive APIを使うための準備
・Google Apps ScriptでGoogle Drive APIを有効化する手順
・親フォルダIDを安全に保存する方法
・Google Drive APIでフォルダを検索するコード
・Google Drive APIでフォルダを作成するコード
・AIで保存用ファイル名を作成するプロンプト
・Google Drive APIで議事録ファイルを作成するコード
・保存先URLを取得するコード
・Google Sheets APIで管理表へDrive URLを反映するコード
・第2章から第4章までの処理とつなげる完成コード
・テスト用コード
・運用時の注意点
■Google Drive APIを使うための準備
ここからは、実際にGoogle Drive APIを使う準備に入ります。
Google Apps ScriptでGoogle Drive APIを使う場合は、スクリプト側でGoogle Drive APIを有効化します。
手順は下記です。
1. Google Apps Scriptを開く
2. 左側メニューの「サービス」を開く
3. 「サービスを追加」を選ぶ
4. Google Drive APIを選ぶ
5. 識別子が「Drive」になっていることを確認する
6. 追加するこの設定を行うと、Google Apps Script内で Drive.Files などのGoogle Drive API操作を使えるようになります。
初回実行時には権限の確認が表示されます。
Google Drive内のファイルやフォルダを操作する処理なので、許可する内容を確認して進めます。
■親フォルダを用意する
まず、Google Drive上に親フォルダを用意します。
この章では、親フォルダ名を下記にします。
AI業務自動化この親フォルダの下に、議事録、問い合わせ資料、回答案、ナレッジなどの用途別フォルダを作成します。
Google DriveのURLにはフォルダIDが含まれています。
たとえばURLが下記の場合です。
https://drive.google.com/drive/folders/XXXXXXXXXXXXXXXXXXXXXXXXXXXXこの中の XXXXXXXXXXXXXXXXXXXXXXXXXXXX の部分がフォルダIDです。
このIDをGoogle Apps Scriptのスクリプトプロパティに保存します。
■親フォルダIDを保存する
親フォルダIDは、コードに直接書かず、スクリプトプロパティに保存します。
function setDriveRootFolderId() {
PropertiesService.getScriptProperties().setProperty(
'DRIVE_ROOT_FOLDER_ID',
'<ここにGoogle Driveの親フォルダIDを入れる>'
);
}この関数は初期設定のときだけ実行します。
実行後は、コード内にフォルダIDを直接書かず、スクリプトプロパティから呼び出します。
■設定情報を取得する関数
第4章までで使っている getConfig() に、Driveの親フォルダIDを追加します。
function getConfig() {
return {
aiApiKey: PropertiesService.getScriptProperties().getProperty('AI_API_KEY'),
sheetId: PropertiesService.getScriptProperties().getProperty('SHEET_ID'),
slackBotToken: PropertiesService.getScriptProperties().getProperty('SLACK_BOT_TOKEN'),
slackChannelId: PropertiesService.getScriptProperties().getProperty('SLACK_CHANNEL_ID'),
driveRootFolderId: PropertiesService.getScriptProperties().getProperty('DRIVE_ROOT_FOLDER_ID')
};
}この形にしておくと、AI API、Google Sheets API、Slack API、Google Drive APIで使う設定をまとめて管理できます。
■用途別フォルダ名を決める
保存先フォルダ名を関数で管理します。
function getDriveFolderNames() {
return {
minutes: '議事録',
inquiryMaterials: '問い合わせ資料',
replyDrafts: '回答案',
knowledge: 'ナレッジ'
};
}フォルダ名を関数にまとめておくと、後から変更しやすくなります。
業務に合わせて、顧客別、案件別、月別などに広げることもできます。
■Google Drive APIでフォルダを検索する
親フォルダの下に、指定した名前のフォルダがあるか検索します。
function findFolderByName(parentFolderId, folderName) {
const query = [
`'${parentFolderId}' in parents`,
`name = '${folderName}'`,
`mimeType = 'application/vnd.google-apps.folder'`,
`trashed = false`
].join(' and ');
const response = Drive.Files.list({
q: query,
fields: 'files(id, name)'
});
if (!response.files || response.files.length === 0) {
return null;
}
return response.files[0];
}この関数では、指定した親フォルダ内にある同名フォルダを検索します。
存在しない場合は null を返します。
■Google Drive APIでフォルダを作成する
指定したフォルダが存在しない場合は、新しく作成します。
function createFolder(parentFolderId, folderName) {
const resource = {
name: folderName,
mimeType: 'application/vnd.google-apps.folder',
parents: [parentFolderId]
};
const folder = Drive.Files.create(resource);
return folder;
}Google Drive APIでは、フォルダもファイルの一種として扱います。
mimeType に application/vnd.google-apps.folder を指定すると、フォルダとして作成できます。
■フォルダを取得または作成する
フォルダがあれば取得し、なければ作成する関数を作ります。
function getOrCreateFolder(parentFolderId, folderName) {
const existingFolder = findFolderByName(parentFolderId, folderName);
if (existingFolder) {
return existingFolder;
}
return createFolder(parentFolderId, folderName);
}この関数を使うと、毎回フォルダの存在を気にせず保存先を用意できます。
■用途別フォルダをまとめて準備する
議事録、問い合わせ資料、回答案、ナレッジのフォルダをまとめて作ります。
function setupDriveFolders() {
const config = getConfig();
const folderNames = getDriveFolderNames();
const folders = {};
Object.keys(folderNames).forEach(key => {
folders[key] = getOrCreateFolder(config.driveRootFolderId, folderNames[key]);
});
Logger.log(JSON.stringify(folders, null, 2));
return folders;
}この関数を実行すると、親フォルダ配下に必要な用途別フォルダが作成されます。
すでに存在する場合は、既存フォルダを使います。
■AIで保存用ファイル名を作る
議事録や資料を保存するとき、ファイル名を毎回考えるのは手間です。
AIに内容を渡して、保存用ファイル名を作成させます。
function buildFileNamePrompt(data) {
return `
以下の内容をもとに、Google Driveへ保存するファイル名を作成してください。
【日付】
${data.date || ''}
【分類】
${data.category || ''}
【件名】
${data.subject || ''}
【概要】
${data.summary || ''}
【条件】
・日本語の自然なファイル名にする
・長すぎないようにする
・日付_分類_件名 の形式にする
・ファイル名に使いにくい記号は避ける
・拡張子は付けない
【出力形式】
ファイル名のみを出力してください。
`;
}ファイル名だけを出力させることで、後続処理で扱いやすくなります。
■ファイル名に使えない文字を整える
AIが作ったファイル名に、扱いにくい記号が含まれることがあります。
保存前に簡単に整えます。
function sanitizeFileName(fileName) {
if (!fileName) {
return '無題';
}
return fileName
.replace(/[\\/:*?"<>|]/g, '_')
.replace(/\s+/g, ' ')
.trim();
}この関数を通すことで、ファイル名として扱いやすい形にします。
■AIでファイル名を作成する関数
AI APIを呼び出して、保存用ファイル名を作成します。
第2章で作成した callAiApi() を使います。
function generateFileNameByAi(data) {
try {
const prompt = buildFileNamePrompt(data);
const aiText = callAiApi(prompt);
return sanitizeFileName(aiText);
} catch (error) {
logError('generateFileNameByAi', error);
const fallback = `${data.date || ''}_${data.category || '分類未設定'}_${data.subject || '無題'}`;
return sanitizeFileName(fallback);
}
}AIでファイル名作成に失敗しても、最低限のファイル名を作って処理を続けます。
■議事録作成用プロンプトを作る
会議メモや文字起こしから、AIに議事録を作成させます。
function buildMinutesPrompt(meetingText) {
return `
以下の会議内容をもとに、議事録を作成してください。
【会議内容】
${meetingText}
【条件】
・会議の目的を整理する
・決定事項を整理する
・未決事項を整理する
・担当者とタスクを整理する
・次回確認事項を整理する
・読みやすい見出しを付ける
・ビジネス文書として自然な日本語にする
`;
}録音データそのものではなく、文字起こし済みのテキストや会議メモを渡す形にします。
AIで議事録を作成し、その結果をDriveへ保存します。
■AIで議事録を作成する関数
会議内容をAIへ渡して、議事録本文を作成します。
function generateMinutesByAi(meetingText) {
try {
const prompt = buildMinutesPrompt(meetingText);
return callAiApi(prompt);
} catch (error) {
logError('generateMinutesByAi', error);
return '議事録作成中にエラーが発生しました。\n\n' + error.message;
}
}この関数を使うと、会議メモや文字起こしから議事録本文を作成できます。
■Google Drive APIでGoogleドキュメントを作成する
Google Drive APIを使って、Googleドキュメント形式のファイルを作成します。
function createGoogleDocFile(folderId, fileName, content) {
const resource = {
name: fileName,
mimeType: 'application/vnd.google-apps.document',
parents: [folderId]
};
const blob = Utilities.newBlob(content, 'text/plain', fileName + '.txt');
const file = Drive.Files.create(resource, blob, {
fields: 'id, name, webViewLink'
});
return file;
}この関数で、指定したフォルダにGoogleドキュメントを作成できます。
webViewLink を取得しておくと、あとからGoogle SheetsへURLとして登録できます。
■議事録をDriveへ保存する
AIで作成した議事録を、Driveの議事録フォルダへ保存します。
function saveMinutesToDrive(meetingText, data) {
const folders = setupDriveFolders();
const minutesText = generateMinutesByAi(meetingText);
const fileName = generateFileNameByAi({
date: data.date || new Date().toISOString().slice(0, 10),
category: data.category || '議事録',
subject: data.subject || '会議メモ',
summary: data.summary || ''
});
const file = createGoogleDocFile(folders.minutes.id, fileName, minutesText);
Logger.log(JSON.stringify(file, null, 2));
return file;
}この関数を実行すると、会議内容から議事録を作成し、Google Driveへ保存できます。
■サンプル会議メモでテストする
まずは、サンプルの会議メモでDrive保存をテストします。
function testSaveMinutesToDrive() {
const meetingText = `
問い合わせ管理の自動化について打ち合わせを行った。
Gmail APIで問い合わせメールを取得し、AIで分類する。
分類結果はGoogle Sheetsへ登録する。
高優先度の問い合わせはSlackへ通知する。
次回はDriveへの資料保存を確認する。
`;
const file = saveMinutesToDrive(meetingText, {
date: '2026-05-13',
category: '議事録',
subject: '問い合わせ管理自動化打ち合わせ',
summary: '問い合わせ管理自動化の処理方針を確認した会議'
});
Logger.log('保存先URL: ' + file.webViewLink);
}このテストで、AIが議事録を作成し、Google Driveへ保存できるか確認します。
■問い合わせ資料用の整理文を作る
問い合わせ対応では、調査メモや関連資料も残したくなります。
AI分類結果をもとに、対応メモとして保存する文書を作ります。
function buildInquiryMemoContent(inquiry) {
return [
`# ${inquiry.subject || '問い合わせメモ'}`,
'',
`受付日時:${inquiry.receivedAt || ''}`,
`送信者:${inquiry.from || ''}`,
`分類:${inquiry.category || ''}`,
`優先度:${inquiry.priority || ''}`,
`担当候補:${inquiry.assignee || ''}`,
'',
'## 概要',
inquiry.summary || '',
'',
'## 分類理由',
inquiry.reason || '',
'',
'## 本文',
inquiry.body || ''
].join('\n');
}この関数を使うと、問い合わせ情報をDriveへ保存しやすい形にできます。
■問い合わせメモをDriveへ保存する
問い合わせ1件ごとに、関連メモをGoogle Driveへ保存します。
function saveInquiryMemoToDrive(inquiry) {
const folders = setupDriveFolders();
const content = buildInquiryMemoContent(inquiry);
const fileName = generateFileNameByAi({
date: new Date().toISOString().slice(0, 10),
category: inquiry.category || '問い合わせ',
subject: inquiry.subject || '問い合わせメモ',
summary: inquiry.summary || ''
});
const file = createGoogleDocFile(folders.inquiryMaterials.id, fileName, content);
Logger.log(JSON.stringify(file, null, 2));
return file;
}この関数を使うと、問い合わせ内容をGoogle Driveへ文書として保存できます。
管理表だけでなく、詳細メモとして残せます。
■Drive URLを管理表へ登録する準備
第3章の管理表に、Drive URLを登録できる列を追加します。
既存の列に加えて、下記を追加します。
Drive保存状況
Drive URL
Drive保存日時第4章でSlack通知列を追加しているため、第5章ではさらにDrive関連の列を追加します。
■Drive用ヘッダーを追加する
管理表にDrive関連の列がなければ追加します。
function getInquiryHeaders() {
return [
'受付日時',
'送信者',
'件名',
'問い合わせ概要',
'分類',
'優先度',
'担当候補',
'ステータス',
'分類理由',
'対応メモ',
'スレッドID',
'メッセージID',
'登録日時',
'Slack通知状況',
'Slack通知日時',
'Drive保存状況',
'Drive URL',
'Drive保存日時'
];
}このヘッダーにすることで、問い合わせごとに関連資料の保存状態とURLを持てます。
■Drive関連ヘッダーを追加する
既存表に不足しているDrive列を追加します。
第4章の ensureSlackNotificationHeaders() と同じ考え方です。
function ensureDriveHeaders() {
const config = getConfig();
const sheetName = getInquirySheetName();
const range = `${sheetName}!A1:R1`;
const response = Sheets.Spreadsheets.Values.get(config.sheetId, range);
const existingHeaders = response.values && response.values.length > 0 ? response.values[0] : [];
const requiredHeaders = getInquiryHeaders();
const updatedHeaders = existingHeaders.slice();
requiredHeaders.forEach(header => {
if (!updatedHeaders.includes(header)) {
updatedHeaders.push(header);
}
});
const updateRange = `${sheetName}!A1:${columnNumberToLetter(updatedHeaders.length)}1`;
Sheets.Spreadsheets.Values.update(
{ values: [updatedHeaders] },
config.sheetId,
updateRange,
{ valueInputOption: 'RAW' }
);
}この関数を実行すると、既存管理表を壊さずにDrive用の列を追加できます。
■Drive保存対象を抽出する
管理表から、Drive保存がまだ行われていない問い合わせを抽出します。
function filterDriveSaveTargets(inquiries) {
return inquiries.filter(inquiry => {
return !inquiry.driveStatus && inquiry.status !== '完了';
});
}Drive保存対象は、業務に合わせて変更できます。
たとえば、高優先度だけ保存する、要確認だけ保存する、完了時だけ保存するなどの運用もできます。
■Drive列を含めて行データを読み取る
第4章の convertRowToInquiry() に、Drive関連項目を追加します。
function convertRowToInquiry(row, headers, rowNumber) {
const map = getHeaderIndexMap(headers);
return {
rowNumber: rowNumber,
receivedAt: row[map['受付日時']] || '',
from: row[map['送信者']] || '',
subject: row[map['件名']] || '',
summary: row[map['問い合わせ概要']] || '',
category: row[map['分類']] || '',
priority: row[map['優先度']] || '',
assignee: row[map['担当候補']] || '',
status: row[map['ステータス']] || '',
reason: row[map['分類理由']] || '',
memo: row[map['対応メモ']] || '',
threadId: row[map['スレッドID']] || '',
messageId: row[map['メッセージID']] || '',
registeredAt: row[map['登録日時']] || '',
slackStatus: row[map['Slack通知状況']] || '',
slackNotifiedAt: row[map['Slack通知日時']] || '',
driveStatus: row[map['Drive保存状況']] || '',
driveUrl: row[map['Drive URL']] || '',
driveSavedAt: row[map['Drive保存日時']] || ''
};
}この形にすると、問い合わせごとのDrive保存状況を判定できます。
■Drive保存状況を更新する
Driveへ保存したら、Google Sheetsの管理表に保存状況、URL、保存日時を反映します。
Drive保存状況は16列目、Drive URLは17列目、Drive保存日時は18列目です。
function updateDriveSaveStatus(rowNumber, fileUrl) {
const config = getConfig();
const sheetName = getInquirySheetName();
const range = `${sheetName}!P${rowNumber}:R${rowNumber}`;
const valueRange = {
values: [
['保存済み', fileUrl, new Date().toISOString()]
]
};
Sheets.Spreadsheets.Values.update(valueRange, config.sheetId, range, {
valueInputOption: 'USER_ENTERED'
});
}保存先URLを管理表へ登録しておくと、後から関連資料へすぐ移動できます。
■問い合わせ1件をDriveへ保存する
問い合わせ1件をDriveへ保存し、管理表へURLを反映します。
function saveInquiryToDriveAndUpdateSheet(inquiry) {
try {
const file = saveInquiryMemoToDrive(inquiry);
updateDriveSaveStatus(inquiry.rowNumber, file.webViewLink || '');
Logger.log(`Drive保存完了: row ${inquiry.rowNumber}`);
} catch (error) {
logError('saveInquiryToDriveAndUpdateSheet', error);
}
}Drive保存に失敗した場合は、保存済みにしません。
失敗しているのに保存済みにしてしまうと、あとで確認できなくなります。
■Drive保存対象をまとめて処理する
管理表からDrive保存対象を抽出し、1件ずつDriveへ保存します。
function saveDriveTargets() {
ensureDriveHeaders();
const inquiries = getInquiriesFromSheet();
const targets = filterDriveSaveTargets(inquiries);
if (!targets || targets.length === 0) {
Logger.log('Drive保存対象はありません');
return;
}
targets.forEach(inquiry => {
saveInquiryToDriveAndUpdateSheet(inquiry);
});
Logger.log('Drive保存件数: ' + targets.length);
}この関数を実行すると、管理表の未保存問い合わせをDriveへ保存し、Drive URLを管理表へ戻せます。
■第2章から第5章までをつなげる
ここまでの流れをまとめると、下記になります。
Gmail APIで問い合わせメールを取得
→ AIで問い合わせ内容を分類
→ Google Sheets APIで管理表へ登録
→ Slack APIで担当者へ通知
→ Google Drive APIで関連メモや資料を保存
→ Google Sheets APIでDrive URLを管理表へ反映この一連の流れを実行する関数を作ります。
function runInquiryManagementSlackDriveFlow() {
try {
setupInquiryManagementSheet();
const results = classifyInquiryEmails();
saveNewClassifiedResultsToSheet(results);
notifySlackTargets();
saveDriveTargets();
Logger.log('問い合わせ管理、Slack通知、Drive保存が完了しました');
} catch (error) {
logError('runInquiryManagementSlackDriveFlow', error);
}
}この関数を実行すると、問い合わせ対応の初動だけでなく、関連資料の保存まで自動化できます。
■この章の完成コード
この章で追加する主要コードをまとめると下記です。
function setDriveRootFolderId() {
PropertiesService.getScriptProperties().setProperty(
'DRIVE_ROOT_FOLDER_ID',
'<ここにGoogle Driveの親フォルダIDを入れる>'
);
}
function getConfig() {
return {
aiApiKey: PropertiesService.getScriptProperties().getProperty('AI_API_KEY'),
sheetId: PropertiesService.getScriptProperties().getProperty('SHEET_ID'),
slackBotToken: PropertiesService.getScriptProperties().getProperty('SLACK_BOT_TOKEN'),
slackChannelId: PropertiesService.getScriptProperties().getProperty('SLACK_CHANNEL_ID'),
driveRootFolderId: PropertiesService.getScriptProperties().getProperty('DRIVE_ROOT_FOLDER_ID')
};
}
function getDriveFolderNames() {
return {
minutes: '議事録',
inquiryMaterials: '問い合わせ資料',
replyDrafts: '回答案',
knowledge: 'ナレッジ'
};
}
function findFolderByName(parentFolderId, folderName) {
const query = [
`'${parentFolderId}' in parents`,
`name = '${folderName}'`,
`mimeType = 'application/vnd.google-apps.folder'`,
`trashed = false`
].join(' and ');
const response = Drive.Files.list({
q: query,
fields: 'files(id, name)'
});
if (!response.files || response.files.length === 0) {
return null;
}
return response.files[0];
}
function createFolder(parentFolderId, folderName) {
const resource = {
name: folderName,
mimeType: 'application/vnd.google-apps.folder',
parents: [parentFolderId]
};
const folder = Drive.Files.create(resource);
return folder;
}
function getOrCreateFolder(parentFolderId, folderName) {
const existingFolder = findFolderByName(parentFolderId, folderName);
if (existingFolder) {
return existingFolder;
}
return createFolder(parentFolderId, folderName);
}
function setupDriveFolders() {
const config = getConfig();
const folderNames = getDriveFolderNames();
const folders = {};
Object.keys(folderNames).forEach(key => {
folders[key] = getOrCreateFolder(config.driveRootFolderId, folderNames[key]);
});
Logger.log(JSON.stringify(folders, null, 2));
return folders;
}
function buildFileNamePrompt(data) {
return `
以下の内容をもとに、Google Driveへ保存するファイル名を作成してください。
【日付】
${data.date || ''}
【分類】
${data.category || ''}
【件名】
${data.subject || ''}
【概要】
${data.summary || ''}
【条件】
・日本語の自然なファイル名にする
・長すぎないようにする
・日付_分類_件名 の形式にする
・ファイル名に使いにくい記号は避ける
・拡張子は付けない
【出力形式】
ファイル名のみを出力してください。
`;
}
function sanitizeFileName(fileName) {
if (!fileName) {
return '無題';
}
return fileName
.replace(/[\\/:*?"<>|]/g, '_')
.replace(/\s+/g, ' ')
.trim();
}
function generateFileNameByAi(data) {
try {
const prompt = buildFileNamePrompt(data);
const aiText = callAiApi(prompt);
return sanitizeFileName(aiText);
} catch (error) {
logError('generateFileNameByAi', error);
const fallback = `${data.date || ''}_${data.category || '分類未設定'}_${data.subject || '無題'}`;
return sanitizeFileName(fallback);
}
}
function buildMinutesPrompt(meetingText) {
return `
以下の会議内容をもとに、議事録を作成してください。
【会議内容】
${meetingText}
【条件】
・会議の目的を整理する
・決定事項を整理する
・未決事項を整理する
・担当者とタスクを整理する
・次回確認事項を整理する
・読みやすい見出しを付ける
・ビジネス文書として自然な日本語にする
`;
}
function generateMinutesByAi(meetingText) {
try {
const prompt = buildMinutesPrompt(meetingText);
return callAiApi(prompt);
} catch (error) {
logError('generateMinutesByAi', error);
return '議事録作成中にエラーが発生しました。\n\n' + error.message;
}
}
function createGoogleDocFile(folderId, fileName, content) {
const resource = {
name: fileName,
mimeType: 'application/vnd.google-apps.document',
parents: [folderId]
};
const blob = Utilities.newBlob(content, 'text/plain', fileName + '.txt');
const file = Drive.Files.create(resource, blob, {
fields: 'id, name, webViewLink'
});
return file;
}
function saveMinutesToDrive(meetingText, data) {
const folders = setupDriveFolders();
const minutesText = generateMinutesByAi(meetingText);
const fileName = generateFileNameByAi({
date: data.date || new Date().toISOString().slice(0, 10),
category: data.category || '議事録',
subject: data.subject || '会議メモ',
summary: data.summary || ''
});
const file = createGoogleDocFile(folders.minutes.id, fileName, minutesText);
Logger.log(JSON.stringify(file, null, 2));
return file;
}
function buildInquiryMemoContent(inquiry) {
return [
`# ${inquiry.subject || '問い合わせメモ'}`,
'',
`受付日時:${inquiry.receivedAt || ''}`,
`送信者:${inquiry.from || ''}`,
`分類:${inquiry.category || ''}`,
`優先度:${inquiry.priority || ''}`,
`担当候補:${inquiry.assignee || ''}`,
'',
'## 概要',
inquiry.summary || '',
'',
'## 分類理由',
inquiry.reason || '',
'',
'## 本文',
inquiry.body || ''
].join('\n');
}
function saveInquiryMemoToDrive(inquiry) {
const folders = setupDriveFolders();
const content = buildInquiryMemoContent(inquiry);
const fileName = generateFileNameByAi({
date: new Date().toISOString().slice(0, 10),
category: inquiry.category || '問い合わせ',
subject: inquiry.subject || '問い合わせメモ',
summary: inquiry.summary || ''
});
const file = createGoogleDocFile(folders.inquiryMaterials.id, fileName, content);
Logger.log(JSON.stringify(file, null, 2));
return file;
}
function getInquiryHeaders() {
return [
'受付日時',
'送信者',
'件名',
'問い合わせ概要',
'分類',
'優先度',
'担当候補',
'ステータス',
'分類理由',
'対応メモ',
'スレッドID',
'メッセージID',
'登録日時',
'Slack通知状況',
'Slack通知日時',
'Drive保存状況',
'Drive URL',
'Drive保存日時'
];
}
function ensureDriveHeaders() {
const config = getConfig();
const sheetName = getInquirySheetName();
const range = `${sheetName}!A1:R1`;
const response = Sheets.Spreadsheets.Values.get(config.sheetId, range);
const existingHeaders = response.values && response.values.length > 0 ? response.values[0] : [];
const requiredHeaders = getInquiryHeaders();
const updatedHeaders = existingHeaders.slice();
requiredHeaders.forEach(header => {
if (!updatedHeaders.includes(header)) {
updatedHeaders.push(header);
}
});
const updateRange = `${sheetName}!A1:${columnNumberToLetter(updatedHeaders.length)}1`;
Sheets.Spreadsheets.Values.update(
{ values: [updatedHeaders] },
config.sheetId,
updateRange,
{ valueInputOption: 'RAW' }
);
}
function convertRowToInquiry(row, headers, rowNumber) {
const map = getHeaderIndexMap(headers);
return {
rowNumber: rowNumber,
receivedAt: row[map['受付日時']] || '',
from: row[map['送信者']] || '',
subject: row[map['件名']] || '',
summary: row[map['問い合わせ概要']] || '',
category: row[map['分類']] || '',
priority: row[map['優先度']] || '',
assignee: row[map['担当候補']] || '',
status: row[map['ステータス']] || '',
reason: row[map['分類理由']] || '',
memo: row[map['対応メモ']] || '',
threadId: row[map['スレッドID']] || '',
messageId: row[map['メッセージID']] || '',
registeredAt: row[map['登録日時']] || '',
slackStatus: row[map['Slack通知状況']] || '',
slackNotifiedAt: row[map['Slack通知日時']] || '',
driveStatus: row[map['Drive保存状況']] || '',
driveUrl: row[map['Drive URL']] || '',
driveSavedAt: row[map['Drive保存日時']] || ''
};
}
function filterDriveSaveTargets(inquiries) {
return inquiries.filter(inquiry => {
return !inquiry.driveStatus && inquiry.status !== '完了';
});
}
function updateDriveSaveStatus(rowNumber, fileUrl) {
const config = getConfig();
const sheetName = getInquirySheetName();
const range = `${sheetName}!P${rowNumber}:R${rowNumber}`;
const valueRange = {
values: [
['保存済み', fileUrl, new Date().toISOString()]
]
};
Sheets.Spreadsheets.Values.update(valueRange, config.sheetId, range, {
valueInputOption: 'USER_ENTERED'
});
}
function saveInquiryToDriveAndUpdateSheet(inquiry) {
try {
const file = saveInquiryMemoToDrive(inquiry);
updateDriveSaveStatus(inquiry.rowNumber, file.webViewLink || '');
Logger.log(`Drive保存完了: row ${inquiry.rowNumber}`);
} catch (error) {
logError('saveInquiryToDriveAndUpdateSheet', error);
}
}
function saveDriveTargets() {
ensureDriveHeaders();
const inquiries = getInquiriesFromSheet();
const targets = filterDriveSaveTargets(inquiries);
if (!targets || targets.length === 0) {
Logger.log('Drive保存対象はありません');
return;
}
targets.forEach(inquiry => {
saveInquiryToDriveAndUpdateSheet(inquiry);
});
Logger.log('Drive保存件数: ' + targets.length);
}
function runInquiryManagementSlackDriveFlow() {
try {
setupInquiryManagementSheet();
const results = classifyInquiryEmails();
saveNewClassifiedResultsToSheet(results);
notifySlackTargets();
saveDriveTargets();
Logger.log('問い合わせ管理、Slack通知、Drive保存が完了しました');
} catch (error) {
logError('runInquiryManagementSlackDriveFlow', error);
}
}
function testSaveMinutesToDrive() {
const meetingText = `
問い合わせ管理の自動化について打ち合わせを行った。
Gmail APIで問い合わせメールを取得し、AIで分類する。
分類結果はGoogle Sheetsへ登録する。
高優先度の問い合わせはSlackへ通知する。
次回はDriveへの資料保存を確認する。
`;
const file = saveMinutesToDrive(meetingText, {
date: '2026-05-13',
category: '議事録',
subject: '問い合わせ管理自動化打ち合わせ',
summary: '問い合わせ管理自動化の処理方針を確認した会議'
});
Logger.log('保存先URL: ' + file.webViewLink);
}このコードを第2章から第4章までのコードに追加すると、問い合わせ内容や議事録をGoogle Drive APIで保存できます。
■運用時に注意すること
Google Drive APIで資料整理を自動化する場合は、下記に注意します。
・保存先フォルダを先に決める
・フォルダ名やファイル名のルールを決める
・同じ資料を重複保存しないようにする
・Drive URLを管理表へ残す
・個人情報や機密情報を含む資料の共有範囲に注意する
・自動保存する対象を絞る
・AIが作った議事録やメモは必要に応じて確認する
・削除や上書きは最初から自動化しないDrive自動化は便利ですが、保存先や共有権限を間違えると情報管理が崩れます。
最初は、作成と保存に絞って使う方が安全です。
削除や上書きは、運用が固まってから検討する方が安心です。
■まとめ
Google Drive APIを使うと、AIで作成した議事録や問い合わせ関連資料を自動で保存できます。
AIは、会議メモから議事録を作成したり、問い合わせ内容を整理したり、保存用ファイル名を作成したりできます。
Google Drive APIは、フォルダ作成、ファイル作成、保存先URL取得を行えます。
Google Sheets APIと組み合わせれば、管理表にDrive URLを反映できます。
これにより、問い合わせ対応や会議メモをその場限りで終わらせず、後から探しやすい資料として残せます。
この章では、Google Drive APIを使って議事録や資料整理を自動化する方法を作りました。
次章では、Jira APIを使って過去チケットを検索し、問い合わせ対応に活用する方法へ進みます。
■次回
第6章(応用編):AIとJira APIで過去チケット検索を自動化する方法
