【AWS;Lambda入門】第三弾；Linebotなじゃんけんゲームで遊ぶ♬

今回は、apigatewayを利用したnode.js@Lambdaでじゃんけんゲームを作ってみた。
環境構築やプログラムなどは、全て以下の参考①~④を参考に進めた。
だいたい、最初の①と②でLinebotが動く。そして③と④でLambda版のLinebotが動いた。筆者も言っているように感動ものである。
ちなみに、ほぼ1日でLinebotが動くようになった。
したがって、コードのほとんどは参考のまんまであることをお断りしておきます。
【参考】
①[LINEBotをみんなで作ろう〜環境構築編〜【GWアドベントカレンダー１日目】](https://qiita.com/inoue2002/items/a87df2b520f8b6e37f42)
②[LINEBotをみんなで作ろう〜おうむ返しbotを作ろう編〜【GWアドベントカレンダー3日目】](https://qiita.com/inoue2002/items/dcf40d5ebfe2454796fa)
③[LINEBotをみんなで作ろう〜レイヤーとAPIgateway設定編〜【GWアドベントカレンダー7日目】](https://qiita.com/inoue2002/items/ab226d9836a891b01bc9)
④[LINEBotをみんなで作ろう〜コードを実装編〜【GWアドベントカレンダー最終日】](https://qiita.com/inoue2002/items/dbbd862d6149c83f6513)
ということで、この先のことも考えてじゃんけんゲームを作ってみた。
###やったこと
・環境構築（Line）
・環境構築（Lambda@AWS）
・じゃんけんボット
###・環境構築（Line）
参考①の解説のとおりで環境が構築出来ました。

#### LINE Developerのサイトにログインできるようにする。
- [ログイン](https://developers.line.biz/ja/)→開発者名、メール入力
- プロバイダーを作成→プロバイダー名；この下にボットを構築
- 新規チャネルを作成→Messaging APIを選択
- Messaging APIのチャンル作成する→ボット名、その他
- Botの設定→
  - channel secret（短い方）とChannel access token (long-lived)（長い方）をアプリ側に設定、
  - アプリの公開アドレスからWebhook URLを設定、
  - BotのLINE Official Account featuresのeditから
     - アカウント設定で応答設定；bot オン、
     - あいさつ　オフ、
     - 応答メッセージ　オフ、
     - Webhook　オン
     
###おうむ返しボット
まず、自PC、Windows10でNode.jsを利用しておうむ返しボットを作ります。
#### ngrokをインストールする
[Download & setup ngrok](https://ngrok.com/download)から自PCのOsにあったものをダウンロードして、解凍するとngrok.exeが入っているのでこれを実行すれば（ダブルクリックで）コンソールが動きました。そこで以下を実行すると実行できます。
※ちなみに、専用コンソールで実行してください。WindowsPromptだと動きませんでした
`ngrok -v`
`ngrok version 2.3.35`
#### node.js(v10以上推奨）をインストールする
[node.js(v10以上推奨）ダウンロード](https://nodejs.org/ja/download/)からWindowsInstaller；node-v12.18.1-x64.msiをダウンロードして、インストール。
通常のコマンドラインで以下のように確認できる。
`>node -v`
`v12.18.1`
#### エディタ
使い慣れたjupyter notebookを使いました。
####おうむ返しボットのnode.jsコード
以下のようにdesktop配下にDirを作って、そこで環境構築して作業します。参考にいろいろな拡張があって凄い記事になっていました。
【参考】
⑤[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)

```
cd deskTop
mkdir test-linebot
cd test-linebot
npm init -y 
npm i @line/bot-sdk express
```
次にプログラムのメインとなるindex.jsをtest-linebotフォルダ内に作成します。
エディタで以下をコピペします。
上記のとおり、短い方と長い方をBotのBasic settingsとMessaging API settingsからコピペして設定します。

```index.js
"use strict";
const express = require("express");
const line = require("@line/bot-sdk");
const PORT = process.env.PORT || 5000;

const config = {
  channelSecret: "短い方", 
  channelAccessToken: "長い方"
};
const app = express();
app.post("/webhook", line.middleware(config), (req, res) => {
  console.log(req.body.events);
  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);
  };
  let mes = { type: "text", text: event.message.text };
  return client.replyMessage(event.replyToken, mes);
}
app.listen(PORT);
console.log(`Server running at ${PORT}`);
```
ここで、肝心な部分として、返信している文字列は
eventが以下のようなものなので、
`event.message.text='動くかな😅'`となります。

```
{
    type: 'message',
    replyToken: '*******************************',
    source: { userId: '*****************', type: '***' },
    timestamp: **************,
    mode: 'active',
    message: { type: 'text', id: '*************', text: '動くかな😅' }
}
```
これで、Linebotを動かしてみます。
`\Desktop\test-linebot>node index.js`
`Server running at 5000`
この状態で、もう一個ターミナルを起動して、
`ngrok http 5000`
これで上記のindex.jsを公開します。
以下のような表示が出現します。以下は1時間37分ほど経過するとexpireするものになっています。
以下の表示から`https://73e6916ab721.ngrok.io`というサイトからフォワードしていることが分かるので、ボットのWebhook URLとして、`https://73e6916ab721.ngrok.io/webhook`とします。
※因みにこのサーバーは一意だと思いますが、毎回起動の度に変化します。また、Session Expiresの時間も長いとき（約7時間半）も短いとき（約1時間半）もバラバラです。

```
ngrok by @inconshreveable                                                                               (Ctrl+C to quit)

Session Status                online
Session Expires               1 hour, 37 minutes
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://73e6916ab721.ngrok.io -> http://localhost:5000
Forwarding                    https://73e6916ab721.ngrok.io -> http://localhost:5000
```
これで動くと思います。動かなかったら、参考②や参考⑤を見てください。どちらもとても丁寧に書かれています。
出来ると、単なるおうむ返しですが、なんとなくうれしいです。
###・環境構築（Lambda@AWS）
今度は、上記のindex.jsをLambdaで動作させApiGatewayを利用して、Linebotと連携します。
これも、参考③のとおりです。
・AWSアカウントでログイン；コンソールへ行く
・Lambdaを開く
・Lambda関数を作成・関数名（lamdbaTst）、ランタイムNode.Node.js 12js 12.x、ロール（基本的な...）等の設定
・コンソールに戻って、(検索して)API Gatewayを選択
・RestAPI　構築・REST・新しいAPI・API名；linebotAPI・エンドポイントタイプ；リージョン・APIの作成
・アクション・メソッドの作成・Post設定
・アクション；POSTセットアップ・Lambda関数・Lambdaプロキシ統合の使用；チェック・Lambda関数；lambdaTst・保存
・Lambda関数に権限を追加するダイアログ；OK
・アクション；POSTメソッドの実行・メソッドリクエスト・HTTPリクエストヘッダー・ヘッダー追加・名前欄にX-Line-Signatureと入力、必須にチェック
・アクション・APIのデプロイ・レイヤーの作成
・デプロイされるステージ；新しいステージ・ステージ名；test1・デプロイ
・Lambdaの関数の画面へ行き、関数にAPIGatewayが連携（追加）されていれば成功
・利用するNode.js環境をzipファイルにまとめて、レイヤーとして登録する・参考③はMac環境ですが、全く同じように作成します。
・cd Desktop・mkdir nodejs・npm init -y・npm i @line/bot-sdk・nodejsディレクトリをzip圧縮
・Lambda関数ページのレイヤーを開く・レイヤーの作成でnodejs.zipをアップロード。名前はそれらしい名前;linebot-SDK。・互換性のあるランタイムオプション；node.js 12,x node.js 10.x・作成
・Lambda関数ページのレイヤーを連携・レイヤーの追加・ランタイムと互換性のあるレイヤーのリストから選択・名前；linebot-SDK
・バージョン；１・追加
・関数画面でレイヤーで読み込めていたら（下に表示されたら）成功
・関数名をクリックして下へスクロール、環境変数の項目を探す、編集をクリック・編集・ACCESSTOKEN；長い方・CHANNELSECRET；短い方・保存
・Lambda関数をクリックして、関数を以下のコードをコピペして張り付ける。・右上の保存
・WEBhookURLを設定する・API Gatewayを押下・APIエンドポイントをコピペ・LinebotのWEBhookURLに張り付ける
これで、動きました。
さて、張り付けるコードは以下のじゃんけんゲームです。
###じゃんけんゲームのコード


```index.js
"use strict";
// モジュール呼び出し
const crypto = require("crypto");
const line = require("@line/bot-sdk");

// インスタンス生成
const client = new line.Client({ channelAccessToken: process.env.ACCESSTOKEN });

exports.handler = (event, context, callback) => {
  // 署名検証
  const signature = crypto
    .createHmac("sha256", process.env.CHANNELSECRET)
    .update(event.body)
    .digest("base64");
  const checkHeader = (event.headers || {})["X-Line-Signature"];
  const body = JSON.parse(event.body);
  const events = body.events;
  console.log(events);

  // 署名検証が成功した場合
  if (signature === checkHeader) {
    events.forEach(async (event) => {
      let message;
      // イベントタイプごとに関数を分ける
      switch (event.type) {
        // メッセージイベント
        case "message":
          message = await messageFunc(event);
          break;
        // フォローイベント
        case "follow":
          message = { type: "text", text: "追加ありがとうございます！" };
          break;
        // ポストバックイベント
        case "postback":
          message = await postbackFunc(event);
          break;
      }
      // メッセージを返信
      if (message != undefined) {
        client
          .replyMessage(body.events[0].replyToken, message)
          .then((response) => {
            const lambdaResponse = {
              statusCode: 200,
              headers: { "X-Line-Status": "OK" },
              body: '{"result":"completed"}',
            };
            context.succeed(lambdaResponse);
          })
          .catch((err) => console.log(err));
      }
    });
  }
  // 署名検証に失敗した場合
  else {
    console.log("署名認証エラー");
  }
};

const messageFunc = async function (event) {
  var mes = await mesmakeFunc(event);
  let message;
  switch (event.message.type) {
      case "text":
          message= { type: "text", text: mes };
      break;
          case "image":
          message = { type: "text", text: "がぞうを受け取ったよ！" };
      break;
          case "sticker":
          message= { type: "text", text: "ステッカーを送ってくれたんだね！" };
      break;
  }
  if (message !== undefined) {
      return client.replyMessage(event.replyToken, message);
  }
  return message;
};
const mesmakeFunc = async function (event) {
  console.log('event.text:', event.message.text);
  var mes = ['グー','チョキ','パー',event.message.text];
  var r = Math.floor( Math.random() * 4 );
  var mes0 = mes[r]; 
  console.log('message:',mes0);
  return mes0;
};

const postbackFunc = async function (event) {
  let message;
  message = { type: "text", text: "ポストバックイベントを受け付けました！" };
  return message;
};
```
###自PCでのじゃんけんゲームのコード
```index.js
"use strict";

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

const config = {
  channelSecret: "短い方", 
  channelAccessToken: "長い方"
};

const app = express();

app.post("/webhook", line.middleware(config), (req, res) => {
  console.log(req.body.events);
  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" ) {
    return Promise.resolve(null);
  };
  let mes = await messageFunc(event);
    console.log('mes_message:',mes);
  return client.replyMessage(event.replyToken, mes);
};

const messageFunc = async function (event) {
  var mes = await mesmakeFunc(event);
  let message;
    console.log('message_message:',event.message.type);
  switch (event.message.type) {
      case "text":
          message= { type: "text", text: mes };
      break;
          case "image":
          message = { type: "text", text: "がぞうを受け取ったよ！" };
          console.log('image_message:',message);
      break;
          case "sticker":
          message= { type: "text", text: "ステッカーを送ってくれたんだね！" };
          console.log('sticker_message:',message);
      break;
  }
  return message;
};

const mesmakeFunc = async function (event) {
  console.log('event.text:', event.message.text);
  var mes = ['グー','チョキ','パー',event.message.text];
  var r = Math.floor( Math.random() * 4 );
  var mes0 = mes[r]; 
  console.log('message:',mes0);
  return mes0;
};

app.listen(PORT);
console.log(`Server running at ${PORT}`);    
```
###まとめ
・Linebotを自PCとLambdaで連携して遊んでみた
・ちょっとした拡張として、じゃんけんゲームを作成してみた

・利用性の高いアプリを作ってみようと思う