VSCodeで動く天気Botを作り、GASで動く天気Botへ書き換えてみた。

VScodeで動く天気Botの作成に成功したが、VSCodeはその都度サーバーの立ち上げを必要とするのであまり実用的ではないと感じていました(Vercelを使えば永続化できる様なので、後に挑戦する予定です。)。いつでも使えるようにするために、GASであればいつでも動作することから、GASで動く天気Botを作ってみました。
（VScodeで動く天気Botを作成　⇒　GASで動く天気Bot　へ書き換え）
![名称未設定 1のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2165076/5d9e0040-e57a-0d9e-6157-13e27476a319.png)


#作成の流れ
　①VScodeで動く天気Botを作成
　　オウム返しBOTを作成する
　　　1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
　　　https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d#2-nodejs%E3%81%A7bot%E9%96%8B%E7%99%BA
　　　を参考に、オウム返しBOTを作成する。
　　　　１.　Botアカウントを作成する
　　　　２.　Node.jsでBot開発
　　　　３.　ngrokでトンネリング
　　　　４.　天気APIと連携する

　②Google作業
　　　　１.　GoogleAppsScript(GAS)作成
　　　　２.　Line Botとの連携

#実際の作業
##①VScodeで動く天気Botを作成

###１.　Botアカウントを作成する
　（自分のメモがてら、最も基本的な設定から記載したので、初心者以外は読み飛ばしてください。）
　※BOTの登録までは様々なサイトで解説されているので適宜参考にしてください。
　LINEのBotはTwitterなどと違い、通常のユーザーのアカウントをBot化することは出来ず、専用のアカウントを作る必要がある。

####　(1) LINEディベロッパーサイトへログイン
　　　まずはLINE developersのサイトにアクセスし、ログインする。
　　　　https://developers.line.biz/ja/ LINE Developerサイト
　　　（LINEアカウントでログイン、ビジネスアカウントでログイン　のどちらでもOK）

####　(2) プロバイダーの作成
　　　ログインしたらプロバイダーの作成を行う。
　　　プロバイダーは自分が作るLINE BOTなどの開発者名やチーム名、企業名になる。
　　　初回だけディベロッパー登録で個人情報を聞かれるので回答する。

####　(3) Messaging APIの新規チャンネルを作成
　　　チャンネルを作成する。「新規チャンネル作成」をクリックし、
　　　今回はLINE BOTを作るので「Messaging API」を選択。

　　　●アプリアイコン画像(任意): BOTのアイコンになる画像を選ぶ。
　　　●アプリ名（必須）: BOTの名前を入力する。
　　　●アプリ説明（必須）: BOTの説明を入力する。取り急ぎは適当でも大丈夫。
　　　●大業種/小業種（必須）: BOTのカテゴリを選択する。取り急ぎは適当でも大丈夫。
　　　●メールアドレス（必須）: BOTの開発者にLINE側からお知らせがあるときに
　　　　　　　　　　　　　　受け取るメールアドレス。
　　　●プライバシーポリシーURL/サービス利用規約URL（任意）:取り急ぎなくても大丈夫。

　　　同意にチェックと確認して作成

####　(4) Botの設定確認
　　　　　設定を変更する。作ったBOTを選択しウインドウを開き、
　　　　　Basic setingsタグの、Basic informationの下行にある
　　　　　 ”LINE Official Account Manager”をクリック
　　　　　表示されたページ左側の”応答設定”を選択

　　　忘れがちなのがWebhook送信を「利用する」にするところ。
　　　その他は以下の設定にしておくことをオススメする。
　　　　●Webhook送信: 利用する`（設定を忘れがちなので注意）`
　　　　●Botのグループトーク参加: 利用する
　　　　●自動応答メッセージ: 利用しない
　　　　●友達追加時あいさつ: 利用する

####　(5) Botと友達になる
　　　　Messaging APIタグを開き、上部にある「QRコード」を読み込んで、
　　　　自分が作成したBotアカウントと友達になる。
　　　　　スマートフォンの自分のLINEを開き、左下の「ホーム」をタップ
　　　　　→右上の「友だち追加（人+マーク）」をタップ
　　　　　→上段中央の「QRコード」をタップし、QRコードを読み込む
　　　　　→友だち追加をクリック

###２.　Node.jsでBot開発
　　Node.jsをインストール
　　　 https://nodejs.org/ja/ のv16.10.0 LTSを
　　　ダウンロードしてインストール（Windowsの場合）。
　　Visual Studio Codeをインストールする。
　　　https://azure.microsoft.com/ja-jp/products/visual-studio-code/
　　　よりダウンロードしてインストールする。

