自動化厨のプログラミングメモブログ │ CODE:LIFE

Python/ExcelVBA/JavaScript/Raspberry Piなどで色んなことを自動化

LINEボットにメッセージを送ったら特定メンバーに転送する アナウンスボットをGASだけで作ってみた

会社の同僚からこんなのできるー?とお題を頂いたので2時間くらいで作ってみました。

MQTTみたいだなと思ったので用語をそっちに合わせてみた。

f:id:maru0014:20200930231459p:plain

2020/10/03 追記
GASライブラリ利用パターンもやってみました。 codelife.cafe

動作概要

①LINE botに対してメッセージを送信するとGoogleスプレッドシートの通知先シートに登録されているユーザに対して自動で転送します。
※以下の画像は自分自身も通知先なのでメッセージが届いちゃってます。

f:id:maru0014:20200930231530p:plain
送信者名と本文が通知先のユーザに転送されます

通知先はスプレッドシートで管理しています。
※ユーザIDはLINEアプリから見える方ではなくWebhookで飛んできた長い文字列です。手動登録できません。

f:id:maru0014:20200930231602p:plain
通知先はスプレッドシートで管理


②全ての投稿はログシートに記録されます

日時はタイムスタンプを変換するのが面倒だったのでGAS側で new Date しましたごめんなさい。

f:id:maru0014:20200930231809p:plain
ログ シートに全て記録が残ります


③通知先として登録してもらうには設定シートの合言葉と一致するメッセージを送信する

カンマ区切りで複数設定可能。「登録」だと簡単すぎるのでもうちょっと違う合言葉作ったらいいと思います。

f:id:maru0014:20200930231838p:plain
GAS側で配列に変換して一致するメッセージかどうかを判定しています

構築手順

取り急ぎ箇条書きレベルで書いておきます。また後日画像いれつつ解説を追記します。

LINE Messaging API の利用方法はここが分かりやすく書いてありましたので参照ください。

細かい仕様は公式のドキュメントがめちゃ分かりやすくて感動した。

first-contact.jp

1. LINE botを作る

f:id:maru0014:20201001210249p:plain
作るのはMessaging API

f:id:maru0014:20201001210307p:plain
こんなかんじで設定

https://cdn-ak.f.st-hatena.com/images/fotolife/m/maru0014/20201001/20201001211812_original.gif
Messaging API設定からチャンネルアクセストークンを発行しておく

2. スプレッドシートを作成

https://cdn-ak.f.st-hatena.com/images/fotolife/m/maru0014/20201001/20201001211542_original.gif
ログ、設定、通知先 の3つのシートを作る。中身は空でOK。

f:id:maru0014:20201001210355p:plain
設定シート!B2に登録用フレーズをカンマ区切りで設定

3. GAS設定と公開

f:id:maru0014:20201001210412p:plain
コード.jsに 後述のコード全文を貼り付け

f:id:maru0014:20201001210441p:plain
プロジェクトのプロパティからスクリプトのプロパティに「token」としてチャンネルアクセストークンをセット

f:id:maru0014:20201001210458p:plain
公開>ウェブアプリケーションとして導入>Who has access to the app:Anyone, even anonymous で公開

4. 利用者にbotの友達登録用のQRコードを配る

f:id:maru0014:20201001210515p:plain
QRコードやIDを共有して友達登録してもらう

https://cdn-ak.f.st-hatena.com/images/fotolife/m/maru0014/20201001/20201001211034_original.gif
通知先として登録する人にはbotに合言葉を投げてもらう

コード全文

エラー処理とかちゃんと出来てないので実用するにはもうちょっと書き直しが必要。

GASライブラリないのかなー。

const ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty("token");

const LOG_SHEET_NAME = 'ログ';
const SETTING_SHEET_NAME = '設定';
const SUBSCRIBER_SHEET_NAME = '通知先';

const ss = SpreadsheetApp.getActiveSpreadsheet();
const logSheet = ss.getSheetByName(LOG_SHEET_NAME);
const settingSheet = ss.getSheetByName(SETTING_SHEET_NAME);
const subscriberSheet = ss.getSheetByName(SUBSCRIBER_SHEET_NAME);

function doPost(e) {
  
  const event = JSON.parse(e.postData.contents).events[0];
  
  const date = Utilities.formatDate(new Date(), 'JST', 'yyyy/MM/dd HH:mm:ss');
  const userId = event.source.userId;
  const userProfile = getLineUserName(userId);
  const userName = userProfile.displayName;
  const userMessage = event.message.text;
  const rowContents = [date,userId,userName,userMessage];
  logSheet.appendRow(rowContents);
  
  // 設定シートのキーワードを含むかどうかで動作分岐
  const keywords = getKeywords();
  const replyMessage = keywords.includes(userMessage) ? register(userId,userName) : push(userMessage,userName);

  const replyToken = event.replyToken;
  
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': replyMessage,
      }],
    }),
    });
  
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

function register(userId,userName){
  const subscriber = getSubscriber();
  if(!subscriber.includes(userId)){
    
    const rowContents = [userId,userName];
    subscriberSheet.appendRow(rowContents);
    
    return '登録完了';  
    
  } else {
    
    return '既に登録済みです';  
    
  }
}

function push(userMessage,userName){
  const subscriber = getSubscriber();
  if(subscriber.length === 0){
    return '通知先が登録されていません';
  }
  
  try{
       UrlFetchApp.fetch('https://api.line.me/v2/bot/message/multicast', {
         'headers': {
           'Content-Type': 'application/json; charset=UTF-8',
           'Authorization': 'Bearer ' + ACCESS_TOKEN,
         },
         'method': 'post',
         'payload': JSON.stringify({
           'to': subscriber,
           'messages': [{
             'type': 'text',
             'text': `送信者: ${userName}\n本文: ${userMessage}`,
                        }],
         }),
       });
      return '送信完了';

  } catch(e) {
    console.error(e);
    return '送信に失敗しました';
  }
}

function getLineUserName(userId){
  const response = UrlFetchApp.fetch(`https://api.line.me/v2/bot/profile/${userId}`, {
    'headers': {
      "Content-Type" : "application/json charset=UTF-8",
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'get'
    });
  
  return JSON.parse(response);
}

function getKeywords(){
  const keywords = settingSheet.getRange(2, 2).getValue().split(',');
  return keywords;
}

function getSubscriber(){
  
  const subscriberSheetLastRow = subscriberSheet.getLastRow();
  let subscriber = new Array();
  
  if(subscriberSheetLastRow > 1){
    subscriber = subscriberSheet.getRange(2, 1, subscriberSheetLastRow - 1,1).getValues().flat();
  }
  
  return subscriber;

}