3
2

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 5 years have passed since last update.

(市民吹奏楽団向け)freemlの代替手段をGASとLineNotifyで構築した話

Last updated at Posted at 2020-02-02

はじめに

市民吹奏楽団に所属するみなさんにとって、2019年にひとつの大事件が起きたと思います。
freemlのサービス終了
大手メーリングリストサービスの終了です。

その代替として、以下を検討した団体も多いかと思います。

  • Slack
  • Googleグループ

Slackは技術者ではない人には馴染みが薄く、
Googleグループは管理が面倒くさい上にメールが届かない人もいる…
(freemlもそうでしたが)

私の所属する団体でも同様の問題に直面しており、
メールの届かない人にはLINE等で個別に連絡する事態になっていました。
そこで、通知をメールに頼らない、Gmailに送ったメールをLINEに通知するという仕組みを構築しました。

なお、本記事は市民吹奏楽団向けと記載していますが、
要件さえ合えばサークルや部活にも使えるのではないでしょうか。

要件

本仕組みは、以下を必須の要件として考えています。

Gmailの宛先を解析してLINEグループに通知する

連絡用メールアドレスを作成し、そこに送られた内容をLINEグループに通知します。
通知は個人宛てではなく、LINEグループという制限があります。

複数の宛先に対応する

市民吹奏楽団には「係」が存在しているかと思います。
お金を管理する「会計係」や演奏会の演出を考える「企画係」など。
それぞれの係ごとに対応できるようにしています。
LINEグループさえあれば、パートでの対応も可能です。
つまり、

  • 団全体向けの通知
  • 会計係向けの通知
  • 企画係向けの通知
  • トランペットパート向けの通知

等々と振り分けを行います。
また、通知の宛先毎にLINEグループが必要です。

LINEグループに入っていない人からもメールが送れる

全員に連絡するだけであれば、LINEのトークで十分です。
係に所属していない人でも、その係に連絡が取れる
ことを目的としています。
例えば、「この楽器を買いたいんだけど、承認お願いします」と会計係に連絡するような場合です。
ここが他の代替サービスでなかなか実現できなかったことです。

定期的な自動実行

1分周期でGmailを確認し、メールがあればLINEグループに通知するようにします。
メールがなければなにもせずに終了します。

使用するサービス

本仕組みでは、以下のサービスを組み合わせています。

LINE Notify

LINEが提供している、外部サービスとの連携機能です。
提供されているAPIを使用して後述のGASからLINEに通知します。
LINE Notify

Gmail

ユーザ名の後ろに「+」と任意の文字を付与することで複数アドレスとして扱える仕様を活用します。
例えば以下のような(自動リンク回避しています。アドレスは架空です。)。
sample@ gmail.com(元々のアドレス)
sample+all@ gmail.com
sample+kaikei@ gmail.com
これらは全て「sample@ gmail.com」に届きますが、「宛先」に設定されたアドレスが異なります。
この「+」以降に文字を付与することで複数の宛先を実現しています。
詳しくは以下の記事がわかりやすいです。
https://www.howtonote.jp/gmail/sent/index18.html

GoogleAppsScript(GAS)

Googleのサービスとの連携に特化しているスクリプトです。
Web上で作成するため、開発環境を整える必要がないという特徴があります。
今回はその中から、Gmailに関する機能を活用していきます。
詳しくは以下の記事がわかりやすいです。
超初心者へGoogleAppsScriptを始めるメリットをこれでもかと説明します

これらのサービスを組み合わせ、
Gmailに送られたメールをGASで宛先の解析を行い、LINE Notifyを使用して対応するLINEグループに通知する
仕組みを作成していきます。

スライド1.PNG

準備

LINE Notify

まずはLINEを通知するグループを作りましょう。すでに存在しているグループでも問題ないです。
この仕組を作成する人が一旦グループに入る必要がありますが、あとで抜けても大丈夫です。

そしてLINEグループに対応するLINE Notifyの登録をしましょう。
登録方法は以下のページを参照ください。非常にわかりやすいです。
[超簡単]LINE notify を使ってみる

