98
72

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

【GAS】推しキャラを模したLINEbotを作り、ほんの少し孤独を埋める

Posted at

#はじめに
「出先で雨に降られ、濡れがち」
「物忘れ多すぎ」
「部屋に籠ってばっかで寂しい」
ここ最近の個人的問題を一気に解決すべく、GAS(GoogleAppsScript)を用いてLINEbot作成しました。

#作ったもの

「天気予報を朝にプッシュ通知で知らせる」「指定時間にリマインド」の二つの機能を、「好きなキャラとのLINE風」に実装することでウキウキな気分になれます。
ちなみに私の好きなキャラは、シャニマスの黛冬優子さん。
黛 冬優子(まゆずみ ふゆこ)|アイドルマスターシャイニーカラーズ(シャニマス)
S__70877207.jpg
S__70877209.jpgS__70877210.jpg

#実装
##1. 準備
Google Apps Scriptでオウム返しLINE Botを作る。
こちらの記事を参考にbot用のLINEアカウントを作成し、自分の発言をオウム返しするプログラムを実装してみましょう。
GASであれば、サーバーを用意せず無料でLINEbotを動かせます。
##2. 天気予報の通知
LINE BOT お天気通知くん
こちらの記事を参考にし、天気予報、気温、降水確率、傘が必要か、を通知してくれる関数を作ります。

wether.gs

var CHANNEL_ACCESS_TOKEN = 'アクセストークンを入力'; 

function RandomReturn(){
  if (Math.floor(Math.random()*100)<5){
    return true;
  }
  return false;
}

function push_message() {
  var today = new Date();
  var toWeekday = toWD(today);
  var msgWeatherForecast = getTemperatureForecast();
  
  var postData = {
    "to": 'ユーザーIDを入力',
    "messages": [
      {
        "type": "text",
        "text": "おはようございます!" +Utilities.formatDate( today, 'Asia/Tokyo', 'M月d日') + toWeekday + "、今日の天気をお知らせしますね♡\n"
                + msgWeatherForecast[0] + msgWeatherForecast[1] + msgWeatherForecast[2]
      },{
        "type": "text",
        "text": msgWeatherForecast[3]
      }
    ]}
  
  var headers = {
    "Content-Type": "application/json",
    'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
  };
  
  var options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(postData)
  };

  var response = UrlFetchApp.fetch("https://api.line.me/v2/bot/message/push", options);
  
}

// 天気予報の取得 
function getTemperatureForecast() {
  const area = "東京地方"
  var options =
      {
        "contentType" : "text/xml;charset=utf-8",
        "method" : "get",
      };
  var response = UrlFetchApp.fetch("https://www.drk7.jp/weather/xml/13.xml", options); 
  var xmlDoc = XmlService.parse(response.getContentText());
  var rootDoc = xmlDoc.getRootElement();
  var region = parser.getElementById(rootDoc,area);
  var weather = parser.getElementsByTagName(region, 'weather');
  var temperature = parser.getElementsByTagName(region, 'range');
  var rainyPercent = parser.getElementsByTagName(region, 'period');
  var weathermsg = "■天気予報" + "\n" + weather[0].getValue() + "\n";
  var tempmsg ="■気温\n" + temperature[0].getValue() + "℃/" + temperature[1].getValue() + "\n";
  var rainfall = "■降水確率\n"+"朝:"+rainyPercent[1].getValue()+"%"+"昼:"+rainyPercent[2].getValue()+"%"+"夜:"+rainyPercent[3].getValue()+"%";
  var umbrellamsg = getUmbrellNecessary(rainyPercent[1].getValue(),rainyPercent[2].getValue(),rainyPercent[3].getValue()) ;
  var rainyTemperature = [weathermsg,tempmsg,rainfall,umbrellamsg];
  return rainyTemperature
}

// 傘予想
function getUmbrellNecessary(mor,eve,nig){
  var msg = ""

  if(mor > 70 || eve > 70 || nig > 70){
    if (nig > 70 ) {
      msg = "夜降るらしいわよ。傘持っていきなさい";
    }
    if (eve > 70){
      msg = "昼から降るみたいよ。傘持っていきなさい";
    }
    if (mor > 70 ) {
    msg = "ちょっと......朝から雨?";
    }
  }else if(mor <= 70 && mor > 20 || eve <= 70 && eve > 20 || nig <= 70 && nig > 20 ){
    msg = "雨降ってんじゃないの?\n今降ってなくても、折りたたみ持っていきなさいよ";
  }else if(mor <= 20 && mor > 10 || eve <= 20 && eve > 10 || nig <= 20 && nig > 10){
    msg = "傘は必要なさそうね";
  }else if(mor <=10 && eve <=10 & nig <=10) {
    msg = "ふゆが晴れにしてあげたのよ、感謝しなさい!";
  }else{
    msg = "たまには自分で調べなさい";
  }
  if(RandomReturn()){
    msg = "ふゆにばっか頼ってないでたまには自分で調べたら?";
  }
  return msg
}

