8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Node.js, TypeScript】LINE MessagingAPIで作った天気予報アプリをAWSのLambdaにデプロイしてみる!

Posted at

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

完成形としては以下の通りです。
名称未設定.png

この記事内では、Glitchでデプロイをしています。
まぁ無料なわけで色々問題があります。
・プロジェクトは、利用されていないときは5分でスリープ状態になる
・4000件/1hのリクエスト制限がある(Error: 429 too many requests)

ということで、AWSのLambdaを使ってデプロイすることにします。
それではアーキテクチャに関してみていきましょう。

サーバーレスアーキテクチャとは

AWSにおけるサーバーレスとは、**「インスタンスベースの仮想サーバー(EC2など)を使わずにアプリケーションを開発するアーキテクチャ」**を指します。

一般にシステムの運用には、プログラムを動かすためのサーバーが必要です。
そしてそのサーバーは、常に稼働していなければなりません。

しかし開発者がやりたいことは、「サーバーの管理」なのでしょうか?
エンドユーザーに価値を届けることこそが使命なわけです。

ということで、こういうめんどくさい作業から解放してくれるのがサーバーレスアーキテクチャなわけです。

サーバーレスアーキテクチャでよく使われるサービスは以下の通りです。
特に、丸で囲っている3つがよく使われます。

スクリーンショット 2021-06-26 15.43.25.png

今回は、Lambda, API Gateway, S3の3つを使ってアプリを作成していきます。

アーキテクチャ

arch.png

追記

AWSのEC2を使ってデプロイした記事もあります。
サーバーレスよりもEC2に興味があるぞという方はこちらの記事もどうぞ。

どのようなアプリか

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

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

image.png

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

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

アプリの流れ

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

・①クライアントが現在地を送る
・②OpenWeatherから天気予報を取得
・③データの整形
・④クライアントに送る

GitHub

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

では実際に作成していきましょう!

AWSの環境構築を行う

上記で説明した通り、今回はAWSの3つのサービスを使用します。
その3つのサービスの環境を構築しましょう。

Lambda

まずは関数を作成しましょう。

スクリーンショット 2021-07-14 15.41.05.png

では設定を行います。

スクリーンショット 2021-07-14 15.45.20.png

これで終了です。

API Gateway

APIを作成しましょう。

スクリーンショット 2021-07-14 15.48.01.png

REST APIを選択します。

スクリーンショット 2021-07-14 15.51.29.png

適当にAPI名をつけて作成しましょう。

スクリーンショット 2021-07-14 15.52.39.png

作成できたらメソッドを作成しましょう。

スクリーンショット 2021-07-14 15.53.45.png

メソッドはPOSTになります。

スクリーンショット 2021-07-14 15.54.58.png

Lambda関数を呼び出せるようにします。

スクリーンショット 2021-07-14 15.55.51.png

最後にデプロイをしてURLを発行します。

スクリーンショット 2021-07-14 15.58.34.png

URLができました。
このURLを叩くと、Lambda関数が実行されることになります。

スクリーンショット 2021-07-14 15.59.23.png

S3

バケットの作成を行います。

スクリーンショット 2021-07-14 16.31.47.png

これで完了です。

LINE Developersにアカウントを作成する

LINE Developersにアクセスして、「ログイン」ボタンをクリックしてください。

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

スクリーンショット 2021-05-29 16.13.47.png

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

スクリーンショット 2021-05-29 16.16.20.png

スクリーンショット 2021-05-29 16.17.51.png

ではこの2つをLambdaの環境変数に入力します。

スクリーンショット 2021-07-14 16.08.10.png

package.jsonの作成

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

ターミナル
$ npm init -y

必要なパッケージのインストール

dependencies

dependenciesはすべてのステージで使用するパッケージです。

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

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

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

devDependencies

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

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

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

ターミナル
$ npm install -D typescript @types/node ts-node rimraf npm-run-all

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

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

package.json
{
  "scripts": {
    "clean": "rimraf dist",
    "tsc": "tsc",
    "build": "npm-run-all clean tsc"
  },
}

tsconfig.jsonの作成

以下のコマンドを実行しTypeScriptの初期設定を行います。

ターミナル
$ npx tsc --init

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

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

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

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

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

.gitignore
_.drawio
node_modules
package-lock.json
dist

Webhook URLの登録

先ほどAPI Gatewayで作成したhttpsのURLをコピーしてください。

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

スクリーンショット 2021-07-14 16.30.04.png

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

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

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

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

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

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

api/src/index.ts
// パッケージのインストール
import { ClientConfig, Client, WebhookEvent } from '@line/bot-sdk';

// アクセストークンとチャンネルシークレットをenvから読み込む
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// インスタンス化
const client: Client = new Client(clientConfig);

// 実行
exports.handler = async (event: any, context: any) => {
  const body: any = JSON.parse(event.body);
  const response: WebhookEvent = body.events[0];

  try {
    await actionButtonOrErrorMessage(response);
  } catch (err) {
    console.log(err);
  }
};