####　(1) プロジェクトを作成とハローワールド
######　　　(a) Node.jsのプロジェクトはpackage.jsonがあるディレクトリが起点となる。
　　　　　まずはnpm initコマンドでpackage.jsonを作成する。以下の指示を順次実行する。
　　　　　　mkdir mylinebot　　　mylinebotディレクトリ（フォルダ）を作成する。
　　　　　　cd mylinebot　　　　　mylinebotディレクトリ（フォルダ）に移動する。
　　　　　　npm init -y

######　　　(b) 次に依存モジュールを追加する。今回はこちらのSDKを利用する。
　　　　　　npm i @line/bot-sdk express

######　　　(c) 次にプログラムのメインとなるserver.jsをmylinebotフォルダ内に作成する。
　　　　　　type nul > server.js
　　　　　　　エラーメッセージは出るが、mylinebot内にserver.jsは作成される。
　　　　　　　（左上の新規ファイル作成ボタンからserver.jsを作成してもよい）

######　　　(d) エディタでserver.jsを編集
　　　　　　（server.jsを開いて、以下のコードをコピー&ペースト）

```javascript
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '作成したBOTのチャンネルシークレット',
    channelAccessToken: '作成したBOTのチャンネルアクセストークン'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);

    //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }

    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  return client.replyMessage(event.replyToken, {
    type: 'text',
    text: event.message.text //実際に返信の言葉を入れる箇所
  });
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);
```

####　(2) Channel SecretとChannel Access Token　の設定
######　　　(a) Channel SecretとChannel Access Token　の確認と入力
　　　　　●「LINE developers」の画面で、Channel Secret、Channel Access Token　を確認
**　　　　　　　　Channel Secret：**　Basic settingsタグの下の方にある。
**　　　　　　　　Channel Access Token：**　Massaging APIタグの一番下にある
　　　　　　　　　　　　　　　　　　　　（issueボタンを押して確認）。
　　　　　●server.jsのソースコードのchannelAccessTokenとchannelSecretの部分に
　　　　　　値をそれぞれ入力する。

######　　　(b) アプリケーションを起動してみる
　　　　　　ターミナルに　node server.js　と入力し実行する（サーバーを立ち上げる）。
　　　　　　起動すると、ターミナルの次の行に　Server running at 3000　と
　　　　　　ターミナルに表示されることを確認。
　　　　　　ここで問題なければ、ブラウザで　http://localhost:3000
　　　　　　にアクセスすると`Hello LINE BOT!`と表示される。


###３.　ngrokでトンネリング
　　　LINE Botを作るためにはWebhookURLをLINE developersに登録する必要がある。
　　　（windows, macそれぞれ、またはOSのバージョンにより、それぞれ設定が異なる
　　　　ので注意：一番苦労するところかも）
　　　トンネリングツールは、ngrok、localtunnel、serveoなどが有名。今回はngrokを利用。

####　(1) npm経由でngrokをインストール
　　　　ターミナルの右上にある「＋」をクリックし新しいターミナル画面を作成する。
　　　　新しく作成したターミナルに　npm i -g ngrok　と入力し実行。

####　(2) 次にトンネリングサーバーを起動
######　　　(a) トンネリングサーバーを起動する（ここがPCのOSやバージョンにより異なる）
　　　　　　ngrok http ポート名　と指定する。今回はNode.jsアプリケーションを
　　　　　　3000番ポートで利用するので3000を指定。
　　　　　　　　ngrok http 3000　を入力し実行

　　　　**（windowsの場合）**
　　　　　　ngrok http 3000を実行したときにエラーとなる場合は、
　　　　　　npm install -g npx 　と入力し実行。
　　　　　　そのあとに、npx ngrok http 3000　と入力し実行
　　　　**（MACの場合）**色々やり方があるようですが、分かりません。

　　　　　※起動するとターミナルに表示される、
　　　　　　　Forwardingの項のhttps://で始まるアドレスを利用する。
　　　　　　　このアドレスは起動の度に変わる。

######　　　(b) Webhook URLの更新
　　　　　　トンネリングサーバーを起動したターミナル画面で、
　　　　　　Forwardingの項の　”https”の方のURLをコピーする。
　　　　　　「LINE developers」の画面の、Messaging APIタグの
　　　　　　Webhook settingsのWebhook URLに、コピーしたURLを張り付け、
　　　　　　後ろに”/webhook”を付ける。

　この状態でBotにメッセージを送ると、Botがおうむ返ししてくれる。

###（豆知識）
　Visual Studio Codeは画面を閉じるとサーバーが自動的に終了する。
　再び使用するとき（サーバーを再び立ち上げる）は以下の手順。