グループLINEに「LINE Notify」を招待するところまでできればOKです
作成したトークンは忘れないようにメモしておきましょう。

Gmail

専用のGoogleアカウントを用意しましょう。
また、複数アカウントにログインした状態だとGASが操作できない場合がありますので、
一旦他のアカウントからはログアウトしてください。
本仕組みが出来上がった後は個人アカウントにのみログインした状態で問題ないです。

GAS

上記で作成したGoogleアカウントにログインし、Googleドライブから任意の場所でスクリプトを作成していきます。
「新規」ボタンから「その他」→「Google Apps Script」を選択します。
20200202_134448.JPG

以下のような画面が表示されたかと思います。
20200202_134701.JPG

名前もそれらしいものに変更しましょう。
「Rename」から可能です。今回は「sendLine」として作成しています。
20200202_134837.JPG

ではソースコードを書いていきましょう。

ソースコード

それではスクリプトのソースコードになります。
まずは全体を。

sendLine.gs
function splitAddress(address_all){
  var address = address_all.split(",");
  for(var i = 0; i < address.length; i++){
    address[i] = getAddress(address[i]);
  }

  return address;
}

function getAddress(address){
  var match_address = address.match(/[\w\-\.\+\_]+@[\w\-\.]+\.[a-zA-Z]+/);
  /* メールアドレスではない文字の場合は空白にする(後続処理にて送信しない) */
  if(null === match_address){
    match_address = " ";
  }
  var trim_address = match_address[0].trim().toLowerCase();

  return trim_address;
}

function getToken(address){
  var token;
  var pr_adr_all = PropertiesService.getScriptProperties().getProperty('ADR_ALL');
  var pr_adr_kaikei = PropertiesService.getScriptProperties().getProperty('ADR_KAIKEI');
  var pr_adr_kikaku = PropertiesService.getScriptProperties().getProperty('ADR_KIKAKU');
  
  switch (address){
    case pr_adr_all:
      token = PropertiesService.getScriptProperties().getProperty('TOKEN_ALL');
      break;
    case pr_adr_kaikei:
      token = PropertiesService.getScriptProperties().getProperty('TOKEN_KAIKEI');
      break;
    case pr_adr_kikaku:
      token = PropertiesService.getScriptProperties().getProperty('TOKEN_KIKAKU');
      break;
    default:
      token = "none";
      break;
  }

  return token;
}

function sendLine(address,message){
  for(var i = 0; i<address.length; i++){
    var token = getToken(address[i]);
    /* 対象外のアドレスの場合はスキップ */
    if("none" == token){
      continue;
    }
    
    var subject = message[0].getSubject();
    var from = message[0].getFrom();
    from = getAddress(from);
    var body = message[0].getPlainBody();

    /* 通知本文作成 */
    var content = "";
    content += "件名:"+ subject + "\n";
    content += "From:" + from + "\n";
    content += "--------\n";
    content += body;
        
    /* 送信処理 */
    var options = 
        {
          "method" : "post",
          "payload" : "message=" + content,
          "headers" : {"Authorization" : "Bearer "+ token}
    };
    var ret = UrlFetchApp.fetch("https://notify-api.line.me/api/notify",options);
  }
}

function main() {
  var threads = GmailApp.search("is:unread label:inbox");
  var messages = GmailApp.getMessagesForThreads(threads);

  for(var i = messages.length - 1; 0 <= i; i--){
    /* Toの処理 */
    var to_all = messages[i][0].getTo();
    if(0 != to_all.length){
      var to_array = splitAddress(to_all);
      sendLine(to_array,messages[i]);
    }
    
    /* Ccの処理 */
    var cc_all = messages[i][0].getCc();
    if(0 != cc_all.length){
      var cc_array = splitAddress(cc_all);
      sendLine(cc_array,messages[i]);
    }

    /* Bccの処理 */
    var bcc_all = messages[i][0].getBcc();
    if(0 != bcc_all.length){
      var bcc_array = splitAddress(bcc_all);
      sendLine(bcc_array,messages[i]);
    }
  }
  
  /* 既読にする */
  for(var i = 0; i < threads.length; i++) {
    threads[i].markRead();
  }
  return;
}

