御岳登 ~ GAS×LINE APIで販促ツールを作ってみる

GAS×LINE APIで作ってきたものを載せています。

LINE API × GAS でイベント情報を自動投稿

1624930883795.png

概要

LINE公式アカウント(旧LINE@)を使用しているお店のイベント情報を自動投稿できるようにする。

仕組みはGoogleカレンダーの予定をGASで取得し、指定の時間になったら翌日の予定(イベント情報)を、自動的に投稿する。といった流れ。

経緯

これまで作成した"気象に応じた"販促の自動投稿に手応えがあったため、「GASなら予定の自動投稿も簡単にできるだろう」と思い、作成してみることに。

使用したもの

・LINE Messaging APIGoogle Apps Script (GAS) ・Googleカレンダー

Googleカレンダーの予定日取得について

GASで予定日を取得するため以下3つの変数を宣言する。

  const calendar_ID = "×××××××××××××××××@group.calendar.google.com"; 
  const calendar = CalendarApp.getCalendarById(calendar_ID);
  const events = calendar.getEventsForDay("取得したい日にち");

カレンダーIDは、Googleカレンダー → 取得するカレンダーの"設定" → カレンダーの統合 にある。

なお取得するカレンダーには以下の設定が必要 ・アクセス権限の「一般公開して誰でも利用できるようにする」にチェック ・「特定のユーザーとの共有」にGASを作成するアカウントを入れる

取得可能な設定になると上記の events に、指定した "取得したい日にち" の予定が格納される(といってもプロパティを含まないと予定は取得できない)

予定を取得するには、

  var msgContent = "";
  for(const event of events){
    //var desc = event.getDescription(); /*予定の説明。今回はメッセージの体裁を考え保留に。*/
    var scheduleTitle = event.getTitle() //予定のタイトル
    var startTime = Utilities.formatDate(event.getStartTime(), 'JST', 'HH:mm'); //予定の開始日(終日のときは00:00)
    var endTime = Utilities.formatDate(event.getEndTime(), 'JST', 'HH:mm'); //予定の終了日(終日のときは00:00)
    var time = event.isAllDayEvent() ? "" : "(" + startTime + " ~ " + endTime + ")"; //開始時間~終了時間のフォーマットを生成(終日のときは空白)
    msgContent += "\n◯" + scheduleTitle + time + "\n"; //こんな形で予定を表示させる
  }

のように events の中身をfor文でまわしながら msgContent という変数に格納していく。



イベント情報をLINEに投稿

上記で使用した変数 msgContent に値が入っていれば投稿処理をおこなう。 ( headerfooter の変数については完成形を参照。)

  //イベント情報が格納された時のみ投稿処理
  if(msgContent != ""){
    var postData = {
      'messages': [{
      'type': 'text',
      'text': header + msgContent + footer //ここではイベント情報に上・下部のメッセージ(header,footer)の変数を挟む *完成形を参照
      }],
    };
    broadcast_fetch_data(postData);
  }



function broadcast_fetch_data(postData) {
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + "LINEのアクセストークン"
    },
    "payload": JSON.stringify(postData)
  };
  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/broadcast", options);
}

あとはGASのトリガーで毎日一定時間に作動させれば無事完了。

完成形はこちら

上記で説明した構文をつなげるのと、ついでにスタンプをつけてみる。(こちらのサイトよりIDなどをコピーして使用)

function getEvent(){
  //明日の日付
  var tomorrow = new Date();
  tomorrow.setDate(tomorrow.getDate() + 1);
  var tomorrowformat = Utilities.formatDate(tomorrow, 'Asia/Tokyo', 'M/d');

  //カレンダー  
  const calendar_ID = "×××××××××××××××××@group.calendar.google.com"; 
  const calendar = CalendarApp.getCalendarById(calendar_ID);
  const events = calendar.getEventsForDay(tomorrow);

  //メッセージ 
  var msgContent = "" //ここにイベント情報を格納
  var header = "こんばんは🙂\n\n明日(" + tomorrowformat + ")のイベントのお知らせです💡\n";
  var footer = "\nお客様のご来店お待ちしております。"

  for(const event of events){
    //var desc = event.getDescription(); /*予定の説明、今回はメッセージの体裁を考え保留に。*/
    var scheduleTitle = event.getTitle() //予定のタイトル
    var startTime = Utilities.formatDate(event.getStartTime(), 'JST', 'HH:mm'); //予定の開始日(終日のときは00:00)
    var endTime = Utilities.formatDate(event.getEndTime(), 'JST', 'HH:mm'); //予定の終了日(終日のときは00:00)
    var time = event.isAllDayEvent() ? "" : "(" + startTime + " ~ " + endTime + ")"; //開始時間~終了時間のフォーマットを生成(終日のときは空白)
    msgContent += "\n◯" + scheduleTitle + time + "\n"; //こんな形で予定を表示させる
  }

  //イベント情報が格納された時のみ投稿処理
  if(msgContent != ""){
    var postData = {
      'messages': [{
      'type': 'text',
      'text': header + msgContent + footer
      },{
        "type": "sticker",
        "packageId": "1070",
        "stickerId": "17839"
      }],
    };
    broadcast_fetch_data(postData);
  }
}