// 曜日の日本語変換
function toWD(date){
  var myTbl = new Array("","","","","","","",""); 
  var myDay = Utilities.formatDate(date, "JST", "u");
  return "(" + myTbl[myDay] + ")";
}

完全なプライベート用なので、ユーザーIDを指定して送信します。
pushMessage()を毎朝6時に実行するようtriggerを設定し、起きた時通知が入ってるようにします。
また、キャラの性格を反映させるべく、5%の確率で傘予想を教えてくれない仕様にしています。気持ち悪いこだわり。
S__70885388.jpg

##3. リマインド
LINEで予定を登録→通知してくれるリマインダーアプリを作ろう
こちらの記事を参考に、一人のユーザーが複数のタスクを登録・リマインドできるよう変更を加え、実装します。

###3.1 スプレッドシートの操作に関する関数

sheet.gs
var spreadsheet = SpreadsheetApp.openById('IDを入力');
var sheet = spreadsheet.getSheetByName('webhook');
function appendToSheet(text) {
 sheet.appendRow([text]);
}

//セルに中身があるか
function blankTF(row, col){
 return !(sheet.getRange(row, col).isBlank());
}

//sheetの行数
function lastRow() {
 var dat = sheet.getLastRow();
 return dat;
}

//全行サーチ
function seachAll(){
var dat = sheet.getDataRange().getValues();
}

//指定した行の削除
function deleatRow(RowNum){
  sheet.deleteRow(RowNum);
}

//全削除
function deleatAll(){
  sheet.clearContents();
}
![S__70885388.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378758/8c023ddc-0b07-e307-8a6f-ba3b6387d06f.jpeg)

//セル[row][col]の値を返す
function getFromRowCol(row, col) {
 var dat = sheet.getDataRange().getValues();
 return dat[row][col];
}

//セル[row][col]にvalを入れる
function setFromRowCol(val, row, col) {
 sheet.getRange(row , col).setValue(val);
}

//row行1列のデータ(内容)を取得
function getTodoCell(row) {
 return sheet.getRange(row , 1).getValue();
}

//row行2列のデータ(日付)を取得
function getDateCell(row) {
 return sheet.getRange(row , 2).getValue();
}

//row行3列のデータ(trigger)を取得
function getTriggerCell(row) {
 return sheet.getRange(row , 3).getValue();
}

スプレッドシート使って、リマインドしたいタスクを管理しています。
1列目にリマインド内容、2列目にリマインドする日付、3列目にtriggerIDを格納します。
GASには指定したタイミングで関数を実行するtriggerという機能があり、セットした時に割り振られる固有のIDがtriggerIDです。

###3.2 Triggerをセット、削除する関数

trigger.gs
function setTrigger(row, date) {
 var triggerDay = moment(date,'YYYY年MM月DD日H時m分').toDate(); 
 var trigger =  ScriptApp.newTrigger("remind").timeBased().at(triggerDay).create();
 setFromRowCol(trigger.getUniqueId(), row, 3);
}

function deleteTrigger(uniqueId) {
 var triggers = ScriptApp.getProjectTriggers();
 for(var i=0; i < triggers.length; i++) {
   if (triggers[i].getUniqueId() === uniqueId) {
     ScriptApp.deleteTrigger(triggers[i]);
   }
 }
}

ここは参考記事まんまです。
setTriggerは、格納された日時になると関数を呼び出すtriggerをセットします。

##3.3 リマインド本体

remind.gs
var moment = Moment.load();

  //ポストで送られてくるので、送られてきたJSONをパース