関数ごとに個別に解説していきます。

メイン関数(main)

フローは以下の通りな単純なものです。
スライド2.PNG

コード(再掲)

function main() {
  var threads = GmailApp.search("is:unread label:inbox");
  var messages = GmailApp.getMessagesForThreads(threads);

  for(var i = messages.length - 1; 0 <= i; i--){
    /* Toの処理 */
    var to_all = messages[i][0].getTo();
    if(0 != to_all.length){
      var to_array = splitAddress(to_all);
      sendLine(to_array,messages[i]);
    }
    
    /* Ccの処理 */
    var cc_all = messages[i][0].getCc();
    if(0 != cc_all.length){
      var cc_array = splitAddress(cc_all);
      sendLine(cc_array,messages[i]);
    }

    /* Bccの処理 */
    var bcc_all = messages[i][0].getBcc();
    if(0 != bcc_all.length){
      var bcc_array = splitAddress(bcc_all);
      sendLine(bcc_array,messages[i]);
    }
  }
  
  /* 既読にする */
  for(var i = 0; i < threads.length; i++) {
    threads[i].markRead();
  }
  return;
}
var threads = GmailApp.search("is:unread label:inbox");
var messages = GmailApp.getMessagesForThreads(threads);

Gmailから、未読のスレッドを取得します。
そして、取得したスレッドのかたまりからメールを取得します。
取得したメールは二次元配列に格納されるため、以降の処理ではスレッド先頭のメールを以下の記述で取得しています。
messages[i][0]
(取得したスレッドi番目のうち、先頭のメール)

var to_all = messages[i][0].getTo();
if(0 != to_all.length){
  var to_array = splitAddress(to_all);
  sendLine(to_array,messages[i]);
}

メールからToに設定されているアドレスを取得します。
複数設定されていた場合は、カンマ区切りで取得されますので、
後述の処理で配列に格納し直します。

同様の処理をCcとBccに対しても実施します。

  /* 既読にする */
  for(var i = 0; i < threads.length; i++) {
    threads[i].markRead();
  }

メールの処理が終わったら、次回実行時に再送信しないように既読にしてしまいます。

宛先メールアドレスの分割関数(splitAddress、getAddress)

コード(再掲)
function splitAddress(address_all){
  var address = address_all.split(",");
  for(var i = 0; i < address.length; i++){
    address[i] = getAddress(address[i]);
  }

  return address;
}

function getAddress(address){
  var match_address = address.match(/[\w\-\.\+\_]+@[\w\-\.]+\.[a-zA-Z]+/);
  /* メールアドレスではない文字の場合は空白にする(後続処理にて送信しない) */
  if(null === match_address){
    match_address = " ";
  }
  var trim_address = match_address[0].trim().toLowerCase();

  return trim_address;
}

split(",")でメールアドレスを配列に格納しなおします。
ただし、getTogetCcgetBcc)は、送信者のアドレス帳での登録名を含んでいます。
例:"sample+kaikei@gmail.com" <会計用アドレス>
そのため、matchを使用してメールアドレスを抜き出します。
また、後続処理でアドレスの解析をしやすくするためにtoLowerCaseで大文字小文字を統一します。

LINEへの通知関数(sendLine)

コード(再掲)
function sendLine(address,message){
  for(var i = 0; i<address.length; i++){
    var token = getToken(address[i]);
    /* 対象外のアドレスの場合はスキップ */
    if("none" == token){
      continue;
    }

    var subject = message[0].getSubject();
    var from = message[0].getFrom();
    from = getAddress(from);
    var body = message[0].getPlainBody();

    /* 通知本文作成 */
    var content = "";
    content += "件名:"+ subject + "\n";
    content += "From:" + from + "\n";
    content += "--------\n";
    content += body;

    /* 送信処理 */
    var options = 
        {
          "method" : "post",
          "payload" : "message=" + content,
          "headers" : {"Authorization" : "Bearer "+ token}
    };
    var ret = UrlFetchApp.fetch("https://notify-api.line.me/api/notify",options);
  }
}

