前の記事で、Gmailの添付ファイルをGoogleドライブに自動保存するスクリプトを動かしました。
→ Gmailの添付ファイルをGoogleドライブに自動保存する【GAS初心者向け・コピペOK】
あのコードは「まず動かすこと」だけを考えて書いたものです。実際に動いたので、そのまま自分の仕事で使うつもりで読み返しました。
そこで気づいたことがあります。「保存済み」というラベル名が、コードの中の2か所に書かれていました。検索条件の中と、ラベルを作る行と、両方です。ラベル名を変えたくなったとき、片方だけ直して動かなくなる形です。
そのコードを、次の3点で整えます。
- 設定を先頭の1か所にまとめる
- 処理を役割ごとに分ける
- 1件の失敗で全体が止まらないようにする
動作は前と変わりません。同じことを、後から直しやすい形で書くという話です。
前の記事のコードが動いている前提で進めます。まだの方は、先にそちらを試してください。
整えたあとのコード
先に全体を載せます。前のコードを全部消して、これに貼り替えてください。
/**
* 設定はすべてここにまとめる
*/
const CONFIG = {
FOLDER_ID: 'ここにGoogleドライブのフォルダIDを貼る',
SEARCH_QUERY: 'subject:GASテスト用 has:attachment -label:保存済み',
PROCESSED_LABEL: '保存済み',
};
/**
* メイン処理。全体の流れはこの関数を読めば分かる。
*/
function saveGmailAttachments() {
try {
const threads = getTargetThreads_();
const folder = DriveApp.getFolderById(CONFIG.FOLDER_ID);
const label = getOrCreateLabel_(CONFIG.PROCESSED_LABEL);
let savedCount = 0;
for (const thread of threads) {
savedCount += saveAttachmentsFromThread_(thread, folder);
thread.addLabel(label);
}
Logger.log(`${threads.length}件のスレッドから${savedCount}件のファイルを保存しました`);
} catch (error) {
Logger.log('エラーが発生しました: ' + error.stack);
}
}
/**
* 検索条件に一致するスレッドを取得する。
* @return {GmailThread[]}
*/
function getTargetThreads_() {
return GmailApp.search(CONFIG.SEARCH_QUERY);
}
/**
* 1スレッド分の添付ファイルを、フォルダへ保存する。
* 1件の保存に失敗しても、残りの処理は止めない。
* @param {GmailThread} thread 対象スレッド
* @param {Folder} folder 保存先フォルダ
* @return {number} 保存できたファイル数
*/
function saveAttachmentsFromThread_(thread, folder) {
let count = 0;
for (const message of thread.getMessages()) {
for (const attachment of message.getAttachments()) {
try {
folder.createFile(attachment);
count++;
} catch (error) {
Logger.log(`保存失敗: ${attachment.getName()} - ${error.message}`);
}
}
}
return count;
}
/**
* 指定名のラベルを取得。なければ作成して返す。
* @param {string} labelName ラベル名
* @return {GmailLabel}
*/
function getOrCreateLabel_(labelName) {
const label = GmailApp.getUserLabelByName(labelName);
return label ? label : GmailApp.createLabel(labelName);
}
行数は増えました。前が34行、これで72行です。倍以上になっています。
短いほうがいいように見えますが、あとで手を入れるときは、こちらのほうが早く終わります。
設定を1か所にまとめる
いちばん先頭にある CONFIG がそれです。
const CONFIG = {
FOLDER_ID: 'ここにGoogleドライブのフォルダIDを貼る',
SEARCH_QUERY: 'subject:GASテスト用 has:attachment -label:保存済み',
PROCESSED_LABEL: '保存済み',
};
前のコードでは、フォルダIDと検索条件が関数の中にあり、ラベル名は2か所に散らばっていました。冒頭に書いた、片方だけ直して動かなくなるパターンです。
設定を先頭にまとめておくと、直すときに見る場所が1つになります。コードの本体を読む必要がありません。
自分以外の誰かにこのスクリプトを渡すときも、「先頭の3行だけ書き換えてください」と言えば済みます。私は職場で使うものをこの形にしています。渡した相手に本体を触らせないで済むのが大きいです。
処理を役割ごとに分ける
関数が4つに分かれました。それぞれの役割はこうです。
saveGmailAttachments… 全体の流れを組み立てるgetTargetThreads_… 対象のメールを探すsaveAttachmentsFromThread_… 1通分の添付を保存するgetOrCreateLabel_… ラベルを用意する
メインの関数を見てください。中身はほとんど、他の関数を呼んでいるだけです。
const threads = getTargetThreads_();
const folder = DriveApp.getFolderById(CONFIG.FOLDER_ID);
const label = getOrCreateLabel_(CONFIG.PROCESSED_LABEL);
「対象を探して、フォルダとラベルを用意して、順番に処理する」という流れが、そのまま読めます。細かい手順を追わなくても、何をしているか分かる状態です。
半年後に自分が見返したとき、これが効きます。前のコードは for が3重に入れ子になっていて、どこが何をしているのか、追いかけないと分かりませんでした。
名前の末尾についている _ の意味
getTargetThreads_ のように、末尾にアンダースコアが付いた関数があります。
これはGASの決まりで、末尾に _ を付けた関数は、エディタの実行ボタンから選べなくなります。
前のコードでは、実行する関数を選ぶ欄に getOrCreateLabel も並んでいました。人が手で実行するものではないのに、選択肢に出てくるわけです。うっかり選んで実行しても意味がありません。
_ を付けておけば、実行できるのは saveGmailAttachments だけになります。「これが入口です」と示す目印として使えます。
1件の失敗で全体が止まらないようにする
try と catch が2か所に入りました。
メインの関数を囲んでいるほうは、全体の保険です。何か想定外のことが起きたときに、エラーの内容をログに残します。
} catch (error) {
Logger.log('エラーが発生しました: ' + error.stack);
}
もう1か所は、ファイル1件ごとの保存を囲んでいます。
try {
folder.createFile(attachment);
count++;
} catch (error) {
Logger.log(`保存失敗: ${attachment.getName()} - ${error.message}`);
}
こちらのほうが実用的です。
添付ファイルが壊れていたり、ドライブの容量が足りなかったりすると、保存に失敗することがあります。前のコードだと、そこでスクリプト全体が止まります。10通のうち3通目で失敗すれば、残りの7通は手つかずです。
囲んでおけば、失敗した1件だけをログに記録して、残りの処理を続けます。毎日自動で動かすものは、途中で止まらないことが大事です。
失敗したファイルの名前がログに残るので、後から手で拾えます。
動作確認のしかた
コードを入れ替えたら、必ず1回実行して確かめてください。整えたつもりで壊していることがあります。
ここで先に知っておいてほしいことがあります。前のコードで一度動かしていると、テストメールに「保存済み」ラベルが付いているので、検索に引っかかりません。 そのまま実行すると0件で終わって、うまくいったのかどうか分からなくなります。
私は新しくテストメールを送り直しました。あわせて、次の2つもやっています。
- テストフォルダを空にする … 実行後にファイルが1つだけ増える状態にしておくと、成功したかどうかを目で確認しやすくなります
- 添付ファイルを2つ付ける … スレッド数とファイル数が別々に出るので、ログの意味が分かりやすくなります
送ったのは、件名 GASテスト用(半角)、添付は中身のないダミーを2つです。実行するとこうなりました。
11:53:26 実行開始
11:53:29 1件のスレッドから2件のファイルを保存しました
11:53:30 実行完了

