LINEとメールを連携させて、LINE未利用ユーザとLINEする方法(Ver.3)

【追記】最新版はこちら

また前回前々回からの更新です。

今回は次のような問題を解決しました。
・Google Apps Scriptの実行回数制限に引っかかる
2つのグループに対して本プログラムを適用していたある日、突然プログラムが動かなくなりました。
デバッグしたところ、Google Apps Scriptの制限に引っかかっていることが判明したのです。



こちらにもあるとおり、Gmail の読み取りと書き込み(送信以外)は20000回/日となっています。
毎分5件のスレッドを検索すると、1時間で300件、1日で7200回の読み取りを行う事になりますので、2グループに適用させてもギリギリ大丈夫な計算ですが、なぜか止まりました。
そこで今回は、動かすプログラムを1つにして、その中で条件分岐させることでこの制限に引っかかりにくくしてみました。
また、最初のGmailApp.searchでスターが付いていないメールが含まれるスレッドのみを検索するようにしました。




・1分間に複数の投稿が出来ない
これはfor文の中で、スレッドにおける最新のメールのみを対象としているためです。
そこで、スレッド内のすべてのメールを処理対象にすることにし、解決しました。
なお、スレッド内にはLINE未利用ユーザに転送されたメールが含まれているため、これを除外しています。






・自分が含まれているグループでないと利用できない
LINE Notifyは自分(管理者)が参加しているグループ、または自分(管理者)にしか通知できません(多分)。
そこでLINE Bot自身が投稿も行うように改良し、この問題を解決しました。
LINE Notifyの設定も不要となります。

更に、「②LINEグループの投稿をLINE未利用ユーザに転送」のプログラムも動かすプログラムを1つにして、その中で条件分岐させることにしました。
これによりLINE Botのアカウントを増やすことなく複数グループで運用することが出来ます。





・グループに動画が投稿された際、Google Driveへのアップロードに成功するものと失敗するものがある
容量のせいか、動画に関する処理は失敗していることが多いです。
処理が失敗するとメール送信自体が行われないのでLINE未利用ユーザは動画の存在すら認識できません。
そこで、最初は動画が投稿されたことのみを通知し、その後アップロードに成功したらURLを再通知するよう、処理を変更しました。




①LINE未利用ユーザがLINEグループに投稿


こちらのページを参考に、コードを以下のように変更しました。








アクセストークンは、「②LINEグループの投稿をLINE未利用ユーザに転送」で使用していたのと同じものです。
また、複数のグループに対してメアドを使い分けて投稿することが可能になっています。
これはGmailのエイリアス機能のおかげで実現できたもので、本来のユーザ名の後ろに「+任意の文字列」を付けても同じアカウントで受信できる優れものです。
このエイリアスによって投稿先のグループを変更しているのが、27行目のswitch文です。
その部分では投稿先のグループを指定するための「Group ID」が必要になります。
この「Group ID」を特定するための処理は、「②LINEグループの投稿をLINE未利用ユーザに転送」の方に組み込む必要があるので、そちらで解説します。