// ボタンメッセージもしくはエラーメッセージを送る関数
const actionButtonOrErrorMessage = async (event: WebhookEvent) => {
  try {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }

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

    if (text === '今日の洋服は?') {
      // 「今日の洋服は?」というメッセージを受け取る
    } else {
      // 「今日の洋服は?」以外のメッセージを受け取る
    }
  } catch (err) {
    console.log(err);
  }
};

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

api/src/Common/ButtonMessage/ButtonMessageTemplate.ts
// パッケージを読み込む
import { TemplateMessage } from '@line/bot-sdk';

export const buttonMessageTemplate = (): Promise<TemplateMessage> => {
  return new Promise((resolve, reject) => {
    const params: TemplateMessage = {
      type: 'template',
      altText: 'This is a buttons template',
      template: {
        type: 'buttons',
        text: '今日はどんな洋服にしようかな',
        actions: [
          {
            type: 'uri',
            label: '現在地を送る',
            uri: 'https://line.me/R/nv/location/',
          },
        ],
      },
    };
    resolve(params);
  });
};
api/src/index.ts
// パッケージのインストール
import { ClientConfig, Client, WebhookEvent } from '@line/bot-sdk';

// モジュールを読み込む
import { buttonMessageTemplate } from './Common/ButtonMessage/ButtonMessageTemplate';

// アクセストークンとチャンネルシークレットをenvから読み込む
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// インスタンス化
const client: Client = new Client(clientConfig);

// 実行
exports.handler = async (event: any, context: any) => {
  const body: any = JSON.parse(event.body);
  const response: WebhookEvent = body.events[0];

  try {
    await actionButtonOrErrorMessage(response);
  } catch (err) {
    console.log(err);
  }
};

// ボタンメッセージもしくはエラーメッセージを送る関数
const actionButtonOrErrorMessage = async (event: WebhookEvent) => {
  try {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }

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

    if (text === '今日の洋服は?') {
      const buttonMessage = await buttonMessageTemplate();
      await client.replyMessage(replyToken, buttonMessage);
    } else {
      // 「今日の洋服は?」以外のメッセージを受け取る
    }
  } catch (err) {
    console.log(err);
  }
};

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

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

api/src/Common/ButtonMessage/ErrorMessageTemplate.ts
// パッケージを読み込む
import { TextMessage } from '@line/bot-sdk';

export const errorMessageTemplate = (): Promise<TextMessage> => {
  return new Promise((resolve, reject) => {
    const params: TextMessage = {
      type: 'text',
      text: 'ごめんなさい、このメッセージは対応していません。',
    };
    resolve(params);
  });
};
api/src/index.ts
// パッケージのインストール
import { ClientConfig, Client, WebhookEvent } from '@line/bot-sdk';

// モジュールを読み込む
import { buttonMessageTemplate } from './Common/ButtonMessage/ButtonMessageTemplate';
import { errorMessageTemplate } from './Common/ButtonMessage/ErrorMessageTemplate';

// アクセストークンとチャンネルシークレットをenvから読み込む
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// インスタンス化
const client: Client = new Client(clientConfig);

// 実行
exports.handler = async (event: any, context: any) => {
  const body: any = JSON.parse(event.body);
  const response: WebhookEvent = body.events[0];

  try {
    await actionButtonOrErrorMessage(response);
  } catch (err) {
    console.log(err);
  }
};

// ボタンメッセージもしくはエラーメッセージを送る関数
const actionButtonOrErrorMessage = async (event: WebhookEvent) => {
  try {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }

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

    if (text === '今日の洋服は?') {
      const buttonMessage = await buttonMessageTemplate();
      await client.replyMessage(replyToken, buttonMessage);
    } else {
      const errorMessage = await errorMessageTemplate();
      await client.replyMessage(replyToken, errorMessage);
    }
  } catch (err) {
    console.log(err);
  }
};

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

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

api/src/index.ts
// パッケージのインストール
import { ClientConfig, Client, WebhookEvent } from '@line/bot-sdk';

// モジュールを読み込む
import { buttonMessageTemplate } from './Common/ButtonMessage/ButtonMessageTemplate';
import { errorMessageTemplate } from './Common/ButtonMessage/ErrorMessageTemplate';
import { flexMessageTemplate } from './Common/WeatherForecastMessage/FlexMessageTemplate';

// アクセストークンとチャンネルシークレットをenvから読み込む
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// インスタンス化
const client: Client = new Client(clientConfig);

// 実行
exports.handler = async (event: any, context: any) => {
  const body: any = JSON.parse(event.body);
  const response: WebhookEvent = body.events[0];

  try {
    await actionButtonOrErrorMessage(response);
    await actionFlexMessage(response);
  } catch (err) {
    console.log(err);
  }
};

// ボタンメッセージもしくはエラーメッセージを送る関数
const actionButtonOrErrorMessage = async (event: WebhookEvent) => {
  try {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }

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

    if (text === '今日の洋服は?') {
      const buttonMessage = await buttonMessageTemplate();
      await client.replyMessage(replyToken, buttonMessage);
    } else {
      const errorMessage = await errorMessageTemplate();
      await client.replyMessage(replyToken, errorMessage);
    }
  } catch (err) {
    console.log(err);
  }
};