function doPost(e) {
  var json = JSON.parse(e.postData.contents);
  var replyText = 'null';
  
  var filledDo = false;
  var filledDate = true;
  if(lastRow()>0){
  filledDo = blankTF(lastRow(),1);
  filledDate = blankTF(lastRow(),2);
  }

  var userId = json.events[0].source.userId;
  //返信するためのトークン取得
  var replyToken= json.events[0].replyToken;
  if (typeof replyToken === 'undefined') {
    return;
  }
  var message = json.events[0].message.text; 

      switch (message) {
       case 'キャンセル':
         replyText = cancel();
         break;
        case '天気':
          replyText = '';
          push_message();
          break;
  case '確認':
     if (lastRow()>0) {
       replyText = confirm();
     } else {
       replyText = 'まだ何にも登録してないでしょ';
     }
     break;
       default: 
          if(message.match(/削除/)){
            replyText = del(message);
          }
         else if(filledDate){
           replyText = setDo(message);
           break;
         }else{
           replyText = setDate(message);
           break;
         }
         break;
 }

   // メッセージを返信    
  UrlFetchApp.fetch(line_endpoint, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': replyText,
      }],
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);

}


//データの削除
function del(m){
  m=m.replace('削除', '');
  if (m==''){
  return '「削除+リマインダー番号」で指定しなさい'
  }
   m = m.replace(/[A-Za-z0-9]/g, function (s) {
   return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
 });
  if(m>lastRow()){
    return '番号間違ってるわよ\nもう一回確認しなさい';
  }
  var s =  getTodoCell(m);
  var deltriggerID = getTriggerCell(m);
  deleteTrigger(deltriggerID);
  deleatRow(m);
  return ''+s+''+'を削除したわ';
}

//データの確認
function confirm(){
  var replyText = 'まだ何にも登録してないでしょ';
      if(lastRow()>0){
        replyText = '';
   for (var i = 1; i <= lastRow(); i++) {
     replyText += i+''+ getDateCell(i) + 'に「' + getTodoCell(i) + '\n';
   }
  }
  replyText+='以上よ';
  return replyText;
}

//入力のキャンセル
function cancel(){
  if(lastRow()>0){
  deleatRow(lastRow());
  return 'キャンセルしたわ\nまたなんかあったら言って';  
  }else{
  return 'キャンセルするものなんてないわよ';
  }
}

//日付の入力
function  setDate(m){
 // 全角英数を半角に変換
 m = m.replace(/[A-Za-z0-9]/g, function (s) {
   return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
 });
 var date = Moment.moment(m, 'M月D日H時m分', true).format('YYYY年MM月DD日H時m分');
 if (date === 'Invalid date') {
   var match = m.match(/\d+/g);
   if (match !== null) {
     date = Moment.moment().add(+match[0], 'minutes').format('YYYY年MM月DD日H時m分');
   }
 }
 if (date === 'Invalid date') {
   return '「何分後」「」'
 } 
 setTrigger(lastRow(), date);
 setFromRowCol(date,lastRow(), 2);
 return 'はいはい\n'+date + 'に連絡するわ';
}

//データの入力
function  setDo(m){
  if(RandomReturn()){
    return 'めんどくさぁ......';
  }
  setFromRowCol(m, lastRow()+1, 1);
return '' + m + '」ね、いつ連絡すればいいのよ\n「キャンセル」って言ってくれればやめるけど';
}

function getnowDate(){
  return Moment.moment().format('YYYY年MM月DD日H時m分');
}

function remind(e){
  var nowDate = getnowDate();
  var replyText = 'リマインド登録されてないわよ';
  
    if(lastRow()>0){
   for (var i = 1; i <= lastRow(); i++) {
     replyText = getDateCell(i);
     if(getDateCell(i)==Moment.moment().format('YYYY年MM月DD日H時m分')){
       replyText = getTodoCell(i);
       var deltriggerID = getTriggerCell(i);
       deleteTrigger(deltriggerID);
       deleatRow(i);
     }
   }
  }
  
var postData = {
    "to": 'ユーザーID',
    "messages": [
      {
        "type": "text",
        "text": replyText+'\nふゆがわざわざリマインドしてあげたんだから、忘れるんじゃないわよ'
      }
    ]}
  
  var headers = {
    "Content-Type": "application/json",
    'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
  };
  
  var options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(postData)
  };

  var response = UrlFetchApp.fetch("https://api.line.me/v2/bot/message/push", options); 
}

S__70877212.jpg
S__70877211 (2).jpg

送られてきたテキスト内容によって、天気予報のwether()、タスクを削除するdel()、登録してあるタスクを確認するconfirm()、現在の入力をキャンセルするcancel()、リマインドする日時を格納するsetDate()、リマインドするタスクを格納するsetDo()のどれかにシフトするようになっています。
ここでもタスクを登録する際、5%の確率で面倒くさがられます。
S__70893571.jpg

98
72
2

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
98
72

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?