芝生が好きすぎるので、芝活が捗るLINE botを作ってしまった。

# 芝生はいいぞ...

突然ですがみなさん芝生は好きですか？
僕はと言いますと、他人にドンびかれるくらい芝生を愛しています。
暇さえあればホーム芝生で酒を飲んでますし、土日は遠征して一日中芝生で過ごすみたいな生活をしています。
冗談抜きに、芝生にいくと人生が好転するので、みなさんにも味わってほしいのです。

# 実は、良質な芝生と出会うのは難しい

「さっそく芝生に行ってきてください！」と言いたいのですが、
実は良い芝生に出会うのはなかなか難しかったりします。
特に東京で見つけるのは至難の技で、
僕も上京したばかりの頃は失敗を繰り返して鬱になりそうでした。
東京で良い芝生を見つけるのが難しい理由は大きく分けて２つあります。
- 小規模の芝が無数にあり、かつ整備の質の差が激しい。
- 質の高い芝生でも、養生中で入れなかったり、季節によってコンディションに差が出る

<img width="400" alt="IMG_4673.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281755/ec4ac00c-42c4-f488-73b3-89e3babc4159.png">

＊ちなみに＊
芝生には夏芝と冬芝の２種類があり、冬の夏芝は完全に枯れてしまいます。
まれにコンディションが悪い芝生を好む変わり者もいます（僕）

# 提供したいもの

みなさんに良質な芝生でぼんやりする最高な体験をしてほしいのです。
そのために、こんなものを提供したいなあと思いました。
- 僕が足を運んで自信を持っておすすめできる芝生を区ごとに教える
- 事前に芝生のコンディションの良し悪しもお伝えしてがっかり体験を未然に防ぐ

# 芝生ポータル爆誕
という感じで、芝生ポータルが生まれました。
芝生に行きたい時はまずはここに訪れていただきたいです。

https://twitter.com/atoms50/status/1577234278785896448?s=20&t=mNwO4Az_rArcq1zPgetuDg

機能としては、
- 東京２３区のいずれかを入力するとおすすめする芝生を複数送ってくれる
- 場所に加えておすすめポイントも教えてくれる
- 芝生のコンディションが良いか悪いかの判定をし、良いものだけをおすすめする

こちらのQRを読み込むと使えるのでぜひ友達登録してください。
<img width="300" alt="IMG_4673.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281755/b74b21d2-9033-6c8a-3911-dca6118a8f0f.png">

# 環境
node v18.8.0
Visual Studio Code v1.71.0
axios 0.27.2

# 利用API
- **LINE Messaging API**<br>
Node.jsでLINE BOTを作る方法はこちらを参考にしました<BR>
[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)<br><br>
- **Google Sheets API**<br>
    過去の情報を保有するためにGoogleスプレッドシートを使いました。
    - Googleスプレッドシートの導入部分はこちらを参考<br>
      [Node.js googleapis npmパッケージで Google スプレッドシートを await/async で読み取るメモ　〜1ft-seabass.jp.MEMO](https://www.1ft-seabass.jp/memo/2020/05/08/npm-googleapis-google-sheet-await-async/) <br><br>
    - データの読み書きはこちらのサイトを参考<br>
[【Node.js + Sheets API v4】Googleスプレッドシートを読み書きする](https://qiita.com/vicugna-pacos/items/f7bb0d97bbaa1371edc8)<BR>

# GoogleSpreadSheetの用意
こんな感じのシートを用意します。
C列のコンディションが「良い」ものだけ、B列のURLを送信します。
（まだ江東区と文京区しかありませんが、これから増えます。。。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281755/47db26fa-9b49-c146-f58c-5027481975dc.png)



# サンプルコード

```JavaScript:shibafu_sample
'use strict';

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');

// ローカル（自分のPC）でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIで利用するクレデンシャル（秘匿情報）です。
const config = {
    channelSecret: '##########',
    channelAccessToken: '##########'
};

// Google 公式の npm googleapis
// https://www.npmjs.com/package/googleapis
let {google} = require('googleapis');
 
// ダウンロードしたJSON の鍵ファイルの中身をコピーアンドペースト
const creds = {
    "type": "service_account",
    "project_id": "##########",
    "private_key_id": "##########",
    "private_key": "##########",
    "client_email": "##########",
    "client_id": "##########",
    "auth_uri": "##########",
    "token_uri": "##########",
    "auth_provider_x509_cert_url": "##########",
    "client_x509_cert_url": "##########"
  };
 
// JSON Web Token(JWT)の設定
let jwtClient = new google.auth.JWT(
  creds.client_email,
  null,
  creds.private_key,
  ['https://www.googleapis.com/auth/spreadsheets',
   'https://www.googleapis.com/auth/drive']
);
 
// シートのURLから抽出したID 
const sheet = '##########';
 
// スプレッドシートAPIはv4を使う
let sheets = google.sheets('v4');

// 芝生関数
const shibafuFunction = async (event) => {

  // JSON Web Token(JWT) の認証
  let resultJwtClient;
  try {
    resultJwtClient = await jwtClient.authorize();
    // console.log(resultJwtClient);
  } catch (error) {
    console.log("Auth Error: " + error);
  }
 
  // シートを読み込む
  let cells = event.message.text;
  let responseGetSheet;
  try {
    responseGetSheet = await sheets.spreadsheets.values.get({
      auth: jwtClient,
      spreadsheetId: sheet,
      range: cells
    });

    console.log()

    const items = responseGetSheet.data.values;

    let msgObjs = []; 

    msgObjs = [
    { type: 'text', text: `${event.message.text}のコンディションが良い芝生だよ！` },
    ]

    for(let i = 0; i < items.length; i++){
        if(items[i][2] == "良い"){
        msgObjs.push({ type: 'text', text: `${items[i][3]} \n ${items[i][1]}` })      
        }
    }
    return client.replyMessage(event.replyToken, msgObjs);

    } catch (error) {
    return client.replyMessage(event.replyToken, {
        type: 'text',
        text: '検索したい芝生の区を入力してね'
      });
  }

  };


// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // 芝生関数を実行します
    return shibafuFunction(event)
}



// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
  
  // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
  if (req.body.events.length === 0) {
    res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します（なくてもよい）
    console.log('検証イベントを受信しました！'); // ターミナルに表示します
    return; // これより下は実行されません
  } else {
    // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します
    console.log('受信しました:', req.body.events);
  }

  // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
  // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
  Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// （環境によってはローカルネットワーク内にも公開されます）
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

```

# 今後の展開について
僕のおすすめだけでは限界があるなと感じています。
東京にはまだまだ良質な芝生がたくさんあるでしょうし、
芝生のコンディションを逐一更新するのが難しいためです。


- 登録機能：LINEbotに送信した情報をスプレッドシートに新規で追加する
- 更新機能：送信されたコンディション情報でスプレッドシートの情報を更新する

といった機能を追加して、
みなさんと一緒に芝生ポータルをより良いものにしていきたいなと思っています。