pushメソッドについては送信テキストと送信先を引数として指定できるように変更した以外はほぼそのままで使用することが出来ました。






  1. // LINE Developersに書いてあるChannel Access Token
  2. var access_token = "ここに入力"
  3. function getMail(){
  4.   
  5.   //受信スレッドを5件取得
  6.   var myThreads = GmailApp.search("-is:starred", 0, 5);
  7.   //スレッド情報を取得し配列に格納
  8.   var myMessages = GmailApp.getMessagesForThreads(myThreads);
  9.   
  10.   //古いスレッドから確認
  11.   for(var i=myMessages.length-1; 0<=i; i--){
  12.     //古いメールから確認
  13.     for(var j in myMessages[i]){
  14.         //スターがついていないものを処理
  15.         if(!myMessages[i][j].isStarred()){
  16.         
  17.          var strDate = myMessages[i][j].getDate();
  18.          var strSubject = myMessages[i][j].getSubject();
  19.          var strMessage = myMessages[i][j].getBody().slice(0,1000);
  20.          var from = myMessages[i][j].getFrom();
  21.          var to = myMessages[i][j].getTo();
  22.         
  23.          // スレッド内の対象外メール(転送先メールアドレス以外から来ているメール)
  24.          if(from!="転送先メールアドレス"){
  25.            myMessages[i][j].star(); //処理済みフラグとしてスターをつける
  26.            strMessage = ""; //本文を消す
  27.            attachments = ""; //添付ファイルを消す
  28.          }
  29.         
  30.          //指定アドレス毎にトークン=送信先グループを変更
  31.          switch(to){
  32.             case 'test+A@gmail.com':
  33.              var GID = "ここに入力";
  34.              break;
  35.             case 'test+B@gmail.com':
  36.              var GID = "ここに入力";
  37.              break;
  38.             // スレッド内の対象外メール(宛先が自分以外のメール)
  39.             default:
  40.              myMessages[i][j].star(); //処理済みフラグとしてスターをつける
  41.              strMessage = null; //本文を消す
  42.              attachments = null; //添付ファイルを消す
  43.              break;
  44.          }
  45.     
  46.          //本文がある場合
  47.          if (0<strMessage.length){
  48.             //LINEにメッセージを送信する関数にメッセージ、グループIDをセット
  49.             push(strMessage,GID);
  50.          }
  51.         
  52.          //添付ファイル
  53.          var attachments = myMessages[i][j].getAttachments(); //配列で帰ってくる
  54.         
  55.          //添付ファイルがある場合
  56.          if (0<attachments.length){
  57.             //最初の添付ファイルのみ利用
  58.             var attachment = attachments[0];
  59.             //添付ファイルをGoogle Driveへアップし、そのURLを送信
  60.             var folder = DriveApp.getFolderById('ここに入力');
  61.             var file = folder.createFile(attachment);
  62.             push(file.getUrl(),GID);
  63.          }
  64.         }
  65.         
  66.         //処理済みフラグとしてスターをつける
  67.         myMessages[i][j].star();
  68.     }
  69.   }
  70. }
  71. //実際にメッセージを送信する関数を作成します。
  72. function push(text, GID) {
  73. //メッセージを送信(push)する時に必要なurlでこれは、皆同じなので、修正する必要ありません。
  74. //この関数は全て基本コピペで大丈夫です。
  75.   var url = "https://api.line.me/v2/bot/message/push";
  76.   var headers = {
  77.     "Content-Type" : "application/json; charset=UTF-8",
  78.     'Authorization': 'Bearer ' + access_token,
  79.   };
  80.   //toのところにメッセージを送信したいユーザーのIDを指定します。(toは最初の方で自分のIDを指定したので、linebotから自分に送信されることになります。)
  81.   //textの部分は、送信されるメッセージが入ります。getMailという関数で定義したメッセージがここに入ります。
  82.   var postData = {
  83.     "to" : GID,
  84.     "messages" : [
  85.       {
  86.         'type':'text',
  87.         'text': text,
  88.       }
  89.     ]
  90.   };
  91.   var options = {
  92.     "method" : "post",
  93.     "headers" : headers,
  94.     "payload" : JSON.stringify(postData)
  95.   };
  96.   return UrlFetchApp.fetch(url, options);
  97. }




②LINEグループの投稿をLINE未利用ユーザに転送



こちらにはグループIDの取得メソッドを追加し、以下のように変更しました。
グループIDの取得についてはこちらを参考にしました。



この処理により、グループに投稿がある度にスプレッドシートにグループIDと時刻が記録されていきます。
時刻からグループを推測して、「①LINE未利用ユーザがLINEグループに投稿」のcase文のGroup IDを指定してください。
当方ではグループでしか使用せず、個人間では使用しないためこのようにしていますが、個人の場合も「Group ID」を「User ID」に読み替えれば同様のことが出来ると思われます。
なお、65~69行目はGroup IDを取得したいときのみ実行させ、それ以外の時はコメントアウトしてもOKです。

