2019年1月2日水曜日

GMAILに受信したメールをGoogle Apps Scriptで処理する

GMailに受信したメールを処理するスクリプトを作成した。
検索を行いその結果に対して処理を行うが、一度処理したメールは再度処理しないようにしなければならない。処理をしたメールにたいし、ラベルをつけたり、既読化したり、スターをつけたりすることで対処しようとしているページが多い。ラベルや既読化はスレッド単位で検索されてしまうので問題が発生するし、既読化やスターをつける方法は複数のスクリプトで処理したいメールがある場合に対応できない。

今回はメール受信日時で処理済みかどうかを判断する方法を採用した。時間駆動トリガで定期的に実行することを想定しているが、トリガを仕掛ける処理は組み込んでいないので、手動でトリガ指定する必要がある。処理量が多いと一回のスクリプト実行時間(6分)では処理しきれないこともあるため中断することも考慮に入れている。
目的に合わせて青字の部分をカスタマイズして使用する。


/// トリガ設定は行っていないので、手動で指定する必要があります。

function Main() {
  var ScriptStartTime = new Date();
//////////////////////////////////////////////////////////////////////////////////////
// カスタマイズはここから
////////////////////////////////////////
  // メールを検索する条件(beforとafterは指定しない)
  var SearchString = 'in:all -in:spam -in:trash has:attachment';
    
  // 処理対象期間
  var SearchStartDate = new Date('2018/10/00 0:00:00');
  var SearchEndDate = new Date(ScriptStartTime);

  // 一回の検索で検索する期間5分で処理できる量を指定するのが一番良い。
  var IncrementHours = 24;

  // メールに対する処理(例:メール受信日時フォルダを生成し、その中に添付ファイルを保存する)
  var Action = function(mail)
  {
    var DriveFolderName = "GmailAttachments";  
    var reciveDate = mail.getDate();
    var folderID = getDriveFolder([DriveFolderName,reciveDate.getFullYear(),reciveDate.getMonth()+1,reciveDate.getDate()]);
    var attachments = mail.getAttachments();
    for (var n = 0; n < attachments.length; n++)
    {
      folderID.createFile(attachments[n]);
    }
  }  
////////////////////////////////////////
// カスタマイズはここまで
//////////////////////////////////////////////////////////////////////////////////////

  var properties = PropertiesService.getScriptProperties().getProperties();
  
  // 検索開始日時をプロパティから取得する
  if( 'SearchStartDate' in properties ){
    SearchStartDate = new Date(properties['SearchStartDate']);
  }

  // 実施すべきジョブ(MailIDの配列)をプロパティから取得する。
  var JobList = [];
  if( 'JobList' in properties ){
    JobList = JSON.parse(properties['JobList']);
  }

  for( var start = SearchStartDate; start < SearchEndDate; )
  {
    // メール検索期間の設定
    var end = new Date(start);
    end.setHours(end.getHours() + IncrementHours);
    if( end > SearchEndDate ){
      end = new Date(SearchEndDate);
    }
    
    // JobListの生成(処理すべきメールのIDリスト)
    if( Object.keys(JobList).length == 0 )
    {
      JobList = getMailIdList(SearchString,start,end);
    }
    
    // JobListに従い処理実行
    while( JobList.length > 0 )
    {
      // スクリプト開始から5分経過している場合は実行経過をプロパティに保存して処理を中断する。
      var CurrentTime = new Date();
      if( (CurrentTime - ScriptStartTime) > 5*60*1000 )
      {
        properties['SearchStartDate'] = start;
        properties['JobList'] = JSON.stringify(JobList);
        PropertiesService.getScriptProperties().setProperties( properties );
        return;
      }
      
      Action(GmailApp.getMessageById(JobList.shift()));   // 処理実行関数呼び出し
    }
    
    // 検索期間を更新
    start = end;
  }
  properties['SearchStartDate'] = start;
  properties['JobList'] = JSON.stringify(JobList);
  PropertiesService.getScriptProperties().setProperties( properties );
}

