7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

メールをLINEグループに転送するGoogle Apps Script(改良版)

Last updated at Posted at 2021-08-10

背景

以前にメーリングリストの投稿をLINEグループに転送するGoogle Apps Script(GAS)について書いた。

特定のメールをLINEグループに転送できると(相変わらず)便利だし、知人からの問い合わせからもいただいている。ところが、スクリプトにバグがあって、期待通りに動いていなかったので修正した。

「GASで特定のメールを抽出して処理する」ことができれば、LINEグループへの転送だけじゃなくて、いろいろ応用範囲が広いとおもわれる。(他のメッセージアプリへの転送、スプレッドシートへの転記など)

環境

ここで書いていることは、下記のバージョンで実施しました。

  • Gmail(フリー版)
  • Safari バージョン14.1.2 (16611.3.10.1.3)
  • macOS BigSur 11.5.1

この記事の記述範囲

大まかな手順は下記の通り。

  • LINE Notifyのトークンを取得する
  • Google Apps Script(GAS)を調製する ★
  • Google Apps Script(GAS)のトリガーを設定する
  • LINEグループにLINE Notyfyを招待する

今回は★部分についての記事。その他の箇所に関しては下記リンクご参照。

Google Apps Script(改良版)


var lineToken = "xxxxxxxx";  // LINE Notifyのトークン
var intervalMinute = 5; //●分前~現在の新着メールを取得 #--トリガーの時間間隔をこれに合わせる

/**
 * 取得したメッセージをLINEに転送します
 */
function main() {
 
  // 転送するメッセージを取得
  var newMessages = fetchMail();
  
  // 取得したメッセージをLINEに転送
  newMessages.forEach( msg => {
    console.log("msg: " + msg);
    forwardLine(msg);
  })

}

/**
 * 転送するメッセージを取得します
 */
function fetchMail() {

  // 取得の基準時刻をセット
  var nowTime       = Math.floor( new Date().getTime() / 1000 );  // 現在時刻
  var referenceTime = nowTime - ( ( intervalMinute * 60 ) + 3 );  // 秒に変換し、さらに3秒前に

  // 検索条件を指定
  // NOTE: 「転送用」ラベルをGmailで指定しておくこと
  var strSearch = '(label:11_xxxx転送用 after:' + referenceTime + ')';
  
  // スレッドを検索して取得
  // NOTE: 念の為10スレッドに制限
  var arrThreads = GmailApp.search(strSearch, 0, 10);

  // スレッドからメッセージを取得
  // NOTE: 二次元配列で、[[スレッドのインデックス][メッセージのインデックス]]で参照できる
  var arrMsgs = GmailApp.getMessagesForThreads(arrThreads);

  // ①基準時刻より後で、②エラーメッセージではないもの(正常メール)を抽出し配列に格納
  var newMsgs = [];

  arrMsgs.forEach( thread => {
    thread.forEach( msg => {

      // ①秒換算し、基準時刻より後かを判定(boolean)
      var isAfter = ( (msg.getDate() / 1000 - referenceTime ) > 0 );

      // ②正常かを判定(boolean)
      var isFine  = msg.getSubject().startsWith('[');

      // log for debug //
      // console.log('isAfter: ' + isAfter);
      // console.log('isFine:  ' + isFine);
      // console.log('msg.getDate(): ' + msg.getDate());
      // console.log('msg.getSubject(): ' + msg.getSubject());

      if (isAfter && isFine) {
        // メッセージ文字列を生成
        var strMsg = " "
          + Utilities.formatDate(msg.getDate() , 'Asia/Tokyo', 'MM/dd HH:mm')
          + "\n\n[送信者]" + msg.getFrom()
          + "\n\n[件名]"   + msg.getSubject()
          + "\n\n[本文]\n" + msg.getPlainBody();
        // 配列に追加
        newMsgs.push(strMsg);
      }

    })
  })

 return newMsgs;
}


