今回の記事は、GoogleAppsScript(GAS)とGoogleスプレッドシートで、
LINEのMessagingAPIを利用したBOTを作成した経緯の投稿です。
ソースコードは最後にありますが、本記事は、ソースに関する記述はほぼありません。
コードに関する記事は別途、投稿する予定です。(時期は未定)
学習項目 = プログラミング + そろばん
プログラミングでそろばんの問題を作成
プログラミング学習始めました。
以前、MITのOCWでPythonの講座を受講し始めているのは以前の記事の通りです。
DXレポートを読んだことや、いろいろと思うところはありますが、
これからの時代は、テクノロジー、プログラミングに関する知見は一般市民にも求められることと思います。
プログラミングは、料理や、日曜大工などと同じようになるのではないかと思っています。
つまり、プロはプロとして存在するけれども、一般市民も生活の中でその技術が必要になってくる。というスキルになると思います。
そろばん始めました。
また、ブログの記事としては投稿していませんでしたが、
この4月前後から、そろばんを始めました。
計算が必要な場合は、携帯の電卓機能などを立ち上げればよいのですが、
わざわざ電卓アプリを起動するほど重要ではなく、
でも、お店で、
「これら3つの会計金額はいくらだろう?」
といったことを思った経験はないでしょうか?
そろばんを頭でイメージすることができれば、暗算で計算ができるようになるようです。
中上級者は5桁と5桁の掛け算や割り算も即時に答えることができます。
娘がそろばんを習い、中上級者一歩手前くらいの位置にいるため、
上で書いたような状況になったときには娘に計算をお願いしています。
すぐに答えが返ってくるので助かりますね。
その娘の影響もあり、そろばんを始めました。
現状、私はそこまで到達するつもりもありませんが、いずれ、3桁×2桁を暗算で計算できるようになるくらいまではいきたいと思って、現在練習中です。
csvファイルを作成し、スプレッドシートの問題テンプレートシートにコピペを自動化
そろばん練習用の素材をPythonで作成
今、私が学習中のプログラミングとそろばんについて、
学習をより強化するためには、実践を積み重ねることです。
「そろばんの学習用に問題集を買う。」
これも選択肢としてはありかもしれませんが、
毎日やることを考えると自分で作ったほうが良い気がして、自作することにしました。
Pythonで、指定する範囲内で乱数を発生させて、5口の見取り算(5回連続して行う加減算)と、掛け算の問題を作ります。
私用には、見取り算(加減算)は2桁5口の問題と2桁×1桁の問題を、
家族用には、3桁や4桁の見取り算や4桁×3桁、÷3桁=3桁などの問題などです。
加減算は途中でマイナスになると私には無理なのでNGという条件付きです。
これらの問題作成は、Pythonで比較的簡単に作成でき、
csvファイルへの出力も調べながらできるようになりました。
ここまでがそろばんの練習用の素材をPythonで作り、csvファイルを作成する話です。
そろばん練習用のテンプレートにコピペを自動化するマクロを作成
ここまでで、
Pythonで見取り算(加減算)の問題用の数字5つと答えと、
掛け算割り算の問題と答えを
csvファイルで用意することができました。
これらを、Googleスプレッドシートで用意したテンプレートに値貼り付けすればそろばん練習用のプリントの出来上がりです。
このcsvファイルを、別スプレッドシートにインポートして、
問題分と、解答分をそれぞれコピペする作業を数日行っていました。
数日後、「あれ?このコピペする時間って無駄なのでは?」って思って、
Pythonで作られたcsvファイルをインポートしたのち、
マクロを実行したら問題をテンプレートにコピペするようにしました。
csvファイルの取り込みをやめて、GASで問題を作成
Pythonで作成したcsvファイルをGoogleスプレッドシートで読み込んで、
読み込んだ後は自動化できたのは上で書いた通りです。
Pythonで作成するファイルをGoogleDriveにアップロードして、
Googleスプレッドシートから、GoogleDriveにアクセスして、問題作成が自動化できれば良いのですが、
PythonからGoogleDrive保存するコードを調べた結果、今の私には到底理解できる内容ではありませんでした。
プログラミングは単なるコピペではなく、なるべく理解したうえで利用したいため、
PythonからGoogleサービスへの接続は今回は諦めることにしました。
次に、問題を張り付ける工程自体が無駄な時間に思えてきて、
いっそのことGAS(GoogleAppsScript)で問題が作成できれば良いのではないかな。と考え始めました。
Googleスプレッドシートから、エディターを起動したら、すぐにコードが書けるようになるのが、ExcelのVBEと似ていますね。
Pythonの環境構築で苦労した私には、このすぐにコードが書けて、実行する環境があるのはうれしかったです。
セルの指定などはExcelのVBAと似た感じで操作できるため、ググりながらコードを書いていきます。
初めてGASで書いたコードがこちら
for (var i=1;i<=25;i=i+1){
var num_a;
num_a=Math.floor(Math.random()*89)+11;
var num_b;
num_b=Math.floor(Math.random()*8)+2;
var Q= num_a +"×"+num_b+"=";
var A=num_a*num_b;
var Qrange=sheet.getRange(i,1).setValue(Q);
var Arange=sheet.getRange(i,3).setValue(A);
}
2桁×1桁の掛け算の作問と答え合わせ用の答えを記載するコードです。
やることは簡単で、
2桁の変数と1桁の変数を用意して、
問題を作成
答えを作成
スプレッドシートに書き出す。
これを25回繰り返す。
割り算もランダム変数2つを用意して、
設問と答えを定義すればよいので比較的簡単です。
問題は見取り算で、ランダムな変数5つと答えを用意するまでは簡単なのですが、
計算の途中で負の値になると私のそろばんスキルでは対応できないため、
計算過程で常に正の値をキープする方法がGASでは分からずに諦めました・・・
見取り算用の問題だけは、Pythonで作問して、
スプレッドシートにインポートしてマクロで問題のテンプレートに張り付けています。
好奇心×プログラミング = 遊び
GASをいろいろ調べていたら、LINEBOTを作ってみたくなり調べる
上記の掛け算の問題をGASで作成するにあたり、いろいろなサイトを見ている中で、
スプレッドシートとGASがあれば、BOTが作れることが分かりました。
なんとなく、
「楽しそうだからちょっとやってみっか。」
程度に考えてLINEBOT作成に着手しました。
実際、そろばんの練習で、問題を印刷するのではなく、
LINEBOTで練習できれば、よりたくさん練習できるので、そろばんの練習にもなりますしね。
LINEBOTのpushメッセージ送信のBOTを作成
LINEBOTのpushメッセージだけであれば、
ここ(※1)とか、ここ(※2)のページを参考にしてすぐにできました。
JSON形式の記述に理解が追い付いていない部分はありますが、なんとか形になりました。
トリガーの設置を1分おきに問題を送られてくるようにしたら、物凄く鬱陶しかったですw
※1:GASとLINE Messaging APIでpushメッセージのLINEbotを作る!
※2:LINE BOTでメッセージを送る(push message)決まった時間に自動的に
答えを送っても、こんな下のような感じにしかできません。
自分で設定しておいてなんだけど、お馬鹿さんで笑うw
LINEBOTでやりたいことの設計図作成
ここまでくるとやっぱり答え合わせできるBOTを作りたくなりますよね!?
今までのpush通知だけであれば、問題の作成と送信だけで終わっていたけれども、
答えをreplyする場合は、全体像の把握が必要だと思い、設計図を作りました。
結果的には、この設計図通りにならなかったけれども、
全体像を把握するという意味では、作ってよかったと思います。
LINEBOTのreplyメッセージを送信するBOT作成に挑戦
設計図までできたので、あとはreplyメッセージに関する理解と、
条件分岐のデータの受け渡しをパーツごとに作っていけば、出来そうな気がしていました。
遊び × 理解不足 = 挫折
オウム返しBOT
LINEBOTのreplyメッセージの送信に必要な知識の習得のために、いろいろなサイトを見てみました。
reply Tokenなるものが必要で、JSON形式で送られてくるメッセージから取得して、
送信するメッセージにそれが必要 というのがpushメッセージとの大きな違いでしょうか。
多くのサイトで、まずは送られてきたメッセージをオウム返しするBOTについて書かれていたので、
まずはそれらを参考にしつつ、オウム返しするBOTを作成しました。
オウム返しBOTの理解と改良
オウム返しBOTの受信したメッセージと、返信するメッセージの構造は理解できたので、
返信するメッセージを1/2の確率で、ちょっと加工して送り返すBOTを作成してみました。
いろいろなサイトを見て、コードをなんとなく理解する
GoogleスプレッドシートとGASを利用したLINEBOTに関しては、いろいろなサイト(※3)でコードが公開されているので
何をやっているのかを考えながら読んでいくと、なんとなく理解できるようになりました。
ここで一番理解できなかったのが、受信したJSON形式のメッセージの中身がどうなっているのかでした。
ユーザーIDを取得しているサイトもあれば、タイムスタンプを取得しているサイトもあり、
受信メッセージから何が取得できるのかの全体像がつかめませんでした。
※3:参考URL
【LINE Botの作り方】Messaging API × GAS(Google Apps Script)でおうむ返しボットを作成する
GoogleAppsScriptでDBを使ったLINEbotでユーザー別のデータに配慮する。
LINEで予定を登録→通知してくれるリマインダーアプリを作ろう
GASとLINE Messaging APIで同棲生活を便利にした話
愛犬とチャット
公式のリファレンスを読むことはすごい重要
いろいろググって調べている中で、当然ですが公式のリファレンス(LINE messaging APIリファレンス)も検索に引っ掛かります。
実際のコードが書かれたブログやQiitaの記事を参考にしていましたが、JSON形式の部分などは、公式のリファレンスを確認しながらコードを見ないと、何をしているのかわからなくて当然ということが分かりました。
むしろなぜ今まで公式の文書をあまり見ていなかったのかが不思議に思うくらい公式の文書って参考になることが分かりました。
公式のリファレンスは何よりも重要って感じの発言をどこかで見たことがある気がするのに、
この段階になって、ようやくそれに気づけました。
いろいろなサイトは参考になるけれども、参考にしかならない
公式のリファレンスや、いろいろなサイトを参考にしつつも、少しずつ作っていくも、
やはり、圧倒的に基礎がなっていないことが分かりました。
このまま、理解不足のまま、なんとなく動くものは作れるような気もするけれども、
どう動いているかがわからないと改良も加えられないため、
ここは一度、基礎に戻ることを決めました。
push通知のBOTがうまくいったからって調子に乗ってました・・・
mnd=挫折 ;clear(mnd) ; 基礎学習=get スキル
UdemyでGAS、LINEBOTの講座を受講
基礎に戻って学ぶと言っても、どうやって基礎を学ぶかについてを考えました。
書籍での学習も考えましたが、不要不急の外出は自粛するよう求められており、
Amazonで注文しても、到着まで2日前後かかることを考えると、書籍での学習はあまり良い選択肢ではないように思えてきて、
流行り(?)のUdemyで講座を受講することにしました。
一つはGASで業務効率を上げよう。という内容のもので、GASの取り扱いについて学びました。この講座でGASの基本的な取扱いについて学ぶことができました。(※4)
二つ目はLINEBOTを実際に作成してみよう。という内容のもので、LINEBOTを実際に作成することが主題の講座です。(※5)
Udemyの講座受講は大正解でしたね。
2つの講座を受講したのちに、今まであやふやだった「関数の戻り値」と、「配列」について理解が深まったと感じます。
※4:ビジネスパーソンに贈る業務効率化大全 〜Google Apps Scriptによる業務の自動化〜
※5:LINEで順番待ち。順番待ちLINEボットの作り方講座
関数の戻り値の理解
今まで、関数は、引数と戻り値を意識しないで使っていたけれども、引数と戻り値が重要なことにようやく気付きました。
というか、戻り値をreturnで渡せるようにできるようになりました。
いろいろなところでいわれていることではありますが、
機能や処理を関数として別に出すメリットとしては、次のようなものです。
- 同じ処理を毎回書かなくてよい。
- 関数内で処理している部分は、関数が正確であれば保証されているため、想定と異なる値になった場合は、関数以外に原因があることがわかる。
これらは私も今回のBOT作成の時に実感することができました。
配列の理解
もう一つ、今回のBOT作成に必要な部分として、スプレッドシートの操作で、
情報の読み書きを行単位、場合によっては複数行で実施するため、配列の理解が深まったのが大きいです。
特に、スプレッドシートのデータを2次元配列として取得、条件に合った行の特定と、対象の項目をイメージしてコードが書けるようになっていきました。
スキル+やりたいこと=自作BOT
設計図の作り直し
上で書いたような設計図にはならずに、変更しました。
変更した点は以下の通りです。
- 問題出していなかったら、何を言われても問題を出す。
- 問題はシートから出題するのではなく、毎回作成し記録を残す。
- 「正解!」と出すのは諦める。
理由は、何とかBOTとして形になりそうな目途が立ったため、
特定の文字列の判定やその時の場合分け、送られてきた回答に対する正誤判定を実装しようとすると出来上がるまでに時間がかかるため、まずは形になりそうな状態を目指しました。
関数として別にできる部分は別にして部品を作っていく
今回作成したのは、2桁×1桁の問題ですが、
珠算式暗算の練習で2桁×1桁に慣れてきたら、
3桁×1桁や、2桁×2桁のBOTも作ろうと思っています。
上でも少し書きましたが、読みやすさや汎用性を考えると、
「可能な限り、部品として分けて作っていくことが良さそう。」
というのがUdemyの受講や、ネットの賢人たちから得た私の結論です。
終盤は、実行されるだろうことが頭に浮かびながらコーディング
実際に、いろいろと試行錯誤しながらコードを書いていく中で、うまくいかないことも多々ありましたが、
終盤は、今扱っているデータがどういう構造で、どこをどうしたいのかが見えてくるようになりました。
上級者はもっと効率的に関数や、メソッドを駆使してスマートに書けるのでしょうが、
自分が理解できる形で動くものができたのがうれしいですね。
自作BOT=プログラミングスキルup+そろばん学習+達成感+学習意欲
公開して遊ぶ
試行錯誤しながら、ようやく遊べる状態になりました。
遊んでいる様子はこちら。
出来たものは、理想の形とは違うけれども、まずは動くものができたのがうれしいですね!
改善したい点
改善したい点としては、次のような感じです。
- 回答した答えがあっていたら「正解!」と返す。
- 不正解の場合、正解は伏せたまま、もう一度問題を出す。
- 誤答の傾向をつかむために、ログを取って分析できるようにする。
この辺りは、追って機能追加していければと思います。
3桁×1桁や2桁×2桁の暗算練習BOTを作るときかな。
今は自分で書いたコードが望む形になったことがうれしいですね!(2回目)
APPENDIX
LINEBOT(2桁×1桁の暗算練習用)のソースコード
LINEBOT作成では上記の通り、様々な方のお世話になったため、
私もだれかのためになればと思いソースコード上げときます。
Googleスプレッドシートを準備して、
”user”というシートを作成し、
A列から順に、以下のカラムを用意する。
LINE_User_ID | Question | _numberA | _numberB | Answer | CreateDate | LastUpdateDate |
同一スプレッドシートに、”receiveMessage”という別シートを用意し、
同様にA列から順に、以下のカラムを用意する。
timeStamp | userId | userMessage | replyToken | displyName | Date |
AppsScriptに、以下のコードを張りつけ、変数部分を変更すれば完成です。
LINEdeveloperの登録や、GASのデプロイ方法などは、上記記載の私が参考にしたサイトさんなどに詳しく書かれているので、そちらを参照ください。
///スプレッドシート関連---------------------------------------------------------------
var SS = SpreadsheetApp.openById("【ここにスプレッドシートIDを記載。】");
var receiveMessage_sheet= SS.getSheetByName("receiveMessage");
var user_sheet =SS.getSheetByName("user");
///--------------------------------------------------------------------------------
///LINEBOT関連----------------------------------------------------------------------
var CHANNEL_ACCESS_TOKEN = "【ここにアクセストークンを記載。】"
var LINE_ENDPOINT = "https://api.line.me/v2/bot/message/reply";
///--------------------------------------------------------------------------------
///日付関連-------------------------------------------------------------------------
var date =new Date();
var now =Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss')
///--------------------------------------------------------------------------------
///本体-----------------------------------------------------------------------------
function doPost(e){
var json = JSON.parse(e.postData.contents);
var timeStamp = json.events[0].timestamp;
var userId = json.events[0].source.userId;
var userMessage = json.events[0].message.text;
var replyToken = json.events[0].replyToken;
var displyName = getUserDisplayName(userId);
var datas=[timeStamp,userId,userMessage,replyToken,displyName,now];
receiveMessage_sheet.appendRow(datas);
var userData =getTargetUserData(userId);
if(typeof userData === 'undefined'){
var userData_Question = creatQuestion_multi2_2();
userData_Question.unshift(userId);
user_sheet.appendRow(userData_Question);
sendMessage(replyToken,"問題を出すよ!" +userData_Question[1]);
}
else{
var postedQuestionDataRange=user_sheet.getRange(1+userData[0], 1,1,7);
var postedQuestionDataSet = postedQuestionDataRange.getValues();
sendMessage(replyToken,"出した問題の答えを言うよ!" + postedQuestionDataSet[0][1] + postedQuestionDataSet[0][4] );
postedQuestionDataSet[0][0] = "DEL-"+postedQuestionDataSet[0][0];
postedQuestionDataSet[0][6] = now;
postedQuestionDataRange.setValues(postedQuestionDataSet)
};
}
///----------------------------------------------------------------------------------
///LINEのユーザーの表示名を取得する関数----------------------------------------------------
function getUserDisplayName(userId){
var line_endpoint_profile = 'https://api.line.me/v2/bot/profile';
var res = UrlFetchApp.fetch(line_endpoint_profile + '/' + userId,{
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
},
'method': 'get',
});
return JSON.parse(res).displayName;
}
///------------------------------------------------------------------------------------
///ユーザーシートから、対象のユーザーの配列を取り出す関数---------------------------------------
function getUserDataSet(){
var userDataSet = user_sheet.getDataRange().getValues();
return userDataSet;
}
function getTargetUserData(userId){
var allUserData =getUserDataSet();
for (g=0;g<allUserData.length;g=g+1){
var targetUserData=allUserData[g];
if(targetUserData[0]== userId ){
var targetUserData_2dArr=[g,];
targetUserData_2dArr.push(targetUserData);
return targetUserData_2dArr;
break;
};}
}
///------------------------------------------------------------------------------------
///問題を作成する関数---------------------------------------------------------------------
function creatQuestion_multi2_2(){
var _num_a=Math.floor(Math.random()*89)+11;
var _num_b=Math.floor(Math.random()*8)+2;
var QuestionMulti_22 = _num_a +"×"+_num_b +"=";
var AnswerMulti_22 =_num_a*_num_b;
var arr_multi_22 =[QuestionMulti_22,_num_a,_num_b,AnswerMulti_22,now,now];
return arr_multi_22;
}
///------------------------------------------------------------------------------------
///ReplyMessageを作成して、送信する部分----------------------------------------------------
function sendMessage(replyToken, replyText) {
var postData = {
"replyToken" : replyToken,
"messages" : [{
"type" : "text",
"text" : replyText
}]
};
return postMessage(postData);
}
//JSON形式データをPOST
function postMessage(postData) {
var headers = {
"Content-Type" : "application/json; charset=UTF-8",
"Authorization" : "Bearer " + CHANNEL_ACCESS_TOKEN
};
var options = {
"method" : "POST",
"headers" : headers,
"payload" : JSON.stringify(postData)
};
return UrlFetchApp.fetch(LINE_ENDPOINT, options);
}
///------------------------------------------------------------------------------------
コメント