function broadcast_fetch_data(postData) {
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + "LINEのアクセストークン"
    },
    "payload": JSON.stringify(postData)
  };
  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/broadcast", options);
}

上記の getEvent() を実行させると冒頭の画像のようなメッセージが投稿される。



応用編(今後の課題など)

  • イベント情報に対応した画像投稿 Googleカレンダーには添付ファイルの項目があるものの、GASでは取得できない(らしい)ので、説明文に画像のURLを貼り付けて、それをLINE APIで使用するといったやり方で運用中。説明文なので簡単に編集できてしまうため代替策を試案中。

  • LINE botでイベント情報を返す 特定のメッセージに対して単純に上記の getEvent() を流せばOK。

  • 複数のイベント情報の処理 例えば営業時間を流したいときや、セール情報を流したいときなど幾つか状況が考えられるが、個々にカレンダーを作成して適宜処理をおこなうことで対応。

  • 投稿のタイミングについて 「週末のイベント情報は、前日ではなく週始めにも投稿したい」という声を頂いたので、該当のカレンダーは5日前と1日前に投稿するという仕様に変更。こういった様に「投稿する〇日前に投稿する」という設定機能は必要なのかなと考え中。

    備考

    Googleカレンダーにイベント情報などを作成するだけで自動的に販促投稿するシステムはそこそこ需要があると思いつつ、上記で示したような個々の仕様(画像の設定、投稿するタイミングの拡張性など)が今後の課題。



参考にした本



LINE API × GASで猛暑日クーポンを自動投稿&設定機能をつくる

1624593122625.png

概要

前回おこなった 雨の日セール の第二弾。 前回と同じく気象庁のデータをGASで取得し、対象地域の翌日の最高気温が30℃以上なら、LINE公式アカウント(旧LINE@)で猛暑日クーポンを投稿する。といった流れ。更に今回は投稿の基準となる"しきい値"を設定できるようにする。

前回より

前回の 雨の日セール をテスト運用中、「しきい値を変更できたら有難い」という声を頂いたので、設定機能をつくることに。といっても1からUIをつくる時間も技術も無いのと、GASを使用している事もありスプレッドシートを採用し、しきい値を設定できるようにするのと、ついでに自動投稿を一旦中止する仕組みをつくる。

使用したもの

・LINE Messaging APIGoogle Apps Script (GAS) ・気象庁ホームページ ・Google SpreadSheet ←new!

気象データの取得について

取得する方法は前回の 雨の日セール と同じ。

https://www.jma.go.jp/bosai/forecast/data/forecast/(area_code).json

(area_code)の所に取得したい地域のコードを入れるとjsonでデータが表示される。

各地域のコード … 気象庁 天気予報より都道府県を指定し遷移後のURL末端にある6ケタの数字

前回と同様に山口県の気象データを取得する。

  const response = UrlFetchApp.fetch(`https://www.jma.go.jp/bosai/forecast/data/forecast/350000.json`)
  const data = JSON.parse(response)



最高気温30℃以上の判定

元データのサイトを見ると、複数の地域の値があるので前回と同じく中部のデータのみ取得。

上記で取得したデータのうち中部の最高気温の値はここに格納されている。

data[0].timeSeries[1].areas[1].temps



取得する時間によって格納される値の数が変動する(今日の過ぎた時間帯の値は入らない)ので、今回は格納されている数によって取得する値を変える方法を採用。

  var maxtemp = getMaxtemp(data); //下の関数を使用

  function getMaxtemp(data){
    var tempArray = data[0].timeSeries[2].areas[1].temps
    if(tempArray.length==4){
      return data[0].timeSeries[2].areas[1].temps[3]
    }else if(tempArray.length ==3){
      return data[0].timeSeries[2].areas[1].temps[2]
    }else if(tempArray.length == 2){
      return data[0].timeSeries[2].areas[1].temps[1]
    }
  }





