こんにちは!ITライターのもり(@moripro3)です!
前回の記事で、Gmailで検索条件にヒットしたスレッドの全メールをスプレッドシートに書き込むスクリプトを紹介しました。
このスクリプトは、実行するたびに、検索条件にヒットしたすべてのメッセージを書き出す仕組みになっています。
今回の記事では、Gmailの「メッセージID」を使用して、検索条件にヒットしたメッセージのうち、新規メッセージのみをスプレッドシートに追記するように改良していきます!
前回のスクリプトの確認
前回作成したスクリプトがこちらです。
function searchContactMail() { /* Gmailから特定条件のスレッドを検索しメールを取り出す */ var strTerms = '検索文字列'; var myThreads = GmailApp.search(strTerms,0,30); var myMsgs = GmailApp.getMessagesForThreads(myThreads);//二次元配列 var valMsgs = []; /* 各メールから日時、送信元、件名、内容を取り出す */ for(var i=0;i<myMsgs.length;i++){ for(var j=0;j<myMsgs[i].length;j++){ var date = myMsgs[i][j].getDate(); var from = myMsgs[i][j].getFrom(); var subj = myMsgs[i][j].getSubject(); var body = myMsgs[i][j].getPlainBody().slice(0,200); var perm = myThreads[i].getPermalink(); valMsgs.push([date,from,subj,body,perm]); } } /* スプレッドシートに出力 */ if(myMsgs.length>0){ var mySheet=SpreadsheetApp.getActiveSpreadsheet().getSheetByName('メール'); mySheet.getRange(2, 1, valMsgs.length, 5).setValues(valMsgs); } }
スクリプト修正のポイント
前回のスクリプトと今回のスクリプトの違いです。
【例】1日に3回実行・検索条件にヒットしたメッセージの件数
- 1回目:4件ヒット
- 2回目:7件ヒット(=新規3件)
- 3回目:9件ヒット(=新規2件)
すべてのメッセージを書き込む(前回の記事)
スクリプトを実行するたび、検索条件にヒットしたすべてのメッセージを、2行目を起点として書き込みました。
つまり、1回目・2回目の実行結果は、3回目の実行結果によって上書きされます。
新規メッセージのみを追記する(今回の記事)
E列にメッセージIDの列を用意します。
メッセージIDを使用して前回実行からの差分を判定し、新規メッセージのみを末尾に追記していきます。
メッセージIDを取得して新規メッセージのみをスプレッドシートに書き出す
それではスクリプトを修正していきましょう!
メッセージIDを取得する
メッセージIDとは、Gmailの各メッセージに一意に割り当てられたIDのことです。
このメッセージIDはgetIdメソッドで取得できます。
二次元配列myMsgsに格納したそれぞれのメッセージから「メッセージID」を取得するコードをfor文の中に組み入れます。
var msid = myMsgs[i][j].getId();
こちらの記事もあわせてご覧ください。
スプレッドシートにメッセージIDが存在するか判定する
取得した「メッセージID」を書き込む列をスプレッドシートのE列に用意して、検索条件にヒットしたメッセージの「メッセージID」が既に存在するか判定します。
- 存在する→書き込み不要
- 存在しない→新規メッセージなので書き込む
判定関数のスクリプトがこちらです。
オブジェクト変数のmySheetはグローバル変数として定義しておきます。
function hasId(id){ var data = mySheet.getRange(1, 5, mySheet.getLastRow(),1).getValues();//E列(メッセージID)を検索範囲とする var hasId = data.some(function(value,index,data){//コールバック関数 return (value[0] === id); }); return hasId; }
someメソッドの使い方はこちらの記事をご覧ください。
hasIdの返り値はこのようになります。
- メッセージIDが存在する → true
- メッセージIDが存在しない → false
新規メッセージのデータを配列に格納する
if文を使用して、メッセージIDがスプレッドシートに存在するかチェックし、存在しない場合は、pushメソッドで配列変数valMsgsにデータを格納します。
! は論理NOT演算子です。条件式の真偽値を反転させます。
hasIdの返り値がtrueでなければ(=falseであれば)、処理を実行します。
if(!hasId(msid)){ var date = myMsgs[i][j].getDate(); var from = myMsgs[i][j].getFrom(); var subj = myMsgs[i][j].getSubject(); var body = myMsgs[i][j].getPlainBody().slice(0,200); var perm = myThreads[i].getPermalink(); valMsgs.push([date,from,subj,body,msid,perm]); }
スプレッドシートに書き出す
配列変数valMsgsに格納されたデータを、setValuesメソッドでスプレッドシートの末尾に書き込みます。
ポイントは2点です。
- valMsgsの要素数が0の場合は書き込まない(=新規メッセージ無し)
- getRangeメソッドの行番号に、最終行+1を指定する(末尾に追記する)
if(valMsgs.length>0){ var lastRow = mySheet.getDataRange().getLastRow(); mySheet.getRange(lastRow+1, 1, valMsgs.length, 6).setValues(valMsgs); }
スクリプトまとめ
以上をまとめたスクリプトがこちらです。
var mySheet=SpreadsheetApp.getActiveSpreadsheet().getSheetByName('メール'); function searchContactMail() { /* Gmailから特定条件のスレッドを検索しメールを取り出す */ var strTerms = 'スレッドテスト';//検索文字列 var myThreads = GmailApp.search(strTerms,0,30); var myMsgs = GmailApp.getMessagesForThreads(myThreads);//二次元配列 var valMsgs = []; /* 各メールから日時、送信元、件名、内容を取り出す */ for(var i=0;i<myMsgs.length;i++){ for(var j=0;j<myMsgs[i].length;j++){ var msid = myMsgs[i][j].getId();//メッセージIDを取得 //もしメッセージIDがスプレッドシートに存在しなければ if(!hasId(msid)){ var date = myMsgs[i][j].getDate(); var from = myMsgs[i][j].getFrom(); var subj = myMsgs[i][j].getSubject(); var body = myMsgs[i][j].getPlainBody().slice(0,200); var perm = myThreads[i].getPermalink(); valMsgs.push([date,from,subj,body,msid,perm]); } } } /* スプレッドシートに出力 */ if(valMsgs.length>0){//新規メールがある場合、末尾に追加する var lastRow = mySheet.getDataRange().getLastRow(); mySheet.getRange(lastRow+1, 1, valMsgs.length, 6).setValues(valMsgs); } } function hasId(id){ var data = mySheet.getRange(1, 5, mySheet.getLastRow(),1).getValues();//E列(メッセージID)を検索範囲とする var hasId = data.some(function(value,index,data){//コールバック関数 return (value[0] === id); }); return hasId; }
実行結果
検索条件にヒットした新規メッセージについて、この6つの情報が取得できます。
- 日付
- 差出人
- 件名
- メッセージ(冒頭200文字)
- メッセージID
- パーマリンク
まとめ
Gmailのメッセージに一意に割り当てられたメッセージIDを取得・利用して、スクリプトを実行する都度、新規メッセージのみをスプレッドシートに追記する方法を紹介しました。
スクリプトを実行するたびに全件の書き込みをするよりも、新規メッセージのみを判定して追記していくほうが、「データ蓄積」という観点ではより合理的ですよね。
ほんのひと手間くわえて、よりスマートなスクリプトを作成していきましょう。
今後もGASの便利なツールを紹介していきます。
どうぞお楽しみに!
連載目次:GASでGmailに届いた問い合わせメールを収集する
お仕事の現場では日々様々なメールが届きます。その中で重要なのが「問い合わせメール」ですね。このシリーズでは、Webサイトからの問い合わせメールを自動でスプレッドシートに取り込んだり、チャットワークに送る方法についてお伝えしていきます。
コメント
当方で似たような作業が発生しておりまして、とても参考になる記事をありがとうございます。
おかげさまでプログラム自体完成し利用中なのですが、
特殊な事例として2点質問があり、プログラムで対応する方法があればご教示いただければと思います。
(1)メッセージIDが指数表示される
非常に珍しい例だと思うのですが、メッセージIDが「1.68E+14」のように転記されてしまい、
該当のメールがこのIDで転記され続けるということが発生しました。
このメールの本来のIDを調べたところ「16763457813448e1」のようなものでしたので
先頭にアポストロフィを付けて正しいIDを手入力したところ転記は止まりました。
このような問題を回避する方法はありますでしょうか。
なお
>var msid = myMsgs[i][j].getId();//メッセージIDを取得
を
>var msid = “‘” +myMsgs[i][j].getId();
のように予めアポストロフィを付ける形で行っても転記され続けました。
(2)メッセージIDの範囲検索が複数シートにまたがっている場合
>var data = mySheet.getRange(1, 5, mySheet.getLastRow(),1).getValues();//E列(メッセージID)を検索範囲とする
ここについてですが、mySheetのE列だけではなく、同一ブックの別シートのE列も検索範囲に含ませたい場合はどのように取得すれば良いでしょうか。
ご回答いただけますと幸いです。
宜しくお願い致します。
オノ様、お問い合わせの件につきまして
(1)メッセージIDが指数表示される
貴重なご報告ありがとうございます。
スプレッドシートの設定で、セルの表示形式を事前に設定しておくことで回避できるかと思います。この場合はメッセージIDを転記するE列への設定になります。
「表示形式→数字」がデフォルトで「自動」になっていますので、ここを「書式なしテキスト」に設定しておくと、GASでメッセージIDを転記するときに、指数表示にならずそのままのIDが転記されます。
(2)メッセージIDの範囲検索が複数シートにまたがっている場合
var data1 = mySheet1.getRange(~)
var data2 = mySheet2.getRange(~)
と、検索範囲の変数を2つ用意して、
「data1とdata2のどちらにも存在しなければ新規メッセージと判定する」としてみてはいかがでしょうか。
もり様
大変早々にご回答いただきましてありがとうございます。
ご提案いただいた方法を実行しました。
(1)メッセージIDが指数表示される
予めE列を「書式なしテキスト」に設定した上で転記しましたが、やはり指数表示になってしまいました。
こちらの設定に問題があるのかもしれませんので、色々試してみます。
もし何か進展がありましたらご報告させていただきます。
(2)メッセージIDの範囲検索が複数シートにまたがっている場合
ご提案いただいたように
hasIdの他にhasId2も用意し、hasId2内でdata2を用意した上で
if(!hasId(id)){
if(!hasId2(id)){
~略~
}
}
と入れ子にした所希望通りの動作になりました。
dataだけでどうにか範囲指定しようとして行き詰っていたため、
2つ変数を用意するのはどうかというアドバイスのおかげで形にできました。
本当にありがとうございました。
もり様
お世話になっております。
(1)メッセージIDが指数表示される
こちらの件ですが対応できましたので報告いたします。
当方ではスプレッドシート上でE列を「書式なしテキスト」に予め設定しても指数表示されてしまったため、プログラム内でメッセージIDを転記するセルに対して転記する直前に「書式なしテキスト」を設定したところ対応できました。
var idx = sheet.getLastRow()+1; //最終行の次の行番号を取得しidxに入れる
var idRange=sheet.getRange(idx,5); //(idx行E列)をidRangeとする
idRange.setNumberFormat(‘@’); //idRangeセルを書式なしテキストに設定
idRange.setValue(id); //idRangeセルにidを転記
以上です。
もり様には色々とアドバイスをいただき、おかげさまで何とか形にできました。
当方のメールの自動転記プログラムに関してはこれでひとまず完成したと思いますが、指数表示のように今後また想定外の事象が発生した際はお伝えできればと思います。
ありがとうございました。
もり様
たびたびすみません。
先ほどのプログラムで一部誤りがありましたので訂正します。
×
idRange.setValue(id); //idRangeセルにidを転記
○
idRange.setValue(msid); //idRangeセルにmsidを転記
失礼致しました。