その先の部分では、取得したGroup IDに応じて返信先と件名を設定しています。
replyToで返信先を設定することで、届いたLINE通知メールに返信するだけでそのグループに返信されるようになり、誤爆防止になると思います。
GmailAppクラスを利用する場合はreplyToではなくFromを変更する手もあります。ただし前回(Ver.2)で書いた通り絵文字が文字化けするので私はこちらの方法を用いました。





  1. var ACCESS_TOKEN = 'アクセストークン';
  2. var url = 'https://api.line.me/v2/bot/message/reply'; // 応答メッセージ用のAPI URL
  3. function doPost(e) {
  4.   
  5.   // メッセージ返信
  6.   replyMessage(e);
  7.   return ContentService.createTextOutput(JSON.stringify({ content: 'post ok' })).setMimeType(ContentService.MimeType.JSON);
  8. };
  9. var replyMessage = function (e) {
  10.   var line = JSON.parse(e.postData.contents).events[0]; //メッセージ
  11.   switch(line.message.type){
  12.     // テキストメッセージ
  13.     case 'text':
  14.       var strBody = line.message.text;
  15.       break;
  16.     // 画像
  17.     case 'image':
  18.       var blob_attach = get_line_content(line.message.id);
  19.       var folder = DriveApp.getFolderById('フォルダID');
  20.       var file = folder.createFile(blob_attach);
  21.       var strBody = '画像です:' + file.getUrl();
  22.       break;
  23.     //動画
  24.     case 'video':
  25.       var strBody = '動画です。アップロードに成功した場合URLが通知されます。失敗した場合通知されません。';
  26.       break;
  27.     //音声
  28.     case 'audio':
  29.       var blob = get_line_content(line.message.id);
  30.       var folder = DriveApp.getFolderById('フォルダID');
  31.       var file = folder.createFile(blob);
  32.       var strBody = '音声です:' + file.getUrl();
  33.       break;
  34.     //ファイル
  35.     case 'file':
  36.       var blob = get_line_content(line.message.id);
  37.       var folder = DriveApp.getFolderById('フォルダID');
  38.       var file = folder.createFile(blob);
  39.       var strBody = 'ファイルです:' + file.getUrl();
  40.       break;
  41.     //スタンプ
  42.     case 'sticker':
  43.       var blob_attach = UrlFetchApp.fetch('https://stickershop.line-scdn.net/stickershop/v1/sticker/' + line.message.stickerId + '/android/sticker.png').getBlob();
  44.       var strBody = 'スタンプです:' + 'https://stickershop.line-scdn.net/stickershop/v1/sticker/' + line.message.stickerId + '/android/sticker.png';
  45.       break;
  46.   }
  47.   
  48.   var userId = JSON.parse(e.postData.contents).events[0].source.userId;
  49.   var username = getUsername(userId);
  50.   if (username == 0){
  51.     username = '不明なユーザ';
  52.   }
  53.   
  54.   //グループIDの取得
  55.   var GID = line.source.groupId;
  56.   
  57.   
  58.   // スプレッドシートへグループIDを書き込み;
  59.   var spreadsheet = SpreadsheetApp.openById('スプレッドシートのID');
  60.   var sheet = spreadsheet.getActiveSheet();
  61.   var now = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss');
  62.   sheet.appendRow([GID, now]);
  63.   
  64.   // 返信先と件名の設定
  65.   switch(GID){
  66.     case 'グループID A':
  67.       var replyTo = "test+A@gmail.com";
  68.       var strSubject="グループA LINE転送";
  69.       break;
  70.     case 'グループID B':
  71.       var replyto = "test+B@gmail.com";
  72.       var strSubject="グループB LINE転送";
  73.       break;
  74.   }
  75.   var strTo="test@ezweb.ne.jp";
  76.   
  77.   MailApp.sendEmail(strTo , strSubject, "[" + username + "]" + strBody, {replyTo:replyto, attachments: blob_attach});
  78.   
  79.   //動画の場合の追加処理(アップロードとURL取得)
  80.   if (line.message.type == 'video'){
  81.     var blob = get_line_content(line.message.id);
  82.     var folder = DriveApp.getFolderById('フォルダID');
  83.     var file = folder.createFile(blob);
  84.     var strBody = '動画URLです:' + file.getUrl();
  85.     MailApp.sendEmail(strTo , strSubject, "[" + username + "]" + strBody, {replyTo:replyto, attachments: blob_attach});
  86.   }
  87.   
  88. }
  89. //Userの表示名取得
  90. function getUsername(userId) {
  91.   var url = 'https://api.line.me/v2/bot/profile/' + userId;
  92.   try {//エラー処理
  93.     var response = UrlFetchApp.fetch(url, {
  94.       'headers': {
  95.         'Authorization': 'Bearer ' + ACCESS_TOKEN
  96.       }
  97.     })
  98.     }catch(e){ //表示名を取得できなかったら
  99.       return 0;
  100.     }
  101.   return JSON.parse(response.getContentText()).displayName;
  102. }
  103. // Get content of LINE
  104. function get_line_content(message_id) {
  105.   var headers = {
  106.     'Authorization': 'Bearer ' + ACCESS_TOKEN
  107.   };
  108.   var options = {
  109.     'headers': headers
  110.   };
  111.   var url = 'https://api.line.me/v2/bot/message/' + message_id + '/content';
  112.   var blob = UrlFetchApp.fetch(url, options);
  113.   return blob;
  114. }






これで自分(管理者)が含まれないグループでも、アカウント1つ招待してもらい、条件分岐の設定を入れれば運用が可能となりました。
ただ、Gmailアカウント側から全部会話が見えてしまうので、その前提はLINE未利用ユーザに伝えるべきではありますね。
また、LINE Notifyによる投稿に比べ、Botアカウントは自由にアイコンや名前を設定できるので、それも分かりやすくて良いですね。

コメント

このブログの人気の投稿

湾岸ミッドナイトの聖地についてまとめてみる

MakeMKVでリッピングに失敗する時の対処方法

中華タブレットのバッテリー交換に挑戦した話