【Node.js, TypeScript】天気予報アプリをLINE MessageAPIで作ってみた！

先日、Node.jsでアプリを天気予報アプリを作成しました。

https://qiita.com/Ryo9597/items/bf93618663f28e9d8f7f

今回はNode.jsとTypeScriptで作ってみました。

完成形としては以下の通りです。
![名称未設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/0af0bfce-b695-b341-4e90-aeefde544562.png)

以前作成したLaravelに関してはこちらからどうぞ。

https://qiita.com/Ryo9597/items/bcb807688f35d6a72861

## 追記（2021/06/22）
AWSでデプロイしました。
AWS勉強中の方はこちらの記事もどうぞ！

https://qiita.com/Ryo9597/items/3fd188c4a8c7089c12f8

## 追記(2021/07/14)
AWSのLambdaを使ってサーバレスアプリケーションとしてデプロイしました。
インフラ初心者の方に関しては、上記のEC2を使った構成よりも簡単なのでぜひチャレンジしてみてください。

https://qiita.com/Ryo9597/items/0f82cddb98227d308a9a

## 追記(2021/06/24)
新たに、現在地から美味しいお店を探すアプリをLINE Messaging APIで作ってみました！
こちらでは、Google Maps APIを使用しています。
ぜひこちらの記事もどうぞ！

https://qiita.com/Ryo9597/items/4cd1908e5bd41467cd52

## どのようなアプリか
皆さんは、今日の気温を聞いて、**「快適に過ごすために今日のファッションをこうしよう」**ってパッと思いつきますか？

私は、最高気温、最低気温を聞いてもそれがどのくらい暑いのか、寒いのかがピンと来ず、洋服のチョイスを外したことがしばしばあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/c9583e2c-1e6c-a662-e7cf-b9995ca8e3cb.png)

こんな思いを2度としないために今回このアプリを作りました。

`line-bot-sdk-nodejs`の型定義で多少躓きましたが、TypeScript初心者でもそこまで時間かからずにできるかと思います。
なので、TypeScriptを勉強中の方はぜひ取り組んでみてください。

https://github.com/line/line-bot-sdk-nodejs/blob/master/lib/types.ts

## アプリの流れ
アプリの流れは大まかに以下の4つのステップで成り立っています。