　ターミナルを2つ開いて、以下の二つのサーバーを立ち上げる。
**　　●　npx ngrok http 3000（npm ngrok http 3000　の方もいる）**
　　　　npx ngrok http 3000　または　npm ngrok http 3000を実行。
**　　●　node server.js**
　　　　cd mylinebot　　を実行（mylinbotフォルダ内に入る）
　　　　node server.js　を実行。

###４.　天気APIと連携する
　　**天気を教えてくれるBOTを作る。**
　　　ライブドアの天気APIが2020年7月にサービス終了、
　　　しかし互換APIを公開してくれている方がいたのでこちらのAPIを利用する。
　　　https://weather.tsukumijima.net/
　　　ivedoor 天気互換の天気予報 API をリリースした話

####　●axiosのインストール
　　　　外部のAPIにアクセスするためにaxiosをmylinebotのプロジェクトに
　　　　インストールする。

```javascript
npm i axios
```
####　●server.jsの冒頭箇所にconst axios = require('axios');を追記する。

```javascript
'use strict';

const axios = require('axios'); //追記
const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;
・
・
・
(省略)
```
####　●server.jsの該当箇所(async function hendleEvent(){}の中)を書き換える。

```javascript
'use strict';

const axios = require('axios');
const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '作成したBOTのチャンネルシークレット',
    channelAccessToken: '作成したBOTのチャンネルアクセストークン'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);

    //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }

    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
                                     　　　　　　　//ここから（書き換え始め）
  const CITY_ID = `130010`; //取得したい地域のIDを指定
  var turl = `https://weather.tsukumijima.net/api/forecast?city=${CITY_ID}`;

  if(event.message.text=="天気"){
        const main = async () => {
        try {
          const res = await axios.get(turl);  //3行を1行にまとめた書き方   
            return client.replyMessage(event.replyToken, {
                type: 'text',
                text: res.data.location.prefecture + 'の今日の天気は、「' + res.data.forecasts[0].detail.weather + '」です。\n'
                +res.data.location.prefecture + 'の明日の天気は、「' + res.data.forecasts[1].detail.weather + '」です。\n'
                +res.data.location.prefecture + 'の明後日の天気は、「' + res.data.forecasts[2].detail.weather + '」です。\n'
                + '\n'+ res.data.description.bodyText         
            });
            /* さらに細かくデータを見る場合は、ここに処理を書く */
        } catch (error) {
            console.error(error);
        }
        }
    main();

    }else if(event.message.text!=="天気"){
         return client.replyMessage(event.replyToken, [{
            type: 'text',
            text:"「天気」と入力してください。"
         },
         {
            type: 'sticker',
            packageId: '11539',
            stickerId: '52114110'
         }]);
    }
                                                  //ここまで（書き換え終わり）
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);
```
地域ごとのIDもライブドアのAPIで利用されていた値がそのまま使えるように作られてるAPIなのでドキュメントページを元に利用したい地域のIDを調べるも可。

LINE Botに「天気」と送ると、天気情報を返してくるか確認する。


##②Google作業
###１.　GoogleAppsScript(GAS)作成
#####　　(1) Googleスプレッドシートを新規作成。
#####　　(2) GoogleAppsScript(GAS)を開く。
　　　　　上のメニューバーの「拡張機能」→Apps Script
　　　　　（以前は「データ」→スクリプトエディタ　であったが
　　　　　　2021年11月初めより上記へ変わった。）
#####　　(3) VSCodeからGAS用に書き換えた下記コードをコピー＆ペースト。
　　　　　チャンネルアクセストークンをLINE Developerよりコピーし、
　　　　　コード内へペースト。

```javascript
var CHANNEL_ACCESS_TOKEN = '自分のチャンネルアクセストークンを入れる';

