GoogleAppsScriptでSpreadSheetの内容を元におみくじをしてくれるBotを作る

# はじめに
この記事は以下の記事の続きという位置づけで書いているので、前回説明している内容は説明しないです。なので、まだの人は読んでからこの記事を読んでください。
[GoogleAppsScriptで発言した内容に応答するTelegram BOTを作る \- Qiita](https://qiita.com/k32ru/items/cafa7683c20644583bcf)
今回の最終的な目標としてはスプレッドシートにかかれている内容をもとにおみくじの結果を伝えるBotを作成します。

# コマンドに対応させる
今回は```/omikuji```と発言すると、BOTがおみくじの結果を伝えるBOTをつくろうと思います。
```getRandomInt```は```getRandomInt(3)```と与えると、3つランダムな数字を0から返す関数(0か1か3をランダムに返す)です。
以下のように入力していましょう。入力したら、```omikuji```を何回か実行してみてランダムになっているか確認しましょう。

```javascript
function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}
function omikuji(){
  let omikujiBox= ['大吉','中吉','小吉','吉','超大吉','天地破滅']
  let result = omikujiBox[getRandomInt(omikujiBox.length)];
  Logger.log(result)
}
```
ちなみに、javascriptの配列も、0から始まります。各要素には以下のようにアクセスするが可能です。

```javascript
Logger.log(omikujiBox[0]);//大吉
Logger.log(omikujiBox[5]);//天地破滅
Logger.log(omikujiBox.length);//6
```

ランダムに値が変える事が確認できたら、これを以下のように変えて、ウエブアプリをデプロイしましょう。
大事なことなので何回もいいますが、**デプロイ→デプロイを管理を選択してください。**
新しいデプロイではウエブアプリのURLが変わってしまいます。(WebHookは変更しない限りは最初に設定したURLを見ているので、ソースを変えても新しいデプロイでは内容が変更されません)

```javascript
function omikuji(){
  let omikujiBox= ['大吉','中吉','小吉','吉','超大吉','天地破滅'];
  let result = omikujiBox[getRandomInt(omikujiBox.length)];
  return result; //結果を返すように変更
}

function doPost(e) {
  //telegramからのデータ
  let data = JSON.parse(e.postData.contents);
  let text = data.message.text;
  let name = data.message.from.first_name;
  let id   = data.message.chat.id
　let answer = name + "さんの結果は"　+ omikuji() + "です"
  if(text == "/omikuji" ){  //ユーザが/omikujiと発言したら
    sendText(id,answer);
  }
}

```

デプロイができたら、```/omikuji```と発言して、試してみましょう。以下のようになりましたか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8633/c28b4e8d-5944-ff46-878f-218357c56e11.png)

毎回```/omikuji```と発言するのは大変ですよね、一覧にあって、簡単に入力できるようにしたいですよね？
その場合には[@BotFather](https://telegram.me/BotFather)に話しかけて、```/setcommands```と発言します。変えたいBOTを選択します。
書式はBotFatherが行っているように以下の書式になります。今回は3行目のようにしました。

```
command1 - Description
command2 - Another description
omikuji - おみくじした結果を伝えます
```
多少タイムラグはありますが、BOTがいるチャンネルで/ を押すと以下のように出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8633/da83f297-c80c-c067-d0ab-db1c08fd8d18.png)
こんどはこれで実行しても何も出ないですよね？それは、BOTが```/omikuji```のみし反応しないからです。なので```deleteAfterAt()```関数を追加して```doPost```の内容を一部変更します。
正規表現を使えば、綺麗に処理できるのかもしれまんせが、思いつかなかったです。