// 指定期間で検索を行い、指定期間に受信したメールのIDを配列で取得する
function getMailIdList( searchString, start, end )
{
  var after = 'after:' + Math.floor( start.getTime()/1000 ).toString();
  var before = 'before:' + Math.floor( end.getTime()/1000 ).toString();
  const search = after + ' ' + before + ' ' + searchString;

  var mailIdList = [];
  var indexStart = 0;
  while( true )
  {
    var myThreads = GmailApp.search(search, indexStart, 500);
    if( myThreads.length==0)
    {
      return mailIdList;
    }
    indexStart+=myThreads.length;
    
    var myMsgs = GmailApp.getMessagesForThreads(myThreads);
    for(var thread = 0;thread < myMsgs.length;thread++){
      for(var msg = 0;msg < myMsgs[thread].length;msg++){
        var reciveDate = myMsgs[thread][msg].getDate();
        if( start < reciveDate && end > reciveDate )
        {
          mailIdList.unshift(myMsgs[thread][msg].getId());
        }
      }
    } 
  }
}

//////////////////////////////////////////////////////////////////////////////////////
// カスタマイズ部分から呼び出している関数はここから
////////////////////////////////////////
// 名前を指定したフォルダ生成する、すでに存在する場合はそのフォルダを取得する
// 階層構造を一気に作成できます。
// 作成したいフォルダ:(RootFolder)/folderA/folderB/folderC
// 呼び出し方:getDriveFolder(["folerA","folderB","folderC"]);
function getDriveFolder( folderHierarchy, folder ) {
  if ( folder === undefined ) folder = DriveApp.getRootFolder();    
  for(var h = 0;h < folderHierarchy.length; h++ ){
    var childs = folder.getFoldersByName(folderHierarchy[h]);
    if( childs.hasNext() ) {
      // 指定された名前のフォルダがあるときはそのフォルダを使用
      folder = childs.next();
    } else {
      // 指定された名前のフォルダが見つからなかったときはフォルダを生成
      folder = folder.createFolder( folderHierarchy[h] );
    }
  }
  return folder;
}
////////////////////////////////////////
// カスタマイズ部分から呼び出している関数はここまで
//////////////////////////////////////////////////////////////////////////////////////

メールから必要な部分を抜き出してスプレッドシートに張り付ける場合のAction関数は以下のような感じで実現した。

  // メールに対する処理
  var Action = function(mail)
  {
    // 処理サンプル:以下のメールから必要な部分をシートに張り付ける。
      //    氏 名 様
      //    
      //    いつも ANAプリペシルバーマイルコース をご利用いただき、
      //    ありがとうございます。
      //    氏 名 様のカードのご利用がありましたのでご連絡します。
      //    
      //     【ご利用明細の承認番号】
      //      1234567
      //    
      //     【ご利用カード番号】
      //      ****-****-****-0000
      //      ※セキュリティ保護のため、下4桁のみ表示しています。
      //    
      //     【ご利用日時(日本時間)】
      //      2018/12/29 17:20
      //    
      //     【ご利用先】
      //      セブン-イレブン
      //    
      //     【ご利用金額】
      //      1,234 円
      //    
      //     【バリュー残高】
      //      123,456 円
      //    
      //    ご利用先は、加盟店からの売上情報到着前の情報となり、実際のご利用先と名称が異なる場合があります。
      //    (売上情報到着後の名称については、マイページにログインのうえ「ご利用明細の確認」メニューよりご確認ください。)
      //    なお、携帯電話料金などの月額料金のお支払いに登録され、
      //    9:00PM以降にバリュー残高からの加算・減算が発生した場合は、翌8:00AM以降のメール送信となります。
      //    
      //    ご不明点などありましたら、JCBプリペイドカードデスクにお問い合わせください。    
    
    
    var sheet = SpreadsheetApp.getActiveSheet();
    var valTitle = [["【ご利用日時(日本時間)】","【ご利用先】","【ご利用金額】","【バリュー残高】","【ご利用明細の承認番号】","【ご利用カード番号】"]];
    var i = sheet.getLastRow();
  
    if(i==0){
      sheet.getRange(1, 1, 1, valTitle[0].length).setValues(valTitle); //シートに貼り付け
      i=1;
    }
    var bodys = mail.getPlainBody().replace(/[\t \r]/g,'').split("\n");
    var valMsgs=[];
    valMsgs[0]=[];
    for(var l = 0;l < valTitle[0].length; l++ ){
      valMsgs[0][l] = bodys[bodys.indexOf(valTitle[0][l])+1];
    }
    sheet.getRange(i+1, 1, 1,valTitle[0].length).setValues(valMsgs); //シートに貼り付け
  }

0 件のコメント:

コメントを投稿

質問、要望、指摘など書いていただいてもよいですが、対応できるとは限りませんのでご了承ください。私に対するものも含め他の人を嫌な気分になるようなコメントは避けてください。