function doPost(e) {
 var post_json = JSON.parse(e.postData.contents);
 var reply_token = post_json.events[0].replyToken;
 if (typeof reply_token === 'undefined') {
   return;
 }
 var message = post_json.events[0].message.text;
 const CITY_ID = `130010`; //取得したい地域のIDを指定
 var turl = `https://weather.tsukumijima.net/api/forecast?city=${CITY_ID}`;
 var url = 'https://api.line.me/v2/bot/message/reply';
 
 if(post_json.events[0].message.text==="天気") {
  const main = async () => {
    try {
        var res = UrlFetchApp.fetch(turl).getContentText();  // 天気APIを叩く
        var json = JSON.parse(res);
        json.description.text

        UrlFetchApp.fetch(url, {  // LINE BotのAPIを叩き返信
          'headers': {
            'Content-Type': 'application/json; charset=UTF-8',
            'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
          },
          'method': 'post',
          'payload': JSON.stringify({
            'replyToken': reply_token,
            'messages': [{
              'type': 'text',
              'text': json.location.prefecture + 'の今日の天気は、「' + json.forecasts[0].detail.weather + '」です。\n'
            +json.location.prefecture + 'の明日の天気は、「' + json.forecasts[1].detail.weather + '」です。\n'
            +json.location.prefecture + 'の明後日の天気は、「' + json.forecasts[2].detail.weather + '」です。\n'
            + '\n'+ json.description.bodyText,
            }],
          }),
        });

    } catch (error) {
        console.error(error);

    }
  }

main();

 }else{
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
      'payload': JSON.stringify({
        'replyToken': reply_token,
        'messages': [{
            type: 'text',
            text: "「天気」と入力してください。",
          },
          {
            type: 'sticker',
            packageId: '11539',
            stickerId: '52114110'
          }],
      }),
  });
}

 return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}
```

####　(4) 公開
　　**GASを公開可能にする。**
　　**●やり方①　新しいエディタの場合**
　　　右上の「デプロイ」ボタンを押し、「新しいデプロイ」を選択。
　　　開いたウインドウの左上の「種類の選択」の⚙を押し、「ウェブアプリ」を選択。
　　　出現した画面下のアクセスできるユーザーを「全員」に変更し、
　　　右下の「デプロイ」ボタンを押せば公開終了。
　　　数秒後に開いたウインドウのウェブアプリURLをコピーする。

　　**●やり方②　以前のエディタの場合**
　　　右上の「以前のエディタへ切替え」ボタンを押す。
　　　切り替わったら上のメニューバーの「公開」メニューから
　　　「ウェブアプリケーションとして導入」を選択。
　　　アプリケーションにアクセスできるユーザーを「全員(匿名ユーザーを含む)」
　　　にして導入ボタンを押せば、公開完了。
　　　最後に現在のウェブアプリケーションのURL（Current web app URL）の
　　　画面が出るので、コピーする。

###２.　Line Botとの連携
　　　コピーしたウェブアプリURL
　　（以前のエディタの場合は、現在のウェブアプリケーションのURL
　　（Current web app URL））を、LINE Developerコンソール画面を開き、
　　　Webhook設定に張り付ける。
　　　　（node(VSCode)と違い”/webhook”は後ろに付けない。）


##学んだ箇所、工夫を要した箇所
　① GASではAPIを呼び出す構文が　UrlFetchApp.fetch(　) と改めて学び、
　　getContentTextを付け、JSON.parseと処理することにより中の情報を
　　呼び込めることを学んだ。
　　　　var res = UrlFetchApp.fetch(turl).getContentText();  // 天気APIを叩く
　　　　var json = JSON.parse(res);
　　　　json.description.text
　　これを使い、VSCodeでの「res.data」を「json」と頭文字を書き換える
　　ことによりデータを扱うことができるようになった。

　②返信内容がすべて同じ種類（テキスト）であればPOSTに構文を
　　まとめて記載する方法は検索の範囲では散見されたが、異なる種類
　　（テキスト+スタンプ　等）の場合の書き方はわからなかった。
　　そこで〔参考にしたHP〕①と④を参考に、if分の書くパートに
　　LINE Botへの返信コードを記載することにより、それぞれ独立して
　　違う種類（テキスト+スタンプ　等）の返信をすることができた。
　　これにより、確認型テンプレートメッセージ、ボタン型テンプレート
　　メッセージなどを組み合わせることにより、様々な応用ができるように
　　なったと考える。
　　今回、色々試行錯誤、模索し動作に成功したが、恐らくもっと
　　上手な書き方はあると思われる。

##〔参考にしたHP〕
####①　1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d#%E8%A3%9C%E8%B6%B3%E8%B3%87%E6%96%99%E3%81%9D%E3%81%AE2---%E5%A4%A9%E6%B0%97api%E3%82%92%E4%BD%BF%E3%81%86%E5%BF%9C%E7%94%A8-2021614%E6%9B%B4%E6%96%B0

####②　GAS初心者がLINE Notifyを使って天気予報BOTを作ってみた
https://www.interfactory.co.jp/tech/gas-line-notify/

####③　【LINE BOT】Messaging APIで天気予報通知を作ろう
https://ienokado-blog.com/2021/05/17/line-bot-develop/

####④　GASを使って天気予報botを作ってみよう！
https://qiita.com/shinbunbun_/items/23e2100abbe0fcd0274c

####⑤Google Apps Scriptでオウム返しLINE Botを作る。
https://qiita.com/tetrapod117/items/e7b48485c98f6b88f311
　
　
##【　更に発展　】
**OpenWeatherMapのデータも併せて表示する。**
　　OpenWeatherMapのAPIキーを取得（以下のHPを参照）
　　　https://yuukiyg.hatenablog.jp/entry/2019/11/17/182410

**〔VSCodeは以下のコードをコピー＆ペースト〕**
　　チャンネルシークレット、チャンネルアクセストークン、
　　OpenWeatherMapのAPIキーの情報を書き換える。

```javascript
'use strict';