```javascript
function deleteAfterAt(str){
  let point = str.indexOf("@");
  if(point>0){
    return str.substring(0,point);
  }else{
    return str;
  }
}

function doPost(e) {
  //telegramからのデータ
  let data = JSON.parse(e.postData.contents);
  let text = data.message.text;
  let name = data.message.from.first_name;
  let id   = data.message.chat.id
  let answer = name + "さんの結果は"　+ omikuji() + "です"
  let command = deleteAfterAt(text);　//発言内容に@がそれ以降の内容をけして
  if(command == "/omikuji" ){　//その内容が/omikujiだったら実行する
    sendText(id,answer);
  }  
}
````
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8633/16bbfe69-5ea9-7c8b-72e4-eb8b2ba3143d.png)
## BOTへのコマンドに関する補足
### コマンドを処理しても発言してくれない時は名指ししよう
例えば、一つのチャットルームに複数のBOTが居た場合、この場合ではBotAとBotBが居るとします。
たまに、コマンドを発言しても本来応答すべきBOTが応答してくれないことがあります。
これは、なぜか、BotAにだけコマンドが行っていてBotBに行ってない状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8633/d129a3d7-9a70-1917-a5e8-7068761a73d8.png)
そのような時には、コマンド名@BOT名として名指しをすると反応します。(それでも、反応しない場合にはdoPost()の処理が上手く出来ないことが考えられます)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8633/2461aebb-654f-4bbf-5144-129325145109.png)

## BotからBotへの処理はできない。
図であえて説明する必要もないですが、BotからBotへの処理はできません。
どうしても、処理をさせたい場合にはユーザからの発言というようにする。Pythonだと、[Telethon](https://github.com/LonamiWebs/Telethon)でユーザからのクライアントから処理を書く事ができます。(GASはjavascriptのみしか動かないので、Telethonは動かせません)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8633/1557ea27-80e9-5f08-32ea-36cd19b8dd94.png)


##Spreadsheetに運勢を書く
おみくじには運勢の他になにか一言書いてあると思います。それをSpreadsheetから読み込むようにしてみましょう。
参考：[Google Sheets API  \|  Google Developers](https://developers.google.com/sheets/api/reference/rest)
## Spreadsheetに書く
まずは、表示する内容をGoogle Spreadsheetに書きましょう。書くSpreadsheetはBotを作成したSpreadsheetにします。内容はどんなものでもいいですが、以下のように私は書きました。

| fortune     | description                                                                                                     | 
| -------- | -------------------------------------------------------------------------------------------------------- | 
| 大吉     |チョコボ 金のツメが出るレベルのいいことがおきます。                                                   | 
| 中吉     | 無くしていた物が見つかります。机の下や冷蔵庫の裏あたりを探して見てください。                             | 
| 小吉     | 人に親切にすればよいことが起こるでしょう。                                                               | 
| 吉       | ガリガリ君の当たりが出るレベルのいいことがおきます。                                                      | 
| 超大吉   | むちゃくちゃいいことが起こりまくって、あんなことやこんなことが起こります。あなたの望むことは実現できます、ただし、地球の滅亡は救えない。 | 
| 天変地異 | 地面が割れ、天地がひっくりかえります。＼話は聞かせてもらった。地球は滅亡する！！！/                | 


## Spreadsheetから読み込む
プログラム内でどのようにデータを管理するかについては、色々方法がありますが、今回は以下のようにしたいと連想配列を配列でまとめたようなデータの形にしたいと思いました。

```javascript
Logger.log(omikujiData[0]['fortune']);//大吉
Logger.log(omikujiData[0]['description']);//チョコボ 金のツメが出るレベルのいいことがおきます。
```

そのために、スプレッドシートをからデータを取得する部分は以下のようにしました。
```sheetID```は```https://docs.google.com/spreadsheets/d/1yS5RyHEZxEaA0bKrTid8uWMwIdifaQggrWN1EXydb_E/edit```というURLがあったら```1yS5RyHEZxEaA0bKrTid8uWMwIdifaQggrWN1EXydb_E```の部分です。```SpreadsheetApp.openById(sheetID)```で指定のIDのスプレッドシート開いて、```getSheetByName(sheetName)```で指定のシートを開きます。```sheet.getDataRange().getValues()```で値がある範囲の全部のデータを持ってくる事ができます。今回は最初の一行は説明文(運勢とか内容が書かれている)のでそれ以外の行を取得したいので、最後に```slice(1)```をつけました。
getDataRangeには注意点もあるので[GASのgetDataRange\(\)は便利だけど使用する際には注意が必要 \| iwb\.jp](https://iwb.jp/google-apps-script-spreadsheet-getdatarange/?utm_source=rss&utm_medium=rss&utm_campaign=google-apps-script-spreadsheet-getdatarange)を読んでおいたほうがいいです。
ここでは、javscriptにおける、配列操作や連想配列の操作については述べません。以下を参考にすると良いかもしれません。
[Array \- JavaScript \| MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)
[JavaScriptの連想配列 \- 追加削除やソート存在チェックも解説 \| パソナテック](https://www.pasonatech.co.jp/workstyle/column/detail.html?p=2639)

```javascript
//以下の2行は自分の環境に合わせて変えてください
const sheetID = "1yS5RyHEZxEaA0bKrTid8uWMwIdifaQggrWN1EXydb_E";
const sheetName = "シート1";
function loadOmikujiData(){
    let sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);//指定したスプレッドシートのシートを開く
    let rows = sheet.getDataRange().getValues().slice(1);//最初の1行以外を取得をまとめて取得
    let omikujiData = []//結果は配列で返す
    for(const r of rows){
      let fortuneSheet = r[0];//運勢
      let descriptionSheet = r[1];//内容
      omikujiData.push({fortune:fortuneSheet,description:descriptionSheet});//配列に入れるのは連想配列   
    }
    return omikujiData
}
```

今回の追加と変更開所は以下のとおりです。```mikuji()```は前回の内容を全部けして新しいのに入れ替えました。作成したら、一旦```omikuji()```を実行させて、```Logger.log(result);```でちゃんと結果が表示されている確認します。結果が表示されているようであれば、ウエブアプリをデデプロイをして、今度はTelegramから結果を確認します。

```javascript
const sheetID = "1yS5RyHEZxEaA0bKrTid8uWMwIdifaQggrWN1EXydb_E";
const sheetName = "シート1";

function loadOmikujiData(){
    let sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);//指定したスプレッドシートのシートを開く
    let rows = sheet.getDataRange().getValues().slice(1);//最初の1行以外を取得をまとめて取得
    let omikujiData = []//結果は配列で返す
    for(const r of rows){
      let fortuneSheet = r[0];//運勢
      let descriptionSheet = r[1];//内容
      omikujiData.push({fortune:fortuneSheet,description:descriptionSheet});//配列に入れるのは連想配列   
    }
    return omikujiData
}

function omikuji(){
  omikujiData = loadOmikujiData();//スプレッドシートの内容を読み込み
  let selected = getRandomInt(omikujiData.length);//ランダムに選択する
  let result = 'さんの運勢は' + omikujiData[selected]['fortune'] +'です。'+ omikujiData[selected]['description'];//結果用の文字列の作成
　Logger.log(result);
  return result;
}
//telegramに送信する関数も今回はこれを使います。
function sendTelegram(chatId, text) {
  let payload = {
    'method': 'sendMessage',
    'chat_id': chatId,
    'text': text,
    'parse_mode': 'HTML'
  }
  let data = {
    'method': 'post',
    'payload': payload
  }
  UrlFetchApp.fetch('https://api.telegram.org/bot' + botToken + '/', data);
}

function doPost(e) {
  //telegramからのデータ
  let data = JSON.parse(e.postData.contents);
  let text = data.message.text;
  let name = data.message.from.first_name;
  let id   = data.message.chat.id
  let command = deleteAfterAt(text);
  if(command == "/omikuji" ){
    let answer = name +  omikuji();//名前とおみくじの結果をTGに送る
    sendText(id,answer);
  }  
}
```

最後にまとめて、前回のも含めて全部のソースコードを以下に示します。


```javascript
const botToken = "33333333:xxxxxxxxxxxxxxxxxxxx"
consnt telegramUrl = "https://api.telegram.org/bot" + botToken;
const webAppUrl = "https://script.google.com/macros/s/yyyyyyyyyyyyyyyyyyyyyy/exec"
const chat_id = "-510239868"
const sheetID = "1yS5RyHEZxEaA0bKrTid8uWMwIdifaQggrWN1EXydb_E";
const sheetName = "シート1";

function getMe(){
  var url = telegramUrl + "/getMe"
  var response = UrlFetchApp.fetch(url);
  Logger.log(response.getContentText)
}

function hello(){
  sendText(chat_id,"こんにちは！")
}
function sendText(id,text) {
  let url = telegramUrl + "/sendMessage?chat_id=" + id + "&text=" + text;
  Logger.log(url);
  let response = UrlFetchApp.fetch(url);
  Logger.log(response.getContentText());
}
function setWebhook() {
  let url = telegramUrl + "/setWebhook?url=" + webAppUrl;
  Logger.log(url);
  let response = UrlFetchApp.fetch(url);
  Logger.log(response.getContentText());
}

function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}

function deleteAfterAt(str){
  let point = str.indexOf("@");
  if(point>0){
    return str.substring(0,point);
  }else{
    return str;
  }
}


function loadOmikujiData(){
    let sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);
    let rows = sheet.getDataRange().getValues().slice(1);//最初の1行以外を取得
    let omikujiData = []
    for(const r of rows){
      let fortuneSheet = r[0];
      let descriptionSheet = r[1];
      omikujiData.push({fortune:fortuneSheet,description:descriptionSheet});   
    }
    return omikujiData
}


function omikuji(){
  omikujiData = loadOmikujiData();
  let selected = getRandomInt(omikujiData.length);
  let result = 'さんの運勢は' + omikujiData[selected]['fortune'] +'です。'+ omikujiData[selected]['description'];
  //Logger.log(result);
  return result;
}
//telegramに送信する関数も今回はこれを使います。
function sendTelegram(chatId, text) {
  let payload = {
    'method': 'sendMessage',
    'chat_id': chatId,
    'text': text,
    'parse_mode': 'HTML'
  }
  let data = {
    'method': 'post',
    'payload': payload
  }
  UrlFetchApp.fetch('https://api.telegram.org/bot' + botToken + '/', data);
}

function doPost(e) {
  //telegramからのデータ
  let data = JSON.parse(e.postData.contents);
  let text = data.message.text;
  let name = data.message.from.first_name;
  let id   = data.message.chat.id
  let command = deleteAfterAt(text);
  if(command == "/omikuji" ){
    let answer = name +  omikuji();
    sendTelegram(id,answer);
  }  
}

````

# おわりに
以上でスプレッドシートを使ったおみくじの解説を終わりにします。~~実はバグがあって、```sendText()```に送れない文字列があるみたいで、おみくじの書かれている内容によっては結果が全く出ない(sendText()でエラーになるため)こともあります。/とかをエスケープするなりしてうまく表示する方法を考えたほうがいいかもしれません。~~ ```sendTelegram```を用意することで修正しました。
また、このプログラムでは、超大吉がわりと簡単にでるため全くありがたみを感じない仕様になっています。その当たりを修正するには「プログラム レアリティ 調整」で検索すると出てくるようにな以下のサイトを参考にするとよいかもしれません。
[重み付けの抽選を行うアルゴリズム – Lancarse Blog](http://www.lancarse.co.jp/blog/?p=707)
[ガチャプログラムの実装（中級者向け） \- Qiita](https://qiita.com/41semicolon/items/9d4d200c14b09cb28fd9)
あとは、超大吉が出たら、ステッカーを出してBotが祝ってくれるといいですね(ガチャのSSR演出みたいなもの)。それ以外だと天変地異がでたら「地球は滅亡する mmr」の画像を出すと面白いかもしれません。色々とアプリの改善点を見つけてさらに自分の作成するアプリを磨いていければなと思います。