/**
 * LINEにメッセージを転送します
 * @param {String} Me メッセージの内容
 */
function forwardLine(Me) {

  var payload = {'message' : Me};
  var options = {
    "method"  : "post",
    "payload" : payload,
    "headers" : {"Authorization" : "Bearer "+ lineToken}  
  };

  UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);

}


ほとんどコメントに書いてあるが、若干補足。

基準時刻のセット

// 取得の基準時刻をセット
var nowTime       = Math.floor( new Date().getTime() / 1000 );  // 現在時刻
var referenceTime = nowTime - ( ( intervalMinute * 60 ) + 3 );  // 秒に変換し、さらに3秒前に

Google Apps ScriptでGmailの情報を取得する際に、受信日時を細かく指定する方法 - Qiita
にあるとおり、

  • 日時でなく、秒(UNIX時間)で検索条件を設定できる
  • getTime()はミリ秒を返すので、検索のために秒に変換(÷1000)する

ただし、UNIX時間をつかっても、メールの検索はできない(スレッドの検索のみ可能)ので、スレッドに含まれる過去メールを引っ張ってきてしまう。

また、+3秒というのは適当で、GASの実行時間を考慮し、メールの取りこぼしを防ぐため。

// ①秒換算し、基準時刻より後かを判定(boolean)
var isAfter = ( (msg.getDate() / 1000 - referenceTime ) > 0 );

ここも考えかたはおなじで、個々のメッセージの日時がミリ秒で取れるので、秒に変換したのち、基準時刻と比較。

スレッドの検索

// スレッドを検索して取得
// NOTE: 念の為10スレッドに制限
var arrThreads = GmailApp.search(strSearch, 0, 10);
  • GmailApp.search(query, start, max)
    • スレッドを対象に検索(メールを対象に検索はできない)
    • startは開始番号
    • maxは取得スレッドの最大数
    • 返り値はGmailThread[](スレッドの1次元配列)

取得スレッドが大きくなると、実行時間制限にひっかかる。いまの使い方で取得スレッドが多くなることは有り得ないが、万が一の場合のリソース消費を抑えるために最大数を記述しておいた。

改良点

過去記事のスクリプトとの違いは下記。

  • 転送するメールの件数
    • Before: Gmailのスレッドごとに、いちばん新しいメール1通を転送
    • After: Gmailのスレッドに含まれるメールのうち、指定時刻より後のメールをすべて転送
  • 転送するメールの条件
    • Before: エラーメールであっても転送する
    • After: エラーメールは転送しない
  • リファクタリング
    • 変数名の明瞭化、forループをforEachに変えて見通しを良くした

(参考)Gmailのしくみの前提

Gmailの構成は、GmailApp > スレッド > メッセージ(メール)になっている。スレッドに紐付かないメッセージは存在しない。

  • 検索は、スレッドに対して行われる(メッセージに対する検索はGASにない)
  • ラベルは、スレッドに対してつけられる(メッセージに対してではない)
  • 設定でスレッド表示のON/OFFができるが、表示を変えられるだけで、内部機能としてはスレッドが生きている

この前提を理解するのにいちばん時間がかかった。

(参考)過去記事のGASのバグの現象と原因

現象

  • メーリングリストでエラーメッセージが返ってくると、LINEグループにはエラーメッセージしか転送されない

原因

  • エラーメッセージは数秒で返ってくる
  • GASは5分おきにcronしている
  • スクリプトが「5分以内に届いたスレッドのうち、最新のメッセージ1通を転送せよ」という記述になっていた

よって、エラーメッセージが返ってこない場合は、期待通りにメッセージがLINEに転送されるが、エラーメッセージが返ってくると、エラーメッセージだけLINEに転送されていた。

また、続けてメーリングリストへの投稿があると(めったにないが)、最新の1通のみ転送されていた。

リンク

過去記事へのリンク

その他リンク

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?