本題の判定へ。 最高気温が30℃以上なら投稿処理を実行。 下で使っている変数(hotLine)には後で設定するしきい値を格納する。

/*中略*/
var hotLine = "30" //ここに後述する設定値を格納する

if(maxtemp >= hotLine){
    var tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);
    var tomorrowformat = Utilities.formatDate(tomorrow, 'Asia/Tokyo', 'M/d');
    var title = '【明日は最高気温' + maxtemp + '℃の予報☀ 限定クーポン配信】';
    var header = 'こんばんは\uDBC0\uDC02\n\n'
    var msg = title + '\n\n' + header + '明日' + tomorrowformat + 'の山口は最高気温が' + maxtemp + '℃の予報となっています。お体にお気をつけ下さいませ。\n\n↓猛暑限定クーポンお知らせ↓';
    var imageURL = '販促画像リンク';
    var postData = {
      'messages': [{
      'type': 'text',
      'text': msg
      },{
        "type": "flex",
        "altText": title,
        "contents": {
          "type": "bubble",
          "hero": {
            "type": "image",
            "url": imageURL,
            "size": "full",
            "aspectRatio": "1:1.035",
            "aspectMode": "cover"
          }
        }
      }],
    };
    broadcast_fetch_data(postData);
 }



function broadcast_fetch_data(postData) {
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + "LINEのアクセストークン"
    },
    "payload": JSON.stringify(postData)
  };
  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/broadcast", options);
}

あとはGASのトリガーで毎日一定時間に作動させれば無事完了。



設定機能をつくる

前述したようにスプレッドシートしきい値を設定できるようにするのと、ついでに自動投稿を一旦中止する仕組みをつくる。

仕組みは単純で、  ・スプレッドシートしきい値を入力するセルを作成。  ・GASにてセルの値を取得し、しきい値として使用する。  ・セルが空白の場合は投稿を中止するようにする。

スプレッドシートに以下の形で作成。 SharedScreenshot.jpg

あと共有して使用するため、値以外のセルは保護&余計な値を入れないよう入力規則を設定。 SharedScreenshot.jpg

念のため(?)100℃まで入力可能に。

しきい値を取得する

GASにてスプレッドシートの値を読み込む。 他の機能の設定値も取得したいので、汎用的な関数を作成。



//値を取得したい項目名を引数に入れる
function GetSettingValue(target) {
  var spreadsheet = SpreadsheetApp.openById("スプレッドシートのId");
  var sheet = spreadsheet.getSheetByName("設定"); //シート名
  var textFinder = sheet.createTextFinder("^" + target + "$").useRegularExpression(true); //検索メソッド。ここでは正規表現で完全一致の条件を指定。
  var ranges = textFinder.findAll();
  
  if(ranges.length >0){
    for (var i = 0; i < ranges.length; i++) {
      // 念のため一致した項目がA列にあるか判定
      if(ranges[i].getColumn() == "1"){
        var index = ranges[i].getRowIndex();
        // セルが空白の場合はnullを返す
        if(sheet.getRange(index, 2).isBlank()) return null
        // セルが空白ではない場合は値を返す
        return sheet.getRange(index, 2).getValue();
      }
    }
  }
}



で、先述した処理に組み込むと以下の形に。 しきい値が空欄の時にはnullを返すので、if文に条件を追加し投稿処理から除外させる。

/*中略*/
  var hotLine = GetSettingValue("最高気温条件値");

  //明日の最高気温がしきい値以上かつnull以外のとき
  if((maxtemp >= hotLine)&&(hotLine != null)){
    /*前述の投稿処理*/
  }



備考

データの置き場所兼UIとしてスプレッドシートを採用したものの、 最終的には ・UI->LINEのトーク画面(一応完成済) ・データの置き場所-> Firebase が最適なのかなと日々模索中・・。

参考にした本



雨の日セールをLINEへ自動投稿

1.jpg

概要

気象庁のデータをGASで取得し、対象地域の翌日の降水確率が50%以上なら、LINE公式アカウント(旧LINE@)で雨の日セールを投稿する。といった流れ。

経緯

今年4月にLINEのMessaging APIに触る機会があり、それ以降沼にハマってしまい、色々試しているうちにLINE公式アカウントの利用者が面倒であろう販促投稿を自動で出来ないか検証。興味をもってくれた方がいたので、"雨の日セール"の自動投稿の仕組みをつくってみることに。

使用したもの

・LINE Messaging APIGoogle Apps Script (GAS) ・気象庁ホームページ

気象データの取得について