const actionFlexMessage = async (event: WebhookEvent) => {
  try {
    if (event.type !== 'message' || event.message.type !== 'location') {
      return;
    }
    // 「位置情報メッセージ」を受け取る
  } catch (err) {
    console.log(err);
  }
};

⑤「位置情報メッセージ」を受け取ったら、緯度と経度を使って天気予報を取得する

Flex Messageの作成方法に関してファイル名も出しながら説明します。

【ファイル名】GetWeatherForecast.ts

天気予報を取得します。

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

①API

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

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

スクリーンショット 2021-05-29 20.28.58.png

発行できたらこのAPIをLambdaの環境変数に入力します。

スクリーンショット 2021-07-14 16.46.23.png

②経度、③緯度

これら2つは、eventから取得できます。

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

api/src/Common/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> => {
  return new Promise(async (resolve, reject) => {
    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);
      resolve(weatherData);
    } catch (err) {
      reject(err);
    }
  });
};

【ファイル名】FormatWeatherForecast.ts

取得した天気予報のデータの整形を行う。

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

api/src/Common/WeatherForecast/types/FormatWeatherForecast.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;
};

export type WeatherArrayType = {
  today: string;
  imageURL: string;
  weatherForecast: string;
  mornTemperature: number;
  dayTemperature: number;
  eveTemperature: number;
  nightTemperature: number;
  fashionAdvice: string;
};

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

api/src/Common/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, WeatherArrayType } from './types/FormatWeatherForecast.type';

export const formatWeatherForecastData = async (event: WebhookEvent): Promise<WeatherArrayType> => {
  return new Promise(async (resolve, reject) => {
    // 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,
    };

    resolve(weatherArray);
  });
};

【ファイル名】FlexMessageTemplate

整形したデータを取得して Flex Messageのテンプレートを作成する。

api/src/Common/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> => {
  return new Promise(async (resolve, reject) => {
    const data = await formatWeatherForecastData(event);

    resolve({
      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,
          },
        },
      },
    });
  });
};

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

api/src/index.ts
// パッケージのインストール
import { ClientConfig, Client, WebhookEvent } from '@line/bot-sdk';

// モジュールを読み込む
import { buttonMessageTemplate } from './Common/ButtonMessage/ButtonMessageTemplate';
import { errorMessageTemplate } from './Common/ButtonMessage/ErrorMessageTemplate';
import { flexMessageTemplate } from './Common/WeatherForecastMessage/FlexMessageTemplate';

// アクセストークンとチャンネルシークレットをenvから読み込む
const clientConfig: ClientConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '',
  channelSecret: process.env.CHANNEL_SECRET || '',
};

// インスタンス化
const client: Client = new Client(clientConfig);

// 実行
exports.handler = async (event: any, context: any) => {
  const body: any = JSON.parse(event.body);
  const response: WebhookEvent = body.events[0];

  try {
    await actionButtonOrErrorMessage(response);
    await actionFlexMessage(response);
  } catch (err) {
    console.log(err);
  }
};

// ボタンメッセージもしくはエラーメッセージを送る関数
const actionButtonOrErrorMessage = async (event: WebhookEvent) => {
  try {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return;
    }

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

    if (text === '今日の洋服は?') {
      const buttonMessage = await buttonMessageTemplate();
      await client.replyMessage(replyToken, buttonMessage);
    } else {
      const errorMessage = await errorMessageTemplate();
      await client.replyMessage(replyToken, errorMessage);
    }
  } catch (err) {
    console.log(err);
  }
};

const actionFlexMessage = async (event: WebhookEvent) => {
  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);
  }
};

これで完成です!
npm run buildでコンパイルしましょう。

ターミナル
$ npm run build

S3にアップロードしましょう

コンパイルするとdistディレクトリに2つのファイルと1つのフォルダがあるかと思います。

ファイル フォルダ
index.js Common
index.js.map

これら3つとnode_modulesでzipを作成しましょう。

スクリーンショット 2021-07-14 16.55.36.png

私は__dist.zipという名前で作成しました。

ちなみになぜnode_modulesが必要になるかなのですが、
現在Lambda関数内でLINEのパッケージが使える状況にありません。
なのでLambda関数内で使えるようにするために、node_modulesをLambdaに渡す必要があるのです。

アップロードはめちゃくちゃ簡単です。
ただドラッグアンドドロップするだけです。

スクリーンショット 2021-07-14 16.58.42.png

アップロードが完了するとオブジェクトURLが設定されるのでこれをLambdaにアップロードします。

スクリーンショット 2021-07-14 17.02.18.png

これで完成です!

最後に

今回はすべて手作業で行いました。
所々デバッグしながら進めていきたいですが、
そのためには毎回S3にアップしてそれをLambdaでもアップするという作業が必要になります。

こんなのめんどくさいですよねw

image.png

ということで、そんな人のためにSAMというものがあります。

SAMを使えば、ローカルでデバッグやテストができるようになるとともに、
コマンド1つでデプロイすることが可能になります。

次回は今回作ったアプリをSAMを使って作りたいと思います。

8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?