GAS 指定の条件に合致したメールに添付されているファイルを「元ファイル名+受信日時」に改変して指定のフォルダに保存する ~コード完成編~
↓これらの続きです。
前回までの問題点
拡張子をうまいこと取得してリネームする際に最後につけたい、どうするんだ?
というところで終わっていた。
拡張子~~~と呻いていたところ、FacebookではHさんに、ノンプロ研ではKさんUさんに「splitだ」とアドバイスをいただいた。ありがとう!
最終的なコード
あまりないと信じたいが、もし添付ファイル名が「TE.ST.pdf」のように、ファイル名にドットが含まれていても、最後のドットで拡張子を取得する、というコードになっている。ファイル名にドットを入れない、というのは広く知られて欲しい。
/* 指定の検索条件に合致するメールに添付されているファイルを取得するツール */
//↓ イコール記号のあとの部分、シングルコーテーションに囲まれている個所に「フォルダID」を入力してください。
const FOLDER_ID = 'your ID';
//↓ イコール記号のあとの部分、シングルコーテーションに囲まれている個所に「メールの検索条件」を入力してください。
const SEARCH_TERM = 'from:(xxx@xxx.xxx)) after:2021/1/23 before:2021/5/25';
function fetchFile() {
const folder = DriveApp.getFolderById(FOLDER_ID);
const threads = GmailApp.search(SEARCH_TERM, 0, 10);//(検索条件, 開始スレッドのインデックス, 最大取得数)
const messages = GmailApp.getMessagesForThreads(threads);
for (const thread of messages) {
for (const message of thread) {
let dateForFileName = message.getDate();//メール受信日時
dateForFileName = Utilities.formatDate(dateForFileName, "JST", "yyyy-MM-dd_HH:mm:ss");//日付の表示形式
const attachments = message.getAttachments();
for (const attachment of attachments) {
const originalAttachmentFileName = attachment.getName(); //添付ファイルの元の名称
const splitedFileName = originalAttachmentFileName.split('.');//添付ファイルの元の名称からドットで切り分ける
// 元のファイル名の本体 + 日付 + 拡張子
const datedFileName = splitedFileName.reduce((acc, current, index) => {
if (index == splitedFileName.length - 1) {
return `${acc}_${dateForFileName}.${current}`;
} else {
return `${acc}.${current}`;
}
});
// 指定のフォルダにファイルを作成する
folder.createFile(attachment).setName(datedFileName);
}
}
}
}
以下、自分の理解のためにそれぞれのコードの意味について。
split
上記のコードのこの部分
const splitedFileName = originalAttachmentFileName.split('.');//添付ファイルの元の名称からドットで切り分ける
String.split(“分割したい文字”) という形になっている。今回はドットで切り分ける。
originalAttachmentFileNameは既に前段でgetNameして取得している文字列(String)である。これをドットで切り分ける、ということになる。
コンソールログをみればわかるが、元ファイル名がTEST.pdfならば、
[ 'TEST', 'pdf' ]
のように、配列でドット区切りで文字列が分割される。
reduce&アロー関数
そして、この切り分けたドットを利用して、うまことしているのがこの部分。元ファイル名、日時、拡張子 という形でファイル名を整形している。
アロー関数にif/elseという、この前の講座の内容がてんこ盛りです。これ、私が書いたんでなくて、Uさんの作です。この書き方、今の私には思いつけそうにない。reduceは知らない子ですね。
const datedFileName = splitedFileName.reduce((acc, current, index) => {
if (index == splitedFileName.length - 1) {
return `${acc}_${dateForFileName}.${current}`;
} else {
return `${acc}.${current}`;
}
});
reduceとは(言葉の意味で)
reduceとは(コード的な意味で)
どういうこと?
うまいことやってる、というのでざっくりとした理解はできたが、それぞれのコードがどのような意味、働きをしているのか、じっくり読み解いてみます。
以下、自分の理解のために書いているので、めちゃくちゃわかりにくいと思いますが、だーっと書いてみます。
話を分かりやすくするために、ここでは、取得するファイルの名称は「TEST.pdf」だと仮定します。
reduce((acc, current, index) ここに絞ってみていくと、
acc accumulator(前回の値)
current currentValue(現在の値)
index (現在currentとして処理されている要素のindex)
ということになりそう。
ちょっとそれは置いておいて、if。
if (index == splitedFileName.length - 1) {
return `${acc}_${dateForFileName}.${current}`;
もし、indexが splitedFileName.length - 1 と一致していれば、
`${acc}_${dateForFileName}.${current}` として、元ファイル名、受信日時、拡張子 を返す。if のあとのindexはどこから出てきた?何を指す?
splitedFileName.length は [ 'TEST', 'pdf' ] ならば、配列内の要素(値)が2個なので、2となる。
ここで注意なのは、配列のインデックスは0から始まるんだった。
[ 'TEST', 'pdf' ] は、
TEST=0
pdf=1 というインデックスになる。
このズレを利用する。
index == splitedFileName.length - 1
としているので、TEST.pdfの場合は splitedFileName.length=2なので、
index == 2-1、indexは1、つまりpdfになる。 これが、仮にTE.ST.pdfとなっていても、常にlength - 1で、最後のドットの切り分けの後の拡張子を取ってきている仕組みになる。TE.ST.pd.f とかになっていたら詰むけど、そこは考慮にいれない。
const datedFileName = splitedFileName.reduce((acc, current, index) => {
console.log(acc);
console.log(current);
console.log(index);
if (index == splitedFileName.length - 1) {
return `${acc}_${dateForFileName}.${current}`;
} else {
return `${acc}.${current}`;
}
return `${acc}_${dateForFileName}.${current}`;
TEST_受信日時.拡張子 が戻り値となる。
テンプレート文字列ですね。
うん、ここはいいんだ。
あ~reduceとアロー関数のあたりの関係、仕組みがよくわかってねえなこれは?
reduce、うん、これだ、これをちゃんと理解しよう。
というわけで、コードの理解、読み解きは次回に持ち越し。