前は「1件のスレッドを処理しました」でした。メール1通に添付が何個入っていても、前のログでは「1件」としか出ません。何件保存されたかが分からない形です。
自動で回すものは、後からログを見て確認することになります。数字が出ているかどうかで、確認にかかる時間が変わります。
わざとエラーを起こして確かめる
try と catch が効いているかどうかは、正常に動いた画面を見ても分かりません。エラーを起こしてみないと確認できないところです。
CONFIG.FOLDER_ID の文字列を1文字だけ消して、保存してから実行します。存在しないフォルダIDになるので、確実に失敗します。
11:56:57 実行開始
11:56:56 エラーが発生しました: Exception: Unexpected error while getting
the method or property getFolderById on object DriveApp.
at saveGmailAttachments (コード:16:29)
at __GS_INTERNAL_top_function_call__.gs:1:8
11:56:58 実行完了

見てほしいのは最後の行です。「実行完了」で終わっています。 前のコードで同じことをすると、赤い文字で止まります。
自動で回しているスクリプトの場合、この違いがそのまま「気づけるかどうか」につながります。止まった場合は実行履歴に失敗として残るだけですが、catch で受けておけば、何が起きたのかがログに文章で残ります。
予想と違ったこと
エラーの文言は Unexpected error while getting the method or property getFolderById on object DriveApp. でした。
「フォルダが見つかりません」とは書かれていません。私は最初、コードの書き方を間違えたのかと思って、getFolderById の綴りを何度も見直しました。
GASのエラーは、原因をそのまま日本語で教えてくれるとは限りません。心当たりのある変更を先に疑うほうが早いです。この場合は、直前にフォルダIDを触っています。
もうひとつ手がかりがあります。2行目の コード:16:29 の部分です。これは「コード.gsの16行目、29文字目」という意味で、エラーが起きた場所を指しています。16行目を見に行けば DriveApp.getFolderById(CONFIG.FOLDER_ID) の行なので、フォルダIDが怪しいと分かります。
確認が終わったら、フォルダIDを元に戻すのを忘れないでください。 私は1文字消しただけだったので、戻すのも1文字です。戻したら、もう一度実行して正常に動くところまで見ておくと安心です。
つまずいたこと:保存しないと反映されない
これは前の記事にも書いたのですが、また同じところで引っかかりました。
フォルダIDを1文字消して、そのまま「実行」を押したつもりでいました。ところがログは前回の成功したままで、時刻も変わりません。何度見ても 1件のスレッドから2件のファイルを保存しました と出ています。
原因は保存し忘れでした。画面の上のほうに「変更が保存されていません」と出ていたのを見落としていたんです。
GASのエディタは、コードを書き換えても保存するまで実行内容に反映されません。ログの時刻が変わっていなければ、実行そのものが行われていないと考えてください。Ctrl + S(Macは Command + S)で保存してから、もう一度実行します。
前回もこれで止まったので、2回目です。手が覚えるまでは、何か直したら保存、を先にやるのがよさそうです。
トリガーは設定し直さなくていい
前の記事で1時間おきのトリガーを設定した方は、そのまま動きます。
トリガーは関数名で登録されているので、saveGmailAttachments という名前が変わっていなければ、設定はそのまま有効です。
逆に言うと、メインの関数名を変えるときはトリガーを作り直す必要があります。名前を変えただけで動かなくなり、しかもエラーも出ないので、気づくのが遅れます。名前は変えないほうが無難です。
まとめ
- 設定を先頭の
CONFIGにまとめると、直す場所が1か所になる - ファイル1件ごとに
tryで囲むと、1件の失敗で全体が止まらない - 整えたあとは必ず1回実行する。前のテストメールにはラベルが付いているので、送り直すか、ラベルを外す
このコードにも、まだ弱いところがあります。
ひとつは、保存に失敗してもラベルが付くことです。ファイル1件ごとの try は、失敗をログに残して処理を続けます。ただ、そのあとのラベル付けは、失敗があってもそのまま実行されます。そのメールは次回の検索から外れるので、失敗した添付は放っておくと拾い直されません。ログに 保存失敗: の行が出ていないか、ときどき見てください。
もうひとつは、実行時間です。GASには1回の実行が6分までという上限があり、添付ファイルが大量にたまっていると、途中で打ち切られます。1時間おきに動かしていれば、たまる前に処理されるので普段は問題になりません。ただ、しばらく止めていたあとにまとめて動かすときは、この上限に当たることがあります。
まず動かして、続けて使うと決めてから整える。この順番でいいと思っています。
※この記事の内容は執筆時点のものです。Googleの画面表示は変わることがあります。
最後まで読んでいただき、ありがとうございました。
コメント