はじめに
語彙力を増やすために、自分用の辞書を作ろうとしたが、せっかくなのでGASとLINEのMessaging API、どこからでも辞書に登録できるようにする。
前提
javascriptの基本的な文法を理解していること。
内容
完成物のイメージ
LINEで単語と説明を送信すると、それをスプレッドシートの最終行に登録してくれる。単語のみを入れると、単語の説明を返してくれる。
説明に改行を入れたいので、単語と説明は、改行二回で区切る。以下のような感じ。
単語
説明だよ。
これだと単語=説明みたいになってややこしいね。
また、スプレッドシ-トには、1列目に単語、2列目に説明を記録する。
単語を探す関数、単語を追加する関数、メッセージを送信する関数、LINEから受け取った情報を処理して、単語を探すのか、追加するのかを判別する関数を作ればよいだろう。関数をどの程度細かく分ければよいかが、まだあまり分かっていないので、是非コメントで教えてほしい。
作業
前準備
はじめに、Google ドライブを開いて、新規からGoogle スプレッドシートを作成する。拡張機能からApps Scriptを選んで、GASのエディタを開く。プログラムはここに書いていく。
次に、Messaging APIの準備を行う。ここに行って、今すぐ始めようと書かれたボタンを押す。流れに沿って必要事項の入力をしていき、チャンネルを作成する。
グローバルで行うこと
いくつかの関数でスプレッドシートのデータを扱うので、グローバルで取得しておく。
//gasと関連付いたスプレッドシートを取得する。
let spreadSheet=SpreadsheetApp.getActiveSpreadsheet();
//スプレッドシートの開いているシートを取得する。
let sheet=spreadSheet.getActiveSheet();
//シートで、データが入っているセルの範囲を取得する。
let range=sheet.getDataRange();
//範囲のセルの情報を2次配列にして取得。
let values=range.getValues();
単語を探す関数
最初から順番に値を見ていき、単語と合うものがあればその場所を返す。無ければ-1を返す。
function searchWord(word){
for(let i=0;i<values.length;i++){
if(values[i][0]==word)
return i;
}
return -1;
}
単語を追加する関数
単語は、既に存在する場合は上書き、しない場合は最終行に追加することにする。
最終行を取得するには、getLastRowを使えばよいそうだが(参考)、今回は先にvaluesを取得しているので、その長さを使って最終行を取得する。
function addDictionary(word,description){
let wordIndex=searchWord(word);
if(wordIndex==-1){
let lastRow=values.length+1;
sheet.getRange(lastRow,1).setValue(word);
sheet.getRange(lastRow,2).setValue(description);
}else{
sheet.getRange(wordIndex+1,2).setValue(description);
}
}
getRangeでは、セルの位置が(1,1)から始まっていることに注意する。
メッセージを送信する関数
Messaging APIを使って、ユーザーにメッセージを送信する。
function sendMessage(messageText,userId){
let url="https://api.line.me/v2/bot/message/push";
let token="チャンネルのアクセストークン"
let message={
"type":"text",
"text":messageText
};
let params={
"headers":{
"Content-Type":"application/json",
"Authorization":"Bearer "+token
},
"method":"post",
"payload":JSON.stringify({
"to":userId,
"messages":[message]
})
};
UrlFetchApp.fetch(url,params);
}
チャンネルのトークンは、作ったチャンネルのMessaging API設定のチャネルアクセストークンと書かれたところから発行する。表示された文字列がトークンとなるので、それをコピペして貼り付けよう。
メッセージを受け取った時の処理
プログラムを書く前に、いくつかやることがある。
はじめに、Messaging API設定から応答メッセージを編集する。
応答モードをBOTに、応答メッセージをオフに、Webhookをオンにする。応答モードをオンにすると、BOTへメッセージを送った時に、自動で返信してしまう。WebhookはLINEでメッセージ等を受け取った時、それをGASへ送信するのに必要となる。
次に、LINEから送信された情報を受け取るために、プロジェクトを公開したい。そのためには、デプロイから新しいデプロイを作成する。デプロイタイプはウェブアプリに、アクセスできるユーザーを全員にして(LINEもアクセスできるようにするため)、デプロイする。そして、ウェブアプリのURLをコピーしておく。
次に、Messaging API設定のWebhook 設定で、さっきコピーしたURLを貼り付け、Webhookの利用をオンにする。
ここからやっとプログラムを書いていく。
function doPost(e){
let userId="自分のユーザーid";
let contents=JSON.parse(e.postData.contents);
let text=contents.events[0].message.text;
//送ってきたのが自分でなければ、終了する。
if(userId!=contents.events[0].source.userId)
return;
//改行二回で分割してる。もし、この配列の長さが1ならば検索、2ならば登録とわかる。
let textArr=text.split("\n\n");
let sendText;
if(textArr.length==1){
let index=searchWord(textArr[0]);
if(index==-1){
sendText="単語が見つかりませんでした。"
}else{
sendText=sheet.getRange(index+1,2).getValue();
}
}else{
addDictionary(textArr[0],textArr[1]);
sendText="追加しました。"
}
sendMessage(sendText,userId);
}
doPostは、LINEからメッセージを受け取った時に勝手に実行される。
メッセージを送ってきた相手が自分の時のみ、動かしたいので、ユーザーidを照合している。
自身のユーザーidはチャネル基本設定のあなたのユーザーIDから見られる(参考)。
バグの修正
プログラムを保存した後、デプロイ->デプロイの管理->編集からバージョンを新しくし、デプロイすることで、更新することができる。
実際にアカウントを友達登録し、辞書の登録を行ってみると、スプレッドシートが少しおかしなことになっている。
このように、最初の行が空いてしまっているのである。
原因は、スプレッドシートにデータの入ったセルが全くないときも、valuesが[[""]]になってしまうからだ。
動作はするので特に問題はないが、少し気持ち悪いので、修正する。
function addDictionary(word,description){
let wordIndex=searchWord(word);
if(wordIndex==-1){
let lastRow;
//最初の要素の長さが1,つまりvaluesが[[""]]のとき
if(values[0].length==1){
lastRow=1;
}else{
lastRow=values.length+1;
}
sheet.getRange(lastRow,1).setValue(word);
sheet.getRange(lastRow,2).setValue(description);
}else{
sheet.getRange(wordIndex+1,2).setValue(description);
}
}
これで、無事にバグは治った。
さいごに
最終的に、このような感じになった。
LINEでの表示
スプレッドシートでの表示
すべてのコードを乗せておく。早く動かしたい人は、これをコピペして使えばいい。
"use strict"
let spreadSheet=SpreadsheetApp.getActiveSpreadsheet(),
sheet=spreadSheet.getActiveSheet(),
range=sheet.getDataRange(),
values=range.getValues();
function searchWord(word){
for(let i=0;i<values.length;i++){
if(values[i][0]==word)
return i;
}
return -1;
}
function addDictionary(word,description){
let wordIndex=searchWord(word);
if(wordIndex==-1){
let lastRow;
//最初の要素の長さが1,つまりvaluesが[[""]]のとき
if(values[0].length==1){
lastRow=1;
}else{
lastRow=values.length+1;
}
sheet.getRange(lastRow,1).setValue(word);
sheet.getRange(lastRow,2).setValue(description);
}else{
sheet.getRange(wordIndex+1,2).setValue(description);
}
}
function sendMessage(messageText,userId){
let url="https://api.line.me/v2/bot/message/push";
let token="チャンネルのアクセストークン"
let message={
"type":"text",
"text":messageText
};
let params={
"headers":{
"Content-Type":"application/json",
"Authorization":"Bearer "+token
},
"method":"post",
"payload":JSON.stringify({
"to":userId,
"messages":[message]
})
};
UrlFetchApp.fetch(url,params);
}
function doPost(e){
let userId="自分のユーザーid";
let contents=JSON.parse(e.postData.contents);
let text=contents.events[0].message.text;
//送ってきたのが自分でなければ、終了する。
if(userId!=contents.events[0].source.userId)
return;
//改行二回で分割してる。もし、この配列の長さが1ならば検索、2ならば登録とわかる。
let textArr=text.split("\n\n");
let sendText;
if(textArr.length==1){
let index=searchWord(textArr[0]);
if(index==-1){
sendText="単語が見つかりませんでした。"
}else{
sendText=sheet.getRange(index+1,2).getValue();
}
}else{
addDictionary(textArr[0],textArr[1]);
sendText="追加しました。"
}
sendMessage(sendText,userId);
}
最後まで読んでくれてありがとう。
分かりにくかったところや、改善できるところなどは、是非コメントで教えてほしい。