・①クライアントが現在地を送る
・②[OpenWeather](https://openweathermap.org/)から天気予報を取得
・③データの整形
・④クライアントに送る

## GitHub
完成形のコードは以下となります。

https://github.com/ssk9597/LINE-Node-TypeScript-Weather

## では実際に作成していきましょう！
### `LINE Developers`にアカウントを作成する
[LINE Developers](https://developers.line.biz/ja/)にアクセスして、「ログイン」ボタンをクリックしてください。

その後諸々入力してもらったら以下のように作成できるかと思います。
注意事項としては、今回`Messaging API`となるので、**チャネルの種類**を間違えた方は修正してください。

![スクリーンショット 2021-05-29 16.13.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/c255d10d-2dcf-15ed-1fea-fdbeed6da750.png)

**チャネルシークレット**と**チャネルアクセストークン**が必要になるのでこの2つを発行します。

![スクリーンショット 2021-05-29 16.16.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/ede74ac9-add0-c180-016d-efcab62e912a.png)

![スクリーンショット 2021-05-29 16.17.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/b5c7bf10-1726-28df-0eec-99a1447f7e11.png)

ではこの2つを`.env`に入力します。

```env:.env
LINE_CHANNEL_SECRET=abcdefg123456
LINE_CHANNEL_ACCESS_TOKEN=HogeHogeHoge123456789HogeHogeHoge
```

### `package.json`の作成
以下のコマンドを入力してください。
これで、`package.json`の作成が完了します。

```terminal:ターミナル
$ npm init -y
```

### 必要なパッケージのインストール
#### dependencies
`dependencies`はすべてのステージで使用するパッケージです。

今回使用するパッケージは以下の4つです。
・@line/bot-sdk
・express
・dotenv
・axios

以下のコマンドを入力してください。
これで全てのパッケージがインストールされます。

```terminal:ターミナル
$ npm install @line/bot-sdk express dotenv axios --save
```

#### devDependencies
`devDependencies`はコーディングステージのみで使用するパッケージです。

今回使用するパッケージは以下の7つです。
・typescript
・@types/node
・@types/express
・ts-node
・ts-node-dev
・rimraf
・npm-run-all

以下のコマンドを入力してください。
これで全てのパッケージがインストールされます。

```terminal:ターミナル
$ npm install -D typescript @types/node @types/express ts-node ts-node-dev rimraf npm-run-all
```

### `package.json`にコマンドの設定を行う

`npm run dev`が開発環境の立ち上げに使います。
`npm run start`が本番環境用です。

```json:package.json
{
  "scripts": {
    "dev": "ts-node-dev --respawn api/src/index.ts",
    "clean": "rimraf dist",
    "tsc": "tsc",
    "build": "npm-run-all clean tsc",
    "start": "npm run build && node ."
  },
}
```

### `tsconfig.json`の作成
以下のコマンドを実行しTypeScriptの初期設定を行います。

```terminal:ターミナル
$ npx tsc --init
```

それでは、作成された`tsconfig.json`の上書きをしていきます。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./api/dist",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["api/src/**/**/*"]
}
```

簡単にまとめると、
`api/src`ディレクトリ以下を対象として、それらを`api/dist`ディレクトリにES6の書き方でビルドされるという設定です。

tsconfig.jsonに関して詳しく知りたい方は以下のサイトをどうぞ。

https://book.yyts.org/handson/tsconfig.json-settings


また、この辺りで必要ないディレクトリはGithubにpushしたくないので、`.gitignore`も作成しておきましょう。

```.gitignore:.gitignore
node_modules
package-lock.json
.env
dist
```

### `https://localhost:3000`にアクセスすると`hello world`が表示
APIサーバーが正しく動くか検証のため一応作っておきましょう。

```typescript:api/src/index.ts
// Load the package
import { Client, ClientConfig } from '@line/bot-sdk';
import express from 'express';
require('dotenv').config();

// Read the ports from the process.env file
const PORT = process.env.PORT || 3000;

// Load the access token and channel secret from the .env file
const config: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// Instantiate
const app: express.Express = express();
const client = new Client(config);

// Do routing
// Testing Routing
app.get('/', (req: express.Request, res: express.Response): void => {
  res.send('Hello World');
});

// Start the server
app.listen(PORT, (): void => {
  console.log('http://localhost:3000');
});
```

上記の内容としては、
①必要なパッケージを読み込む
②PORT番号を選択する（デプロイ先でPORT番号が指定されるパターンに備えて一応`.env`を読み込む形式にしています。）
③`config`　の作成（これはおまじないのようなものです）
④インスタンス化を行う。(clientもおまじない)
⑤ルーティングの作成
⑥WEBサーバーの実行

おまじないだけで片付けるのもアレなので公式サイトを貼っておきます。
またLINEBot関連の型定義に関してもこちらは基本なので、参考コードがあります。
それを丸パクリしましょう。

https://line.github.io/line-bot-sdk-nodejs/guide/typescript.html#what-s-good-about-using-typescript

https://github.com/line/line-bot-sdk-nodejs/blob/master/examples/echo-bot-ts/index.ts

### `localhost.run`で開発用のhttpsを取得
前回の[Node.js](https://qiita.com/Ryo9597/items/bf93618663f28e9d8f7f)の記事でも使った`localhost.run`を使います。

http://localhost.run/

ということでここからはターミナル2つ使って開発していきます。

ターミナルに2つのコードを貼り付けて実行してください。

```terminal:ターミナル
$ npm run dev
$ ssh -R 80:localhost:3000 localhost.run
```

### Webhook URLの登録
`localhost.run`で作成したhttpsのURLをコピーしてください。
私の場合は以下のURLです。

https://ad40c809887bb5.localhost.run

これをLINE DevelopersのWebhookに設定します。

![スクリーンショット 2021-06-18 15.19.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/6efeafe9-58d6-1741-8ee6-38088e61f7ef.png)


これで初期設定は完了です。

ここからの流れはこのような感じです。

**①「今日の洋服は？」というメッセージを受け取る**
**②「今日の洋服は？」を受け取ったら、位置情報メッセージを送る**
**③「今日の洋服は？」以外を受け取ったら、「そのメッセージには対応していません」と送る**
**④「位置情報メッセージ」を受け取る**
**⑤「位置情報メッセージ」を受け取ったら、緯度と経度を使って天気予報を取得する**
**⑥「位置情報メッセージ」を受け取ったら、天気予報メッセージを送る**

では作っていきましょう！
またこれら全てのコードを`api/src/index.ts`に書くとコードが肥大化し可読性が落ちます。
なので`Common`ディレクトリに関数に切り分けて作成していきます。

またここからはLINEBotのオリジナルの型が頻出します。
1つずつ説明するのはあまりに時間がかかるので、知らない型が出てきたらその度に以下のサイトで検索するようにしてください。

https://github.com/line/line-bot-sdk-nodejs/blob/master/lib/types.ts

### ①「今日の洋服は？」というメッセージを受け取る

```typescript:api/src/index.ts
// Load the package
import { Client, middleware, ClientConfig, MiddlewareConfig, WebhookEvent } from '@line/bot-sdk';
import express from 'express';
import dotenv from 'dotenv';
dotenv.config();

// Load the module
import { SendMessage } from './Common/Send/ButtonOrErrorMessage';

// Read the ports from the process.env file
const PORT: string | 3000 = process.env.PORT || 3000;

// Load the access token and channel secret from the .env file
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};
const middlewareConfig: MiddlewareConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// Instantiate
const app: express.Express = express();
const client: Client = new Client(clientConfig);

// Do routing
// Testing Routing
app.get('/', (req: express.Request, res: express.Response): void => {
  res.send('Hello World');
});

// API Routing
app.post(
  '/api/line/message',
  middleware(middlewareConfig),
  async (req: express.Request, res: express.Response): Promise<void> => {
    const events: WebhookEvent[] = req.body.events;

    events.map(
      async (event: WebhookEvent): Promise<void> => {
        try {
          await SendMessage(client, event);
        } catch (err) {
          console.error(err);
        }
      }
    );
  }
);

// Start the server
app.listen(PORT, (): void => {
  console.log('http://localhost:3000');
});
```

### ②「今日の洋服は？」を受け取ったら、位置情報メッセージを送る

```typescript:api/src/Common/Template/ButtonMessageTemplate.ts
// Load the package
import { TemplateMessage } from '@line/bot-sdk';

export const ButtonMessageTemplate = (): TemplateMessage => {
  return {
    type: 'template',
    altText: 'This is a buttons template',
    template: {
      type: 'buttons',
      text: '今日はどんな洋服にしようかな',
      actions: [
        {
          type: 'uri',
          label: '現在地を送る',
          uri: 'https://line.me/R/nv/location/',
        },
      ],
    },
  };
};
```

```typescript:api/src/Common/Send/ButtonOrErrorMessage.ts
// Load the package
import { Client, WebhookEvent } from '@line/bot-sdk';

// Load the module
import { ButtonMessageTemplate } from '../Template/ButtonMessageTemplate';

export const SendMessage = async (client: Client, event: WebhookEvent): Promise<void> => {
  try {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }

    const { replyToken } = event;
    const { text } = event.message;

    if (text === '今日の洋服は？') {
      await client.replyMessage(replyToken, ButtonMessageTemplate());
    } else {
      // エラーメッセージを送る
    }
  } catch (err) {
    console.log(err);
  }
};

```

ボタンメッセージのJSON作成に関しては公式サイトを参考にしましょう。

https://developers.line.biz/ja/reference/messaging-api/#buttons

### ③「今日の洋服は？」以外を受け取ったら、「そのメッセージには対応していません」と送る

```typescript:api/src/Common/Template/ErrorMessageTemplate.ts
// Load the package
import { TextMessage } from '@line/bot-sdk';

export const ErrorMessageTemplate = (): TextMessage => {
  return {
    type: 'text',
    text: 'ごめんなさい、このメッセージは対応していません。',
  };
};
```

```typescript:api/src/Common/Send/ButtonOrErrorMessage.ts
// Load the package
import { Client, WebhookEvent } from '@line/bot-sdk';

// Load the module
import { ButtonMessageTemplate } from '../Template/ButtonMessageTemplate';
import { ErrorMessageTemplate } from '../Template/ErrorMessageTemplate';

export const SendMessage = async (client: Client, event: WebhookEvent): Promise<void> => {
  try {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }

    const { replyToken } = event;
    const { text } = event.message;

    if (text === '今日の洋服は？') {
      await client.replyMessage(replyToken, ButtonMessageTemplate());
    } else {
      await client.replyMessage(replyToken, ErrorMessageTemplate());
    }
  } catch (err) {
    console.log(err);
  }
};
```

テキストメッセージのJSON作成に関しては公式サイトを参考にしましょう。

https://developers.line.biz/ja/reference/messaging-api/#text-message

### ④「位置情報メッセージ」を受け取る

```typescript:api/src/index.ts
// Load the package
import { Client, middleware, ClientConfig, MiddlewareConfig, WebhookEvent } from '@line/bot-sdk';
import express from 'express';
import dotenv from 'dotenv';
dotenv.config();

// Load the module
import { SendMessage } from './Common/Send/ButtonOrErrorMessage';
import { FlexMessage } from './Common/Send/FlexMessage';

// Read the ports from the process.env file
const PORT: string | 3000 = process.env.PORT || 3000;

// Load the access token and channel secret from the .env file
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};
const middlewareConfig: MiddlewareConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// Instantiate
const app: express.Express = express();
const client: Client = new Client(clientConfig);

// Do routing
// Testing Routing
app.get('/', (req: express.Request, res: express.Response): void => {
  res.send('Hello World');
});

// API Routing
app.post(
  '/api/line/message',
  middleware(middlewareConfig),
  async (req: express.Request, res: express.Response): Promise<void> => {
    const events: WebhookEvent[] = req.body.events;

    events.map(
      async (event: WebhookEvent): Promise<void> => {
        try {
          await SendMessage(client, event);
          await FlexMessage(client, event);
        } catch (err) {
          console.error(err);
        }
      }
    );
  }
);

// Start the server
app.listen(PORT, (): void => {
  console.log('http://localhost:3000');
});
```

### ⑤「位置情報メッセージ」を受け取ったら、緯度と経度を使って天気予報を取得する
Flex Messageの作成方法に関してファイル名も出しながら説明します。

#### 【ファイル名】GetWeatherForecast.ts
天気予報を取得します。

まずはOpenWeatherで天気予報を取得するために必要な情報が3つあります。
①API
②経度
③緯度
それではこの3つを取得していきましょう。

##### ①API
以下にアクセスしてください。

https://openweathermap.org/

アカウントを作成し、APIキーを発行してください。

![スクリーンショット 2021-05-29 20.28.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/a044d998-32d7-a132-c94b-6e55208b6819.png)

発行できたらこのAPIを`.env`に保存します。

```env:.env
# OpenWeather（https://home.openweathermap.org/api_keys）
WEATHER_API = "a11b22c33d44e55f66g77"
```

あとは関数内で`.env`を取得するだけです。

##### ②経度、③緯度
これら2つは、eventから取得できます。

ということで作っていきましょう。

```typescript:api/src/Common/Template/WeatherForecast/GetWeatherForecast.ts
// Load the package
import { WebhookEvent } from '@line/bot-sdk';
import axios, { AxiosResponse } from 'axios';

export const getWeatherForecastData = async (event: WebhookEvent): Promise<any> => {
  try {
    if (event.type !== 'message' || event.message.type !== 'location') {
      return;
    }

    // Get latitude and longitude
    const latitude: number = event.message.latitude;
    const longitude: number = event.message.longitude;

    // OpenWeatherAPI
    const openWeatherAPI: string | undefined = process.env.WEATHER_API || '';

    // OpenWeatherURL
    const openWeatherURL: string = `https://api.openweathermap.org/data/2.5/onecall?lat=${latitude}&lon=${longitude}&units=metric&lang=ja&appid=${openWeatherAPI}`;

    const weatherData: AxiosResponse<any> = await axios.get(openWeatherURL);
    return weatherData;
  } catch (err) {
    console.log(err);
  }
};
```

#### 【ファイル名】FormatWeatherForecast.ts
取得した天気予報のデータの整形を行う。

こちらでは、`const weather`と`const weatherArray`の2つで型定義ファイルを作成する必要があります。
ということで作成しましょう。

```typescript:api/src/Common/Template/WeatherForecast/types/weather.type.ts
export type WeatherType = {
  dt: number;
  sunrise: number;
  sunset: number;
  moonrise: number;
  moonset: number;
  moon_phase: number;
  temp: {
    day: number;
    min: number;
    max: number;
    night: number;
    eve: number;
    morn: number;
  };
  feels_like: {
    day: number;
    night: number;
    eve: number;
    morn: number;
  };
  pressure: number;
  humidity: number;
  dew_point: number;
  wind_speed: number;
  wind_deg: number;
  wind_gust: number;
  weather: [
    {
      id: number;
      main: string;
      description: string;
      icon: string;
    }
  ];
  clouds: number;
  pop: number;
  rain: number;
  uvi: number;
};
```

```typescript:api/src/Common/Template/WeatherForecast/types/weatherArray.type.ts
export type WeatherArrayType = {
  today: string;
  imageURL: string;
  weatherForecast: string;
  mornTemperature: number;
  dayTemperature: number;
  eveTemperature: number;
  nightTemperature: number;
  fashionAdvice: string;
};
```

作成した型定義を使ってファイルを完成させます。

```typescript:api/src/Common/Template/WeatherForecast/FormatWeatherForecast.ts
// Load the package
import { WebhookEvent } from '@line/bot-sdk';
import { AxiosResponse } from 'axios';

// Load the module
import { getWeatherForecastData } from './GetWeatherForecast';

// types
import { WeatherType } from './types/weather.type';
import { WeatherArrayType } from './types/weatherArray.type';

export const formatWeatherForecastData = async (event: WebhookEvent): Promise<WeatherArrayType> => {
  // Get the getWeatherForecastData
  const weathers: AxiosResponse<any> = await getWeatherForecastData(event);

  // Util
  const weather: WeatherType = weathers.data.daily[0];

  // Five required data
  // 1) Today's date
  const UNIXToday: number = weather.dt;
  const convertUNIXToday: Date = new Date(UNIXToday * 1000);
  const today: string = convertUNIXToday.toLocaleDateString('ja-JP');

  // 2) Weather forecast
  const weatherForecast: string = weather.weather[0].description;

  // 3) Temperature (morning, daytime, evening, night)
  const mornTemperature: number = weather.feels_like.morn;
  const dayTemperature: number = weather.feels_like.day;
  const eveTemperature: number = weather.feels_like.eve;
  const nightTemperature: number = weather.feels_like.night;

  // Bifurcate your clothing by maximum temperature
  const maximumTemperature: number = Math.max(
    mornTemperature,
    dayTemperature,
    eveTemperature,
    nightTemperature
  );

  // 4) Fashion Advice
  let fashionAdvice: string = '';

  // 5) Fashion Image
  let imageURL: string = '';

  if (maximumTemperature >= 26) {
    fashionAdvice =
      '暑い！半袖が活躍する時期です。少し歩くだけで汗ばむ気温なので半袖1枚で大丈夫です。ハットや日焼け止めなどの対策もしましょう';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/60aa3c44153071e6df530eb7_71.png';
  } else if (maximumTemperature >= 21) {
    fashionAdvice =
      '半袖と長袖の分かれ目の気温です。日差しのある日は半袖を、曇りや雨で日差しがない日は長袖がおすすめです。この気温では、半袖の上にライトアウターなどを着ていつでも脱げるようにしておくといいですね！';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056e58a5923ad81f73ac747_10.png';
  } else if (maximumTemperature >= 16) {
    fashionAdvice =
      'レイヤードスタイルが楽しめる気温です。ちょっと肌寒いかな？というくらいの過ごしやすい時期なので目一杯ファッションを楽しみましょう！日中と朝晩で気温差が激しいので羽織ものを持つことを前提としたコーディネートがおすすめです。';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6087da411a3ce013f3ddcd42_66.png';
  } else if (maximumTemperature >= 12) {
    fashionAdvice =
      'じわじわと寒さを感じる気温です。ライトアウターやニットやパーカーなどが活躍します。この時期は急に暑さをぶり返すことも多いのでこのLINEで毎日天気を確認してくださいね！';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056e498e7d26507413fd853_4.png';
  } else if (maximumTemperature >= 7) {
    fashionAdvice =
      'そろそろ冬本番です。冬服の上にアウターを羽織ってちょうどいいくらいです。ただし室内は暖房が効いていることが多いので脱ぎ着しやすいコーディネートがおすすめです！';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056e4de7156326ff560b1a1_6.png';
  } else {
    fashionAdvice =
      '凍えるほどの寒さです。しっかり厚着して、マフラーや手袋、ニット帽などの冬小物もうまく使って防寒対策をしましょう！';
    imageURL =
      'https://uploads-ssl.webflow.com/603c87adb15be3cb0b3ed9b5/6056ebd3ea0ff76dfc900633_48.png';
  }

  // Make an array of the above required items.
  const weatherArray: WeatherArrayType = {
    today,
    imageURL,
    weatherForecast,
    mornTemperature,
    dayTemperature,
    eveTemperature,
    nightTemperature,
    fashionAdvice,
  };

  return weatherArray;
};
```

#### 【ファイル名】FlexMessageTemplate
整形したデータを取得して Flex Messageのテンプレートを作成する。

```typescript:api/src/Common/Template/WeatherForecast/FlexMessageTemplate.ts
// Load the package
import { WebhookEvent, FlexMessage } from '@line/bot-sdk';

// Load the module
import { formatWeatherForecastData } from './FormatWeatherForecast';

export const FlexMessageTemplate = async (event: WebhookEvent): Promise<FlexMessage> => {
  const data = await formatWeatherForecastData(event);

  return {
    type: 'flex',
    altText: '天気予報です',
    contents: {
      type: 'bubble',
      header: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: data.today,
            color: '#FFFFFF',
            align: 'center',
            weight: 'bold',
          },
        ],
      },
      hero: {
        type: 'image',
        url: data.imageURL,
        size: 'full',
      },
      body: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: `天気は、「${data.weatherForecast}」です`,
            weight: 'bold',
            align: 'center',
          },
          {
            type: 'text',
            text: '■体感気温',
            margin: 'lg',
          },
          {
            type: 'text',
            text: `朝：${data.mornTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#C8BD16',
          },
          {
            type: 'text',
            text: `日中：${data.dayTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#789BC0',
          },
          {
            type: 'text',
            text: `夕方：${data.eveTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#091C43',
          },
          {
            type: 'text',
            text: `夜：${data.nightTemperature}℃`,
            margin: 'sm',
            size: 'sm',
            color: '#004032',
          },
          {
            type: 'separator',
            margin: 'xl',
          },
          {
            type: 'text',
            text: '■洋服アドバイス',
            margin: 'xl',
          },
          {
            type: 'text',
            text: data.fashionAdvice,
            margin: 'sm',
            wrap: true,
            size: 'xs',
          },
        ],
      },
      styles: {
        header: {
          backgroundColor: '#00B900',
        },
        hero: {
          separator: false,
        },
      },
    },
  };
};
```

### ⑥「位置情報メッセージ」を受け取ったら、天気予報メッセージを送る

```typescript:api/src/Common/Send/FlexMessage.ts
// Load the package
import { Client, WebhookEvent } from '@line/bot-sdk';

// Load the module
import { FlexMessageTemplate } from '../Template/WeatherForecast/FlexMessageTemplate';

export const FlexMessage = async (client: Client, event: WebhookEvent): Promise<void> => {
  try {
    if (event.type !== 'message' || event.message.type !== 'location') {
      return;
    }

    const { replyToken } = event;
    const message = await FlexMessageTemplate(event);

    await client.replyMessage(replyToken, message);
  } catch (err) {
    console.log(err);
  }
};
```

これで完成です！
めちゃくちゃ簡単ですね。

## 最後にデプロイをしましょう
今回もデプロイは`Glitch`を使います。

https://glitch.com/

アカウントは、Githubで作るのがおすすめです。
作成しましたら、プロジェクトを作成します。
**「import from GitHub」**をクリックします。

![スクリーンショット 2021-06-14 21.26.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/392a4d01-ab7e-3e09-2c75-0b348ea2f87c.png)


ここには、GithubのURLを貼り付けます。
![スクリーンショット 2021-06-18 16.01.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/c85f1621-c9db-f3da-e365-608935dab983.png)


ちょっと待つとこのように読み込まれます。
便利なのは全てのファイルが確認できるところです。
`Heroku`などはどちらかというとCUIであり、GUIの`Glitch`は直感的に操作できてすごく良かったです。

![スクリーンショット 2021-06-18 16.01.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/deb0acae-d58c-45ac-3d9d-1f566cde1415.png)

最後に`.env`に値を入力します。

![スクリーンショット 2021-06-14 21.31.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/1a6a6ca4-1ed8-ed97-0db0-6a1da77d0650.png)

ここまで行えばデプロイは成功です！

![スクリーンショット 2021-06-18 16.02.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/4c7cd59b-af2f-dcef-9e78-e9aa39508824.png)


### ちなみに
#### URL変えたいときはここをいじってください
![スクリーンショット 2021-06-18 16.03.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/095170a9-64f7-d2d7-ea96-6983684a1898.png)

#### `Share`ボタンをクリックすればURLが`Live site`に書いているよ
![スクリーンショット 2021-06-18 16.03.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/5908f6b0-1302-9508-c522-be7126b4b96a.png)

### Webhookの設定を変更

![スクリーンショット 2021-06-18 16.05.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/694d1d40-4c19-b7e6-7bdf-e8464f2e62dc.png)


これで完成です！

## 最後に
FlexMessageなどコードにミスが起きやすいので、TypeScriptの型定義さえあればミスに気づきやすくなるなぁと実感しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/e3ead90e-26b9-3bac-ac07-5cad84d1dbbc.png)

次は、このアプリをAWSへデプロイするハンズオン記事を書いていきます。