画面右上の「🔍」マークをクリックするとヘルプページ内のキーワード検索が可能です
Chatwork連携とGAS作成・実行を行うことで、【MTG】が含まれるGoogleミートについて「指定の共有ドライブへの動画データの保存・Chatworkへの通知」を日に3度、定期的に自動実行できるようになります。
[この自動化の仕様について詳細を知りたい方はここを選択]
「【MTG】の自動化」で自動保管・自動通知の対象は、Googleミートの名称に【MTG】が含まれ、かつ録画をしたケースに限ります。
→ Googleミートの録画・文字起こしの方法について知りたい方はこちら
GAS実行の初回のケースを除き、「【MTG】の自動化」は日に3回実行されます(9:00/12:00/15:00)
録画の開始タイミングは事前の設定でも、ミーティング中でも自動化の対象となります。ただし、Googleミート名称は事前設定のみが自動化の対象です。終了後にミートの名称を【MTG】を追加しても、自動化の対象にならないため、ご注意ください。
GoogleメールへのGoogleミートの自動通知の件名をトリガーにプログラムを設計しています。最新のメール履歴から20件分に【MTG】が含まれていない、それ以前の動画は自動的に保管・通知されません。
GASサンプルをご案内する範囲以上に加工・修正しての実行は、自己の責任において行ってください。
・・・・・・・・・・・・・・・・・・・・・・・
※ GAS(ガス)は「Googole Apps Script」(グーグル アップス スクリプト)の略で、Googleが提供するプログラミング言語・開発環境(プラットフォーム)のことです。「Google Apps Script (GAS)」および関連するGoogleの名称は、Google LLCの商標または登録商標です。
※ Chatwork(チャットワーク)は、同社が提供するビジネスチャットツールの名称・サービスのことで、株式会社kubell(キューベル:旧Chatwork株式会社)の登録商標です。
※API(エーピーアイ)は「Application Programming Interface」の略で、異なるソフトウェア同士が機能やデータをやり取りするための「接点」や「窓口」となる仕組みで、一般的な技術用語・概念です。
Googleミートの録画データの保管先を、共有ドライブ内に作成します。保管先の共有ドライブ名は何でもOKです。
※ Googleの共有ドライブからアクセスできる場所であることを確認します(下の画像ではわかりやすいように表示させていますが、共有ドライブ直下、一覧に保管先の共有ドライブ名がある必要はありません)。
保管先として共有ドライブ「MTGデータ保管」を新規作成完了した前提で、手順を進めます。
1)Googleワークスペースのマイドライブを開き、白い部分で右クリックし、「その他」から「Google Apps Script」を選択する。
2)新規のGASファイルが開かれるときに警告が表示されるので「スクリプトを作成」を選択する。
2)ファイル名を変更する。「無題のプロジェクト」を選択して表示されたプロジェクトタイトルに「【MTG】MTGデータ保管/CW通知 自動化 GASコード」を入力し、「名前を変更」を選択する。
3)GASサンプルプログラムをコピーする。最初から入っていた「function」からはじまるプログラムを全て削除し、ペーストして貼り付ける。
[GASサンプルプログラムは ここを選択]※ 長いので折りたたんでいます
/**
* Google Meet録画+GeminiメモをDriveへ保存し、保存済みファイルをChatworkへ通知。
*/
function saveMeetAttachmentsIncremental() {
const CHATWORK_API_TOKEN = '*****'; // 修正① ChatworkのAPIトークンを入れてください
const CHATWORK_ROOM_ID = '*****'; // 修正② Chatworkの通知先グループのIDを入れてください
const DRIVE_FOLDER_ID = '*****'; // 修正③ データ保管先のGoogle DriveのIDを入れてください
const THREAD_FETCH_LIMIT = 20; // 一度に取得するメールスレッドの最大件数
const props = PropertiesService.getScriptProperties();
const now = new Date();
let success = false;
try {
const activeUser = Session.getActiveUser().getEmail();
Logger.log(`Gmail受信トレイの確認を開始します。: ${activeUser}`);
const query = buildQuery(props.getProperty('lastCheckTime'), now);
Logger.log(`検索クエリ: ${query}`);
const folder = DriveApp.getFolderById(DRIVE_FOLDER_ID);
const threads = GmailApp.search(query, 0, THREAD_FETCH_LIMIT);
Logger.log(`対象スレッド数: ${threads.length}`);
threads.forEach(thread => {
thread.getMessages().forEach(message => {
Logger.log(`メール処理を開始します。: ${message.getSubject()} (ID: ${message.getId()})`);
const savedFileNames = saveDriveFilesFromMessage(message, folder);
if (savedFileNames.length > 0) {
const folderUrl = `https://drive.google.com/drive/u/0/folders/${DRIVE_FOLDER_ID}`;
notifyChatwork(CHATWORK_API_TOKEN, CHATWORK_ROOM_ID, savedFileNames, folderUrl);
}
});
});
success = true;
} catch (e) {
Logger.log(`処理中にエラー発生: ${e && e.stack || e}`);
} finally {
if (success) {
props.setProperty('lastCheckTime', now.toISOString());
Logger.log(`最終チェック時刻を保存: ${now.toISOString()}`);
} else {
Logger.log('処理中にエラーが発生したため、最終チェック時刻は保存されませんでした');
}
createDailyFixedTriggers();
Logger.log('翌日分のトリガーを再作成しました。');
}
}
/**
* スクリプトプロパティに保存している最終チェック時刻を削除し、次回実行を初回扱いに戻す。
* テスト時に使用します。
*/
function resetLastCheckTime() {
PropertiesService.getScriptProperties().deleteProperty('lastCheckTime');
Logger.log('lastCheckTime をリセットしました。次回実行時は初回扱いになります。');
}
/**
* 指定した時刻(9:00 / 12:00 / 15:00)のトリガーを作成し、毎日実行されるように設定。
* トリガー作成成功後、既存のトリガーがある場合は、既存を削除
*/
function createDailyFixedTriggers() {
const functionName = 'saveMeetAttachmentsIncremental';
const now = new Date();
const times = [
{ hour: 9, minute: 0 },
{ hour: 12, minute: 0 },
{ hour: 15, minute: 0 }
];
const created = [];
try {
// ① 新トリガーを全て作成
times.forEach(({ hour, minute }) => {
const triggerTime = nextTriggerTime(now, hour, minute);
const trigger = ScriptApp.newTrigger(functionName)
.timeBased()
.at(triggerTime)
.create();
created.push(trigger);
Logger.log(`新トリガー作成成功: JST ${hour}:${minute.toString().padStart(2, '0')}`);
});
// ② すべて成功したら既存トリガー削除
ScriptApp.getProjectTriggers()
.filter(trigger => trigger.getHandlerFunction() === functionName)
.forEach(trigger => {
const isNew = created.some(newTrigger => newTrigger.getUniqueId() === trigger.getUniqueId());
if (isNew) return;
try {
ScriptApp.deleteTrigger(trigger);
Logger.log(`既存トリガー削除成功: ${trigger.getUniqueId()}`);
} catch (e) {
Logger.log(`既存トリガー削除失敗: (${trigger.getUniqueId()}): ${e.message}`);
}
});
} catch (e) {
Logger.log(`トリガー作成中にエラー: ${e}`);
// ③ 作成途中でエラー発生 → 新規作成分を削除(差し戻し)
created.forEach(trigger => {
try {
ScriptApp.deleteTrigger(trigger);
Logger.log(`差し戻し: 作成済みトリガーを削除 (${trigger.getUniqueId()})`);
} catch (e) {
Logger.log(`差し戻し失敗 (${trigger.getUniqueId()}): ${e.message}`);
}
});
throw e;
}
}
/**
* Chatwork APIを呼び出してメッセージを送信する。
*
* @param {string} apiToken ChatworkAPIトークン
* @param {string|number} roomId 通知先ルームID
* @param {string} message 通知内容
*/
function sendChatworkNotification(apiToken, roomId, message) {
const chatworkUrl = `https://api.chatwork.com/v2/rooms/${roomId}/messages`;
const options = {
method: 'post',
headers: { 'X-ChatWorkToken': apiToken },
payload: { body: message },
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(chatworkUrl, options);
const status = response.getResponseCode();
if (status === 200 || status === 201) {
Logger.log('Chatwork通知に成功しました。');
} else {
Logger.log(`Chatwork通知に失敗: ステータスコード ${status}`);
}
} catch (error) {
Logger.log(`Chatwork通知中にエラー: ${error}`);
}
}
/**
* 指定したGmailメッセージ内のDrive/Docsリンクからファイルを保存し、保存したファイル名一覧を返す。
*
* @param {GoogleAppsScript.Gmail.GmailMessage} message 処理対象のメールメッセージ
* @param {GoogleAppsScript.Drive.Folder} destinationFolder 保存先フォルダ
* @returns {string[]} 保存に成功したファイル名の配列。保存対象がなければ空配列を返す
*/
function saveDriveFilesFromMessage(message, destinationFolder) {
const fileIds = extractDriveFileIds(message.getBody());
if (fileIds.size === 0) {
Logger.log('Drive/Docsリンクが見つかりません。');
return [];
}
const savedFileNames = [];
fileIds.forEach(fileId => {
try {
const file = DriveApp.getFileById(fileId);
const name = file.getName();
const existing = destinationFolder.getFilesByName(name);
if (existing.hasNext()) {
Logger.log(`同名ファイルが存在するためスキップ: ${name}`);
return;
}
file.makeCopy(name, destinationFolder);
savedFileNames.push(name);
Logger.log(`ファイルを保存(コピー)しました: ${name}`);
} catch (e) {
Logger.log(`ファイルID処理エラー (${fileId}): ${e.message}`);
}
});
return savedFileNames;
}
/**
* 保存ファイルがある場合に送信する、Chatworkメッセージを組み立てて通知。
*
* @param {string} token ChatworkAPIトークン
* @param {string|number} roomId 通知先ルームID
* @param {string[]} savedFileNames 保存済みファイル名の一覧
* @param {string} folderUrl 保存先フォルダのURL
*/
function notifyChatwork(token, roomId, savedFileNames, folderUrl) {
const message = [
'[info][title]顧客MTGデータ 保存完了[/title]顧客MTGの文字起こし、録画データを保存しました。',
'',
'保存データ:',
savedFileNames.join('\n'),
'',
'保存先フォルダ:',
folderUrl,
'[/info]'
].join('\n');
Logger.log(`Chatwork通知を送信します: Room ${roomId}`);
sendChatworkNotification(token, roomId, message);
}
/**
* Gmail検索クエリを生成する。前回実行時刻が未保存であれば1日前以降を対象にする。
*
* @param {string|null} lastCheckTime 最終チェック日時。無ければ`null`
* @param {Date} now クエリ生成時の現在の日時
* @returns {string} Gmail検索APIに渡すクエリ文字列
*/
function buildQuery(lastCheckTime, now) {
const base = 'subject:(【MTG】) from:(google.com) to:(me)';
if (!lastCheckTime) {
const oneDayAgo = Math.floor((now.getTime() - 24 * 60 * 60 * 1000) / 1000);
return `${base} after:${oneDayAgo}`;
}
// overlapSec分引いて、処理中に受信したメールが次回実行時に対象となるようにする
const lastTimestamp = Math.floor(new Date(lastCheckTime).getTime() / 1000);
return `${base} after:${lastTimestamp}`;
}
/**
* メール本文からGoogle Drive/DocsリンクのファイルIDを抽出。
*
* @param {string} body Gmailメッセージ本文
* @returns {Set<string>} 抽出したファイルIDを格納したSet。メール本文内の重複は自動的に除外される。
*/
function extractDriveFileIds(body) {
const driveRegex = /https:\/\/(?:drive\.google\.com\/file|docs\.google\.com\/document)\/d\/([a-zA-Z0-9_-]+)/g;
const ids = new Set();
let match;
while ((match = driveRegex.exec(body)) !== null) {
ids.add(match[1]);
}
return ids;
}
/**
* 指定の時刻が既に過ぎている場合は翌日に繰り上げたDateオブジェクトを返す。
*
* @param {Date} now 現在日時
* @param {number} hour 実行予定の時(24時間表記)
* @param {number} minute 実行予定の分
* @returns {Date} 次回の実行日時
*/
function nextTriggerTime(now, hour, minute) {
const triggerTime = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
hour,
minute
);
if (triggerTime < now) {
triggerTime.setDate(triggerTime.getDate() + 1);
}
return triggerTime;
}
3)GASファイル(未完成)を保存する。ファイル名の下にあるディスクのアイコン(ドライブにプロジェクトを保存)を選択する。
※ Chatworkの準備を終えてから、GASファイルを修正して完成させます。この時点で実行しないでください。
※ GASファイルの表示されたブラウザ、およびブラウザタブは閉じないでください。
通知用のChatworkアカウントを用意します。
通知用のChatworkアカウントは、有料・無料を問いません。
既に利用している方のアカウントを流用する形で、通知用として併用する形でも構いません(通知時の投稿者として該当アカウントが使用されるだけで、これまでどおりにChatworkをご利用いただけます)。
通知専用のアカウントを用意する場合は、公式の手順に従って、新規作成します。
(参照:Chatwork(チャットワーク)アカウントの新規登録の方法を解説 - Chatwork 公式サイト)
無料アカウントの「テスト花子」を通知用アカウントとして新規作成完了した前提で、手順を進めます。
通知先となるChatworkグループを用意します。
既に利用しているChatworkグループを通知先として指定できます(自動通知がされるようになるだけで、これまでどおりにChatworkグループをご利用いただけます)。
通知専用のChatworkグループを用意する場合は、公式の手順に従って、新規作成します。
(参照:グループチャットを作成する - Chatwork 公式サイト)
通知先のChatworkグループ「MTGデータ保管通知テスト」を新規作成完了した前提で、手順を進めます。
通知専用のアカウントを、通知先のChatworkグループに追加する場合は、公式の手順に従って、メンバーとして追加します。
(参照:グループチャットにメンバーを追加する - Chatwork 公式サイト)
通知用アカウント「テスト花子」を、通知先「MTGデータ保管通知テスト」に追加した前提で、手順を進めます。
1)通知用のChatworkアカウントでログインし、画面右上の「アカウント」から「サービス連携」を選択する。
2)左のメニューから「APIトークン」を選択する。
3)API利用申請画面で「API利用申請」を選択する。
「API利用申請」が完了すると「申請しました」というメッセージが表示されます。
※ この後も通知用のChatworkアカウントで行う操作があるため、この時点でログアウトはしないでください
1)Chatworkの管理者アカウントでログインし、右側のアカウントから「管理者設定」を選択する。
2)左メニューから「API利用申請の確認」を選択する。
3)「API利用申請の確認」画面で、申請者(通知用のChatworkアカウント。画面上は「テスト花子」)を確認して「承認」を選択する。
「API利用申請の確認」が完了すると「【申請者のアカウント名】からの申請を承認しました」というメッセージが表示されます。
1)通知用のChatworkアカウント(画面上は「テスト花子」)でログインした状態で、右側のアカウントから「サービス連携」を選択する。
2)左メニューの「APIトークン」を選択する。
3)APIトークンが表示されたら「コピー」を選択する。
※ このコピーした「APIトークン」は、この直後のGASファイルの作成時に使用します。
1)Tips1で作成したGASファイル「【MTG】MTGデータ保管/CW通知 自動化 GASコード」を表示する。
2)「修正① ChatworkのAPIトークンを入れてください」と記載されている「*****」に、ひとつ前の工程で取得したChatworkのAPIトークンを入力する。
3)「修正② Chatworkの通知先グループのIDを入れてください」と記載されている「*****」にChatworkの通知先グループのIDを入力する。
※ Chatworkの通知先グループをブラウザで開いたときのURL末尾「https://www.chatwork.com/#!rid」に続く数字が、 ChatworkのグループのIDです
4)「修正③ データ保管先のGoogle DriveのIDを入れてください」と記載されている「*****」にデータ保管先のGoogleドライブのIDを入力する。
※ データ保管先のGoogleドライブをブラウザで開いたときのURL末尾「https://drive.google.com/drive/folders/」に続く英数字が、GoogleドライブのIDです
5)「ドライブにプロジェクトを保存」を選択し、GASファイルを保存する。
1)▼ボタンから「saveMeetAttachmentsIncremental」を選択する。
2)「選択した関数を実行」を選択する。
3)「承認が必要です」が表示されたら「権限を確認」を選択し、自分のアカウントを選択する。
4)「【設定したファイル名】 にログイン」が表示されたら、「次へ」を選択する。
5)「【設定したファイル名】 が Googleアカウントへのアクセスを求めています」が表示されたら、「すべて選択」を選択した後で、スクロールして表示されるページ下部の「続行」を選択する。
4)GASファイルが実行され、下の画面に実行結果のログが表示される。
※ 初回実行のみ、前日分のメールを検索し、MTGデータがないか確認します。
※ メールに録画データとドキュメントの添付がない場合は、「Drive/Docsリンクが見つかりません。」というログがでます。
5)GASの左側にある「トリガー」(時計のアイコン)を選択し、トリガーが作成されていることを目視で確認する。
各トリガーは右端の鉛筆アイコンを選択し、内容を確認できます。
※ 今後は、1日3回(9:00/12:00/15:00)自動実行するため、トリガーは3つ作成されます。
※ 実行後、該当の顧客MTGデータがあった場合は、共有フォルダにデータが保存され、CW通知されます。
内容の確認後、閉じるときは誤って変更しないように「キャンセル」を選択してください。