背景
以前にメーリングリストの投稿を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
に変えて見通しを良くした
- 変数名の明瞭化、forループを
(参考)Gmailのしくみの前提
Gmailの構成は、GmailApp > スレッド > メッセージ(メール)
になっている。スレッドに紐付かないメッセージは存在しない。
- 検索は、スレッドに対して行われる(メッセージに対する検索はGASにない)
- ラベルは、スレッドに対してつけられる(メッセージに対してではない)
- 設定でスレッド表示のON/OFFができるが、表示を変えられるだけで、内部機能としてはスレッドが生きている
この前提を理解するのにいちばん時間がかかった。
(参考)過去記事のGASのバグの現象と原因
現象
- メーリングリストでエラーメッセージが返ってくると、LINEグループにはエラーメッセージしか転送されない
原因
- エラーメッセージは数秒で返ってくる
- GASは5分おきにcronしている
- スクリプトが「5分以内に届いたスレッドのうち、最新のメッセージ1通を転送せよ」という記述になっていた
よって、エラーメッセージが返ってこない場合は、期待通りにメッセージがLINEに転送されるが、エラーメッセージが返ってくると、エラーメッセージだけLINEに転送されていた。
また、続けてメーリングリストへの投稿があると(めったにないが)、最新の1通のみ転送されていた。