const axios = require('axios');
const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '作成したBOTのチャンネルシークレット',
    channelAccessToken: '作成したBOTのチャンネルアクセストークン'
};

const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);

    //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }

    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
                                                  //ここから（書き換え始め）

  var message = event.message.text
  const cityname = 'Tokyo'//取得したい地域を指定
  var owurl = 'http://api.openweathermap.org/data/2.5/weather?q='+cityname+'&units=metric&APPID={自分のAPIキー}'
  
  const CITY_ID = `130010`; //取得したい地域のIDを指定
  var turl = `https://weather.tsukumijima.net/api/forecast?city=${CITY_ID}`;

  const owres = await axios.get(owurl);   
  const tres = await axios.get(turl);   

    let currentWeather = owres.data.weather[0].main;
    let description = owres.data.weather[0].description;
    let temp = owres.data.main.temp;
    let temp_max = owres.data.main.temp_max;
    let temp_min = owres.data.main.temp_min;
    let city = owres.data.name;

    var currentDate=new Date();
    var y=currentDate.getFullYear();
    var mo=currentDate.getMonth()+1;
    var d=currentDate.getDate();
    mo = ('0' + mo).slice(-2); // 1桁の数字を0埋めで2桁にする
    d =('0' + d).slice(-2);    // 1桁の数字を0埋めで2桁にする
    var h=currentDate.getHours();
    var m=currentDate.getMinutes();
    h =('0' + h).slice(-2);    // 1桁の数字を0埋めで2桁にする
    m =('0' + m).slice(-2);    // 1桁の数字を0埋めで2桁にする
    var today=y + '年' + mo + '月' + d + '日'
    var str=h+":"+m;
                                            //OpenWeatherMapは英語なので日本語化
  let weather_ja = '晴れ'
    switch (currentWeather){
        case 'Clear':
          weather_ja = '晴れ';
          break;
        case 'Clouds':
          weather_ja = 'くもり';
          break;
        case 'Mist':
          weather_ja = 'もや';
          break;
        case 'Smoke':
          weather_ja = '煙';
          break;
        case 'Dust':
          weather_ja = 'ほこり';
          break;
        case 'Haze':
          weather_ja = 'かすみ';
          break;
        case 'Sand':
          weather_ja = '砂';
          break;
        case 'Ash':
          weather_ja = '灰';
          break;
        case 'Tornado':
          weather_ja = '竜巻';
          break;
        case 'Rain':
          weather_ja = '雨';
          break;
        case 'Snow':
          weather_ja = '雪';
          break;
        case 'Thunderstorm':
          weather_ja = '雷雨';
        case 'Drizzle':
          weather_ja = '霧雨';
          break;
        case 'Fog':
          weather_ja = '霧';
          break;
        case 'Squall':
          weather_ja = 'スコール';
          break;
    }

  let description_ja = 'なし'
    switch (description){
        case 'thunderstorm with light rain':
          description_ja = '小雨で雷雨';
          break;
        case 'thunderstorm with rain':
          description_ja = '雨で雷雨';
          break;
        case 'thunderstorm with heavy rain':
          description_ja = '大雨で雷雨';
          break;
        case 'light thunderstorm':
          description_ja = '軽い雷雨';
          break;
        case 'thunderstorm':
          description_ja = '雷雨';
          break;
        case 'heavy thunderstorm':
          description_ja = '激しい雷雨';
          break;
        case 'ragged thunderstorm':
          description_ja = '不規則な雷雨';
          break;
        case 'thunderstorm with light drizzle':
          description_ja = '軽い霧雨と雷雨 ';
          break;
        case 'thunderstorm with drizzle':
          description_ja = '霧雨と雷雨';
          break;
        case 'thunderstorm with heavy drizzle':
          description_ja = '激しい霧雨で雷雨';
          break;
        case 'light intensity drizzle':
          description_ja = '弱い霧雨';
          break;
        case 'drizzle':
          description_ja = '霧雨';
          break;
        case 'heavy intensity drizzle':
          description_ja = '強い霧雨';
          break;
        case 'light intensity drizzle rain':
          description_ja = '弱い霧雨';
          break;
        case 'drizzle rain':
          description_ja = '霧雨';
          break;
        case 'heavy intensity drizzle rain':
          description_ja = '激しい霧雨';
          break;
        case 'shower rain and drizzle':
          description_ja = 'にわか雨と霧雨';
          break;
        case 'heavy shower rain and drizzle':
          description_ja = '強いにわか雨と霧雨';
          break;
        case 'shower drizzle':
          description_ja = 'にわか霧雨';
          break;
        case 'light rain':
          description_ja = '小雨';
          break;
        case 'moderate rain':
          description_ja = '中程度の雨';
          break;
        case 'heavy intensity rain':
          description_ja = '大雨';
          break;
        case 'very heavy rain':
          description_ja = '強い大雨';
          break;
        case 'extreme rain':
          description_ja = 'とても強い大雨';
          break;
        case 'freezing rain':
          description_ja = '凍雨';
          break;
        case 'light intensity shower rain':
          description_ja = '弱いにわか雨';
          break;
        case 'shower rain':
          description_ja = 'にわか雨';
          break;
        case 'heavy intensity shower rain':
          description_ja = '激しいにわか雨';
          break;
        case 'ragged shower rain':
          description_ja = '不規則なにわか雨';
          break;
        case 'light snow':
          description_ja = '軽い雪';
          break;
        case 'Snow':
          description_ja = '雪';
          break;
        case 'Heavy snow':
          description_ja = '大雪';
          break;
        case 'Sleet':
          description_ja = '霙(みぞれ)';
          break;
        case 'Light shower sleet':
          description_ja = '弱いにわか霙(みぞれ)';
          break;
        case 'Shower sleet':
          description_ja = 'にわか霙(みぞれ)';
          break;
        case 'Light rain and snow':
          description_ja = '小雨と雪';
          break;
        case 'Rain and snow':
          description_ja = '雨と雪';
          break;
        case 'Light shower snow':
          description_ja = '弱いにわか雪';
          break;
        case 'Shower snow':
          description_ja = 'にわか雪';
          break;
        case 'Heavy shower snow':
          description_ja = '強いにわか雪';
          break;
        case 'mist':
          description_ja = '靄（もや）';
          break;
        case 'Smoke':
          description_ja = '煙';
          break;
        case 'Haze':
          description_ja = '霞（かすみ）';
          break;
        case 'sand/ dust whirls':
          description_ja = '砂/塵旋風';
          break;
        case 'fog':
          description_ja = '霧（きり）';
          break;
        case 'sand':
          description_ja = '砂';
          break;
        case 'dust':
          description_ja = ' 塵';
          break;
        case 'volcanic ash':
          description_ja = '火山灰';
          break;
        case 'squalls':
          description_ja = 'スコール';
          break;
        case 'tornado':
          description_ja = '竜巻';
          break;
        case 'clear sky':
          description_ja = '澄んだ空';
          break;
        case 'few clouds':
          description_ja = '少しの雲';
          break;
        case 'scattered clouds':
          description_ja = '散乱雲';
          break;
        case 'broken clouds':
          description_ja = '壊れた雲';
          break;
        case 'overcast clouds':
          description_ja = '曇り雲';
          break;
    }
  
  if(event.message.text=="天気"){
        const main = async () => {
        try {
            console.log(tres.data);
            return client.replyMessage(event.replyToken, {
                type: 'text',
                text: today + ' ' + str + ' ' + city + '\n' +'現在の天気は「'+ weather_ja +'」です。\n'+ '詳細：' + description_ja +'\n' + '現在気温' + temp  +'℃、最高気温'+temp_max+'℃、最低気温'+temp_min+'℃'+'\n'
                +'\n' 
                + tres.data.location.prefecture + 'は本日「' + tres.data.forecasts[0].detail.weather + '」\n'
                +'明日「' + tres.data.forecasts[1].detail.weather + '」\n'
                +'明後日「' + tres.data.forecasts[2].detail.weather + '」です。\n'
                + '\n'+ tres.data.description.bodyText 
            });
            /* さらに細かくデータを見る場合は、ここに処理を書く */
        } catch (error) {
            console.error(error);
        }
        }
        main();

    }else if(event.message.text!=="天気"){
         return client.replyMessage(event.replyToken, [{
            type: 'text',
            text:"「"+message+"」ではなく、"+"「天気」と入力してください。",
         },
         {
            type: 'sticker',
            packageId: '11539',
            stickerId: '52114110'
         }]);
    }
                                                  //ここまで（書き換え終わり）
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);
```

**〔GASは以下のコードをコピー＆ペースト〕**
　　チャンネルアクセストークン、
　　OpenWeatherMapのAPIキーの情報を書き換える。

```javascript