LINE NotifyのAPIを使用する際には宛先に対応するトークンが必要なので、
宛先に応じたトークンを取得します。
メールの宛先に無関係のアドレスが含まれる場合もあるので、その場合はcontinueで飛ばします。

その後は送信する内容を作成していくだけです。
getSubject 件名を取得。
getFrom 送信元を取得。宛先と同様にアドレスのみを切り出します。
getPlainBody 本文を取得。LINEでは装飾ができないため、HTMLタグを取り除いた本文を取得します。

その他に取得できる情報はリファレンスを参照ください。
リファレンス

    var options = 
        {
          "method" : "post",
          "payload" : "message=" + content,
          "headers" : {"Authorization" : "Bearer "+ token}
    };
    var ret = UrlFetchApp.fetch("https://notify-api.line.me/api/notify",options);

作成した内容や取得したトークンをセットしてAPIを実行します。

トークン取得処理(getToken)

コード(再掲)
function getToken(address){
  var token;
  var pr_adr_all = PropertiesService.getScriptProperties().getProperty('ADR_ALL');
  var pr_adr_kaikei = PropertiesService.getScriptProperties().getProperty('ADR_KAIKEI');
  var pr_adr_kikaku = PropertiesService.getScriptProperties().getProperty('ADR_KIKAKU');
  
  switch (address){
    case pr_adr_all:
      token = PropertiesService.getScriptProperties().getProperty('TOKEN_ALL');
      break;
    case pr_adr_kaikei:
      token = PropertiesService.getScriptProperties().getProperty('TOKEN_KAIKEI');
      break;
    case pr_adr_kikaku:
      token = PropertiesService.getScriptProperties().getProperty('TOKEN_KIKAKU');
      break;
    default:
      token = "none";
      break;
  }

  return token;
}

メールアドレス(sample+xxxxx@ gmail.com)に対応したLine Notifyのトークンを返します。
メールアドレスやトークンはソースコードにベタ書きしたくないですよね。
その場合はスクリプトプロパティに登録しましょう。

「ファイル」から「プロジェクトのプロパティ」を選択します。
20200202_144845.JPG
「スクリプトのプロパティ」タブで登録していきます。
「プロパティ」がソースコードで使用する名前、
「値」が実際の値です。
今回はメールアドレスやLINE Notifyで作成したトークンを登録していきましょう。
20200202_145343.JPG

登録したプロパティはソースコード上で
PropertiesService.getScriptProperties().getProperty('プロパティ名');
で使用することができます。

実行

正しく動作するか実行してみましょう。
GASを手動で実行する場合は、
実行する関数を選択して実行ボタンを押します。
20200202_145624.JPG
20200202_145639.JPG

初回の実行時、以下のダイアログが出ると思います。
Googleから許可を求められているものですので、許可しましょう。
20200202_145707.JPG
20200202_145839.JPG
20200202_145856.JPG
20200202_145932.JPG

定期的な自動実行

ここまでで、Gmailを解析してLINEに送信することが手動でできました。
あとは自動化です。
「編集」→「現在のプロジェクトのトリガー」を選択します。
20200202_150047.JPG

「自分のプロジェクト」から作成したスクリプトの「トリガー」を選択します。
20200202_150652.JPG

「トリガーを追加」を選択します。
トリガーを設定します。本仕組みでは以下を設定しています。

実行する関数を選択 実行するデプロイを選択 イベントのソースを選択 時間ベースのトリガーのタイプを選択 時間の間隔を選択(分)
main Head 時間主導型 分ベースのタイマー 1分おき

これで、1分おきにスクリプトを自動で実行できます。

以上で「Gmailに送ったメールをLINEに通知する」仕組みの完成です。
本記事では汎用的な処理しかしていませんが、

  • 内容をCSVに出力する
  • 送信時間制限をつける

など、団体に応じた処理を追加してみてくださいね。

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?