天気の情報 = OpenWeatherMap と思いつつ、いろいろ調べているうちに、どうやら気象庁のデータがJSONで取得できるようになったみたいなので、そちらを試してみることに。



気象庁データのJSON取得

まずはデータを取得するURLを調べてみる。

https://www.jma.go.jp/bosai/forecast/data/forecast/(area_code).json

(area_code)の所に取得したい地域のコードを入れるとjsonでデータが表示される。

各地域のコード … 気象庁 天気予報より都道府県を指定し遷移後のURL末端にある6ケタの数字

データを取得する方法がわかったので、GASで当該のデータを取得してみる。(下コードは山口県の気象データを取得)

  const response = UrlFetchApp.fetch(`https://www.jma.go.jp/bosai/forecast/data/forecast/350000.json`)
  const data = JSON.parse(response)





降水確率50%以上の判定

元データのサイトを見ると、複数の地域の降水確率があるので今回はピンポイントに中部のデータのみ対象。

上記で取得したデータのうち中部の降水確率はここに格納されている。

data[0].timeSeries[1].areas[1].pops

しかし取得する時間によって格納される値の数が変動する(今日の過ぎた時間帯の値は入らない)ので、発表された時刻を指定(今回は17時発表を採用)することに。

  var date = new Date();
  var datetime = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy-MM-dd') + 'T17:00:00+09:00';
  if(datetime != data[0].reportDatetime) return;

本題の判定へ。 3つの時間帯でそれぞれ降水確率が50%以上なら値を格納。(夜の時間帯は判定から除外するため、すでに値が入っている場合のみ格納。)

/*中略*/
var rainy_msg = rainychk(data[0].timeSeries[1].areas[1].pops,'50')
// 値が入っていれば投稿処理へ

function rainychk(rainyArray, value){
 var msg = ''
 var rainymsg = ''
 var i = rainyArray.length - 1
 // valueの値以上になったらメッセージ文を代入
 // 夜の時間帯は営業時間から外れるため判定から除外
 if(rainyArray[i-3] >= value) rainymsg += ' 朝:' + rainyArray[i-3] + '%'
 if(rainyArray[i-2] >= value) rainymsg += ' 昼前:' + rainyArray[i-2] + '%'
 if(rainyArray[i-1] >= value) rainymsg += ' 昼間:' + rainyArray[i-1] + '%'
 if((rainyArray[i] >= value)&&(rainymsg != "")) rainymsg += ' 夜:' + rainyArray[i] + '%'
 if(rainymsg != '') msg = '降水確率は' + rainymsg + ' になっています。'
 return msg;
}





LINE公式アカウントへ投稿

上記の判定処理を通したのち投稿処理へ

    /*中略*/
    var tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);
    var tomorrowformat = Utilities.formatDate(tomorrow, 'Asia/Tokyo', 'M/d');
    var title = '【明日は雨の予報☂ 限定クーポン配信】';
    var header = 'こんばんは\uDBC0\uDC02\n\n'
    var footer = '\n\nお出かけの際はお気をつけ下さいませ。\n\n↓雨の日限定クーポンお知らせ↓'
    var msg = title + '\n\n' + header + '明日' + tomorrowformat + 'の山口は雨の予報です☂\n' + rainy_msg + footer;
    var imageURL = '販促画像リンク';
    var postData = {
      'messages': [{
      'type': 'text',
      'text': msg
      },{
        "type": "flex",
        "altText": title,
        "contents": {
          "type": "bubble",
          "hero": {
            "type": "image",
            "url": imageURL,
            "size": "full",
            "aspectRatio": "1:1.035",
            "aspectMode": "cover"
          }
        }
      }],
    };
    broadcast_fetch_data(postData);



function broadcast_fetch_data(postData) {
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + "LINEのアクセストークン"
    },
    "payload": JSON.stringify(postData)
  };
  UrlFetchApp.fetch("https://api.line.me/v2/bot/message/broadcast", options);
}



あとはGASのトリガーで毎日一定時間に作動させれば無事完了。



備考

今回作った仕組みを動かしてみて約1ヶ月。今のところ目立った不具合はないものの、はっきりとしたAPIとはいえないので、若干様子をみつつ不都合が発生した場合にOpenWeatherMapのデータも取得するような2重構造を考え中。

あとはJSON内にweatherCodesというものを使って簡潔な天気(晴れ、曇、雨のち晴れなど)が取れないか試したものの、まだまだ把握できないコードがいくつか出てきたので今回は採用を見送り。 誰かまとめてくれないかなぁ

参考にした本