var CHANNEL_ACCESS_TOKEN = '作成したBOTのチャンネルアクセストークン';

function doPost(e) {
 var post_json = JSON.parse(e.postData.contents);
 var reply_token = post_json.events[0].replyToken;
 if (typeof reply_token === 'undefined') {
   return;
 }
  var message = post_json.events[0].message.text;
  const cityname = 'Tokyo'//取得したい地域を指定
  var owurl = 'http://api.openweathermap.org/data/2.5/weather?q='+cityname+'&units=metric&APPID={自分のAPIキー}'

  const CITY_ID = `130010`; //取得したい地域のIDを指定
  var turl = `https://weather.tsukumijima.net/api/forecast?city=${CITY_ID}`;

  var url = 'https://api.line.me/v2/bot/message/reply';
 
  var owres = UrlFetchApp.fetch(owurl).getContentText();  // OpenWeatherMapのAPIを叩く
  var owjson = JSON.parse(owres);
  owjson.text

  var tres = UrlFetchApp.fetch(turl).getContentText();  // 天気APIを叩く
  var tjson = JSON.parse(tres);
  tjson.description.text
                                            //OpenWeatherMapは英語なので日本語化
    let currentWeather = owjson.weather[0].main;
    let description = owjson.weather[0].description;
    let temp = owjson.main.temp;
    let temp_max = owjson.main.temp_max;
    let temp_min = owjson.main.temp_min;
    let city = owjson.name;

    var currentDate=new Date();
    var y=currentDate.getFullYear();
    var mo=currentDate.getMonth()+1;
    var d=currentDate.getDate();
    mo = ('0' + mo).slice(-2); // 1桁の数字を0埋めで2桁にする
    d =('0' + d).slice(-2);    // 1桁の数字を0埋めで2桁にする
    var h=currentDate.getHours();
    var m=currentDate.getMinutes();
    h =('0' + h).slice(-2);    // 1桁の数字を0埋めで2桁にする
    m =('0' + m).slice(-2);    // 1桁の数字を0埋めで2桁にする
    var today=y + '年' + mo + '月' + d + '日'
    var str=h+":"+m;

  let weather_ja = '晴れ'
    switch (currentWeather){
        case 'Clear':
          weather_ja = '晴れ';
          break;
        case 'Clouds':
          weather_ja = 'くもり';
          break;
        case 'Mist':
          weather_ja = 'もや';
          break;
        case 'Smoke':
          weather_ja = '煙';
          break;
        case 'Dust':
          weather_ja = 'ほこり';
          break;
        case 'Haze':
          weather_ja = 'かすみ';
          break;
        case 'Sand':
          weather_ja = '砂';
          break;
        case 'Ash':
          weather_ja = '灰';
          break;
        case 'Tornado':
          weather_ja = '竜巻';
          break;
        case 'Rain':
          weather_ja = '雨';
          break;
        case 'Snow':
          weather_ja = '雪';
          break;
        case 'Thunderstorm':
          weather_ja = '雷雨';
        case 'Drizzle':
          weather_ja = '霧雨';
          break;
        case 'Fog':
          weather_ja = '霧';
          break;
        case 'Squall':
          weather_ja = 'スコール';
          break;
    }

  let description_ja = 'なし'
    switch (description){
        case 'thunderstorm with light rain':
          description_ja = '小雨で雷雨';
          break;
        case 'thunderstorm with rain':
          description_ja = '雨で雷雨';
          break;
        case 'thunderstorm with heavy rain':
          description_ja = '大雨で雷雨';
          break;
        case 'light thunderstorm':
          description_ja = '軽い雷雨';
          break;
        case 'thunderstorm':
          description_ja = '雷雨';
          break;
        case 'heavy thunderstorm':
          description_ja = '激しい雷雨';
          break;
        case 'ragged thunderstorm':
          description_ja = '不規則な雷雨';
          break;
        case 'thunderstorm with light drizzle':
          description_ja = '軽い霧雨と雷雨 ';
          break;
        case 'thunderstorm with drizzle':
          description_ja = '霧雨と雷雨';
          break;
        case 'thunderstorm with heavy drizzle':
          description_ja = '激しい霧雨で雷雨';
          break;
        case 'light intensity drizzle':
          description_ja = '弱い霧雨';
          break;
        case 'drizzle':
          description_ja = '霧雨';
          break;
        case 'heavy intensity drizzle':
          description_ja = '強い霧雨';
          break;
        case 'light intensity drizzle rain':
          description_ja = '弱い霧雨';
          break;
        case 'drizzle rain':
          description_ja = '霧雨';
          break;
        case 'heavy intensity drizzle rain':
          description_ja = '激しい霧雨';
          break;
        case 'shower rain and drizzle':
          description_ja = 'にわか雨と霧雨';
          break;
        case 'heavy shower rain and drizzle':
          description_ja = '強いにわか雨と霧雨';
          break;
        case 'shower drizzle':
          description_ja = 'にわか霧雨';
          break;
        case 'light rain':
          description_ja = '小雨';
          break;
        case 'moderate rain':
          description_ja = '中程度の雨';
          break;
        case 'heavy intensity rain':
          description_ja = '大雨';
          break;
        case 'very heavy rain':
          description_ja = '強い大雨';
          break;
        case 'extreme rain':
          description_ja = 'とても強い大雨';
          break;
        case 'freezing rain':
          description_ja = '凍雨';
          break;
        case 'light intensity shower rain':
          description_ja = '弱いにわか雨';
          break;
        case 'shower rain':
          description_ja = 'にわか雨';
          break;
        case 'heavy intensity shower rain':
          description_ja = '激しいにわか雨';
          break;
        case 'ragged shower rain':
          description_ja = '不規則なにわか雨';
          break;
        case 'light snow':
          description_ja = '軽い雪';
          break;
        case 'Snow':
          description_ja = '雪';
          break;
        case 'Heavy snow':
          description_ja = '大雪';
          break;
        case 'Sleet':
          description_ja = '霙(みぞれ)';
          break;
        case 'Light shower sleet':
          description_ja = '弱いにわか霙(みぞれ)';
          break;
        case 'Shower sleet':
          description_ja = 'にわか霙(みぞれ)';
          break;
        case 'Light rain and snow':
          description_ja = '小雨と雪';
          break;
        case 'Rain and snow':
          description_ja = '雨と雪';
          break;
        case 'Light shower snow':
          description_ja = '弱いにわか雪';
          break;
        case 'Shower snow':
          description_ja = 'にわか雪';
          break;
        case 'Heavy shower snow':
          description_ja = '強いにわか雪';
          break;
        case 'mist':
          description_ja = '靄（もや）';
          break;
        case 'Smoke':
          description_ja = '煙';
          break;
        case 'Haze':
          description_ja = '霞（かすみ）';
          break;
        case 'sand/ dust whirls':
          description_ja = '砂/塵旋風';
          break;
        case 'fog':
          description_ja = '霧（きり）';
          break;
        case 'sand':
          description_ja = '砂';
          break;
        case 'dust':
          description_ja = ' 塵';
          break;
        case 'volcanic ash':
          description_ja = '火山灰';
          break;
        case 'squalls':
          description_ja = 'スコール';
          break;
        case 'tornado':
          description_ja = '竜巻';
          break;
        case 'clear sky':
          description_ja = '澄んだ空';
          break;
        case 'few clouds':
          description_ja = '少しの雲';
          break;
        case 'scattered clouds':
          description_ja = '散乱雲';
          break;
        case 'broken clouds':
          description_ja = '壊れた雲';
          break;
        case 'overcast clouds':
          description_ja = '曇り雲';
          break;
    }

 if(post_json.events[0].message.text==="天気") {
  const main = async () => {
    try {
        UrlFetchApp.fetch(url, {  // LINE BotのAPIを叩き返信
          'headers': {
            'Content-Type': 'application/json; charset=UTF-8',
            'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
          },
          'method': 'post',
          'payload': JSON.stringify({
            'replyToken': reply_token,
            'messages': [{
              'type': 'text',
              'text': today + ' ' + str + ' ' + city + '\n' +'現在の天気は「'+ weather_ja +'」です。\n'+ '詳細：' + description_ja +'\n' + '現在気温' + temp  +'℃、最高気温'+temp_max+'℃、最低気温'+temp_min+'℃'+'\n'
                +'\n' 
                + tjson.location.prefecture + 'は本日「' + tjson.forecasts[0].detail.weather + '」\n'
                +'明日「' + tjson.forecasts[1].detail.weather + '」\n'
                +'明後日「' + tjson.forecasts[2].detail.weather + '」です。\n'
                + '\n'+ tjson.description.bodyText,
            }],
          }),
        });

    } catch (error) {
        console.error(error);

    }
  }

main();

 }else{
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
    },
    'method': 'post',
      'payload': JSON.stringify({
        'replyToken': reply_token,
        'messages': [{
            type: 'text',
            text: "「"+message+"」ではなく、"+"「天気」と入力してください。",
          },
          {
            type: 'sticker',
            packageId: '11539',
            stickerId: '52114110'
          }],
      }),
  });

}
 return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}
```