はじめに
この記事は、Google Apps Script(以下、GASと記載します。)によりGmailの添付ファイルを自動保存するシステムの構築方法を紹介しています。GmailとGASとスプレッドシート、そしてGoogle Driveを連携させて実現します。
対象読者
以下に該当する方を想定しています。
・使用言語はGAS
・Gmailの使用頻度が高く、添付ファイルを定期的に保存する業務がある人
・IFTTTでGmailを自動化していたが、サービス停止によって困っている人。もしくは停止してから打つ手がわからない人
・GASで似たスクリプトを制作したことはあるが、メールや添付ファイルを重複して保存してしまい困っている人
プログラムの全体像

実装方針
システムの概要
受信済みのメールの添付ファイルをGoogleドライブにフォルダ分けした上で自動保存するシステムを構築する。
前提条件
・Gmailのアカウントを使用する(無料・有料は問いません)
・1つのアカウントに複数のメールアドレスを受信設定している
・新規のメールアドレスが追加されることもある
・添付データは1メールに対して1添付ファイルである。
目指す成果物(MUST)
・Gmailの受信済みメールの添付ファイルを、受信メールアドレスの名称のGoogleドライブのフォルダに自動的に保存する。
・トリガー要件は、
定期実行タイプ(時間)*定期的に時間単位で実行される時間タイプ
とする。
・保存するフォルダ名は受信メールアドレス名とし、新規のメールアドレスの場合は新たにフォルダを作成した上でそのフォルダに保存する。
・重複した添付ファイルは保存せず、実行の結果がlogとして確認できる。
プログラムの仕様と構成
2つのプロセスでシステムを構成
・添付ファイルのあるメッセージIDを取得し、保存するプロセス
・メッセージIDから添付データを保存するプロセス
仕様の前提
・プログラミング言語は、GASを使用する。
・メッセージIDを軸にデータを管理する。
・スプレッドシートにlogを残し、いつ、何のデータを保存したかを記録する。
・スクリプトの同時実行(並列処理)は不可とする。
メッセージIDを取得するプロセス
1.スプレッドシートに保存したメッセージの最も新しい日付以降で かつ添付ファイルの付いているGmailのスレッドを抽出する。抽出するスレッド件数はカウントで指定する。
2.抽出したスレッドをメッセージに分割する。
3.分割したメッセージと過去に保存済みのメッセージかを Message IDを軸に照合し、一致した場合は保存せず、一致しない場合は保存する。
4.保存内容は、メッセージID, メッセージの受信日時、メッセージの件名、logに保存を実行した日時、ステータス(未処理)とする。
5.なお、メッセージIDから添付データを保存するプロセスでは、メッセージIDとステータスを利用する
6.保存後は、データがメッセージの受信日時の降順(最新が一番上)になるように並べ換える
メッセージIDから添付データを保存するプロセス
1.logのシートから、ステータスが”未処理”のデータが保存されている行数を取得する。
2.行数に一致するメッセージIDからそのメッセージの添付ファイルと受信メールアドレスを取得し、受信メールアドレスと一致するドライブフォルダに添付ファイルを保存する。
3.受信メールアドレスと一致するドライブフォルダが存在しない場合、新たに受信メールアドレスのフォルダ名のフォルダを作成し、そのフォルダに添付ファイルを保存する。
4.保存完了後、logに保存先フォルダ名、フォルダID、保存日時、ステータス(保存済み)に変更する。
仕様上の制約
スプレッドシートでのlogの管理は、シート名やカラム名で管理し、行と列で範囲とデータを取得することから、下記はエラーの原因となるため避ける。
・スクリプト実行中に、logを管理するスプレッドシートの列数や行数に変更を加えること
・スプレッドシートのシート名やカラム(上2行)の名称を変更すること
メインとなるコード 〜メッセージから添付ファイルを保存するまで〜
添付データのあるMessageIdを取得する
// 1.添付データのあるMessageのIdを取得する
function getAttachmentMessages() {
//実行中は他のユーザーや定期実行含む実行をロックする
var lock = LockService.getScriptLock();
//lockを試し、ロックできたらtrue できなかったらfalseを返す 同時実行を防ぐ
if (lock.tryLock(500)) {
//管理するスプレッドシートのシートを定義する ***にはスプレッドシートのIDを挿入してください
var ssId = "*******************";
var ss = SpreadsheetApp.openById(ssId);
var log_sheet = ss.getSheetByName("log");
//取得するスレッド数を定義する
var count = 150;
var last_row = log_sheet.getLastRow();
var last_column = log_sheet.getLastColumn();
var log_header_column_data = log_sheet.getRange(1, 1, 2, last_column).getValues();
var log_start_message_column = log_header_column_data[0].indexOf("start_message_column") + 1;
var log_msg_date_column = log_header_column_data[1].indexOf("msg_date") + 1;
//検索条件の指定 初期値があるかないかで条件を分岐
if (log_sheet.getRange(3, 1).getValue() == "" ) { //logがない場合
var query = "has:attachment before:2016/12/31"; //添付データのあるスレッドを取得する
var threads_count = GmailApp.search(query).length; //検索要件に合致するスレッドを取得
if (threads_count <= count) {
var start_count = 0;
count = threads_count;
} else {
var start_count = threads_count - count;
};
var threads = GmailApp.search(query, start_count, count);
var latest_date = new Date();
} else {
//var end_date = log_sheet.getRange(last_row, 1).getValue();
//var end_date = Utilities.formatDate(log_sheet.getRange(last_row, 1).getValue(),"JST","yyyy/MM/dd");
var get_row = last_row - 2;
var date_list = log_sheet.getRange(3, 2, get_row, 1).getValues();
//logの中で最新のmessageの受信日時を取得する
var latest_date = date_list[0][0];
for (var t = 0; t < date_list.length; t++) {
if (latest_date <= date_list[t][0]) {
var latest_date = date_list[t][0];
}
}
var after_date = Utilities.formatDate(latest_date,"JST","yyyy/MM/dd");
var before_date = Utilities.formatDate(
new Date(latest_date.getYear(),
latest_date.getMonth(),
latest_date.getDay() + 180
),
"JST","yyyy/MM/dd");
var query = "has:attachment after:" + after_date + " before:" + before_date; //添付データのある かつ 指定日以降のスレッドを取得する
var threads_count = GmailApp.search(query).length;
if (threads_count < count) {
var start_count = 0;
var count = threads_count;
} else {
var start_count = threads_count - count;
var count = threads_count;
};
var threads = GmailApp.search(query, start_count, count); //検索要件に合致するスレッドを取得
}
//検索条件に合致したスレッドを取得し、添付のあるメッセージにする
var attachment_messages = [];
var messages = GmailApp.getMessagesForThreads(threads); //スレッドをメッセージの二次元配列に変換
if (log_sheet.getRange(3, 1, 3, 1).getValue() == '') {
var test_messages_id = [''];
} else {
var test_messages_id_2 = log_sheet.getRange(3, 1, get_row, 1).getValues();
var test_messages_id = Array.prototype.concat.apply([],test_messages_id_2);
}
for(var i = 0; i <messages.length; i++) {
for(var j = 0; j < messages[i].length; j++) {
if(messages[i][j].getAttachments().length === 0 ) {
} else if (Utilities.formatDate(messages[i][j].getDate(), "JST","yyyy/MM/dd") == Utilities.formatDate(latest_date, "JST","yyyy/MM/dd")) {
if(test_messages_id.indexOf(messages[i][j].getId()) == -1) {
attachment_messages.push([
messages[i][j].getId(),
messages[i][j].getDate(),
messages[i][j].getSubject(),
messages[i][j].getAttachments().length,
new Date(),
"未処理"
]);
}
} else {
attachment_messages.push([
messages[i][j].getId(),
messages[i][j].getDate(),
messages[i][j].getSubject(),
messages[i][j].getAttachments().length,
new Date(),
"未処理"
]);
};
}
}
//もし保存するデータがなければ終了する
if (attachment_messages.length == 0) {
postDrive();
return;
}
//二次元配列で取得したメッセージ情報をスプレッドシートに保存する
var post_row = attachment_messages.length
var post_column = attachment_messages[0].length;
var start_row = last_row + 1;
var post_range = log_sheet.getRange(start_row, 1, post_row, post_column);
post_range.setValues(attachment_messages);
//日付の降順で全てのデータを並び替える
var filter_last_row = log_sheet.getLastRow();
var filter_range = log_sheet.getRange(3, 1, filter_last_row - 2, last_column);
filter_range.sort({
column: log_msg_date_column,
ascending: false
});
postDrive();
lock.releaseLock();
}
}
MessageのattachmentをDriveに保存する
// 2.MessageをDriveに保存する
function postDrive() {
//実行中は他のユーザーや定期実行含む実行をロックする
var lock = LockService.getScriptLock();
//lockを試し、ロックできたらtrue できなかったらfalse
if (lock.tryLock(500)) {
//未処理データの取得 ***にはスプレッドシートのIDを入力してください。
var ssId = "****";
var ss = SpreadsheetApp.openById(ssId);
var log_sheet = ss.getSheetByName("log");
var admin_sheet = ss.getSheetByName("admin");
var last_row = log_sheet.getLastRow();
var last_column = log_sheet.getLastColumn();
var header_column_data = log_sheet.getRange(1, 1, 2, last_column).getValues();
var log_msg_id_column = header_column_data[1].indexOf("msg_id") + 1;
var log_msg_date_column = header_column_data[1].indexOf("msg_date") + 1;
var log_msg_subject_column = header_column_data[1].indexOf("msg_subject") + 1;
var log_attachments_count_column = header_column_data[1].indexOf("attachments_count") + 1;
var log_post_date_msg_column = header_column_data[1].indexOf("post_date_msg") + 1;
var log_status_column = header_column_data[1].indexOf("status") + 1;
var log_post_date_folder_column = header_column_data[1].indexOf("post_date_folder") + 1;
var log_folder_id_column = header_column_data[1].indexOf("folder_id") + 1;
var log_folder_name_column = header_column_data[1].indexOf("folder_name") + 1;
var log_end_drive_column = header_column_data[0].indexOf("end_drive_column") + 1;
var get_column = 9;
var check_range_2 = log_sheet.getRange(1, log_status_column, last_row, 1).getValues();
var check_range = Array.prototype.concat.apply([],check_range_2);
var max_count = check_range.filter(/未処理/).length;
//未処理がなければ終了する
if (max_count == 0 || max_count == "") {
return
}
var match_range = [];
var t = 2; //indexOfの検索開始位置
for (var i = 0; i < max_count; i++) {
match_range.push(
check_range.indexOf("未処理", t) + 1
);
var t = check_range.indexOf("未処理", t) + 1;
}
//本番とテストフォルダーの切り替え *** にはGoogleドライブのフォルダIDを挿入してください
//var parent_folder_id = "*********"; //自動保存テスト用ß
//var parent_folder = DriveApp.getFolderById(parent_folder_id);
var parent_folder = DriveApp.getRootFolder(); //マイドライブ
var childlen_folder = [];
var folders = parent_folder.getFolders();
while (folders.hasNext()) {
childlen_folder.push(
folders.next().getName()
)
}
//未処理のメッセージを処理する
var max_count = 100; //処理の上限値を上書きする
if ( max_count > match_range.length) { //未処理データが上限値を超えているか判定する
var max_count = match_range.length
}
for (var i = 0; i < max_count; i++) {
var post_id = match_range.length - (i + 1);
var post_row = match_range[post_id];
var post_range = log_sheet.getRange(post_row, 1, 1, last_column);
var msg_id = post_range.getValues()[0][0]
var get_msg = GmailApp.getMessageById(msg_id);
var attachments = get_msg.getAttachments();
var to_reg = new RegExp(get_msg.getTo(), "g");
if (to_reg.test(childlen_folder)) {
var child_folder = parent_folder.getFoldersByName(get_msg.getTo()).next();
var set_values = [[
"保存済み",
new Date(),
child_folder.getId(),
child_folder.getName()
]];
var set_range = log_sheet.getRange(post_row, log_status_column, 1, log_end_drive_column - log_status_column + 1)
set_range.setValues(set_values);
for (var a = 0; a < attachments.length; a++) {
child_folder.createFile(attachments[a]);
}
} else {
var create_child_folder = parent_folder.createFolder(get_msg.getTo());
childlen_folder.push(
create_child_folder.getName()
)
var set_values = [[
"保存済み",
new Date(),
create_child_folder.getId(),
create_child_folder.getName()
]];
var set_range = log_sheet.getRange(post_row, log_status_column, 1, log_end_drive_column - log_status_column + 1)
set_range.setValues(set_values);
for (var f = 0; f < attachments.length; f++) {
create_child_folder.createFile(attachments[f])
}
}
}
}
}
参考:スプレッドシートの管理画面のイメージ

カスタマイズの方向性
例えば、以下のカスタマイズが可能です。
・取得するメールの種類をラベルや件名、メールアドレスで選別する。
・複数の添付ファイルの保存する。
・実行の都度、LINEやchatworkなどに成功の有無や取得件数などを通知する。
・定期的な実行ではなく、手動で指定したタイミングで実行する。 etc
以上

