憧れのギニュー特戦隊の誰に似てるか判定するLINEbotを作ったから、ぜってぇ見てくれよなっ！

# ギニュー特戦隊に入りたい
皆さんも（特に男性なら）、人生で一度はギニュー特戦隊に入隊したいと思いましたよね？
今回はその願いを少しでも叶えるべく、次のようなボットを作成しました。
＊ まさかそんな人はいないと思いますが、ギニュー特戦隊を知らない方は[こちらのwikipedia](https://ja.wikipedia.org/wiki/%E3%82%AE%E3%83%8B%E3%83%A5%E3%83%BC%E7%89%B9%E6%88%A6%E9%9A%8A)をご確認ください。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ギニュー特戦隊識別bot完成！<br>僕はリクームでしたwww<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://t.co/52wTNdbfTu">pic.twitter.com/52wTNdbfTu</a></p>&mdash; 北城雅照@足立慶友整形外科 (@kutuyanomusuko) <a href="https://twitter.com/kutuyanomusuko/status/1332470294359883777?ref_src=twsrc%5Etfw">November 27, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

早速使ってみたい方は、一番下にQRコードを貼っておきますので、ご利用ください。
ちなみに、万が一、リクームとジース以外の方に似ていると判定されたら、その方は人間（ホモ・サピエンス）という概念を超えている方ですので、すぐにご連絡ください！
# 開発環境の下準備
１）　VScodeのインストール
VScodeのインストールついては、googleなどで他の記事を検索してください。
２）　node.jsとnpmのインストール
Macでの環境作りは、こちらの別の記事にまとめてあります。
参考にしてください。
[Macにnode.js,npmのインストール方法](https://qiita.com/Teru_3/items/cc8a4968eff131d00784)


# 開発環境の準備
１）　ginyuforceというフォルダを作成
２）　VSCodeで上記フォルダを開き、フォルダ内にginyuforce.jsを作成
３）　VSCode内でターミナルを開き、フォルダをnpm管理できるように初期化

```:terminalコマンド
$ npm init -y
```

#システムの概要
![スクリーンショット 2020-11-29 17.11.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/3e5a7a79-c297-4e14-7e2a-d45391f34e95.png)


#  Teachable Machineによる画像認識AIの作成
 以前の記事（[誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。](https://qiita.com/Teru_3/items/80cecd138860fbd0c924)）では画像判定AIを[AIメーカー](https://aimaker.io)で作成したので、今回は[Teachable Machine](https://teachablemachine.withgoogle.com)を使用し、画像判定AIを作成しました。
![スクリーンショット 2020-11-29 14.57.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/c58daa16-5eaf-8c38-8488-21f9627cd763.png)

＊ 使い方については、こちらの記事＞[【備忘録】Teachable Machineの利用方法](https://qiita.com/misa_m/items/d8a20d153c8ee7daf0c1)が参考になりました。ぜひ一度お読みください。（よかったらLGTMをお願いします。）



# ginyuforce.jsのコード

```javascript:ginyuforce.js
'use strict'; // おまじない

const { JSDOM } = require('jsdom');
var dom = new JSDOM('');
global.document = dom.window.document;
global.HTMLVideoElement = dom.window.HTMLVideoElement;
const canvas = require('canvas');
global.fetch = require('node-fetch');
const tmImage = require('@teachablemachine/image');

const express = require('express');
const line = require('@line/bot-sdk');
const fs = require('fs');

const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: '自身が作成したLINEbotのシークレットトークンを入力',
    channelAccessToken: '自身が作成したLINEbotのアクセストークンを入力',
};

// Teachable Machine
let model;

// https://teachablemachine.withgoogle.com/
// ここでエクスポート、クラウドにモデルをアップロードした後に取得できる
const URL = 'https://teachablemachine.withgoogle.com/models/ovKT5Lvf_/';

// ########################################
//  Teachable Machineを使って画像分類をする部分
// ########################################

// 初期化が時間かかるので、node立ち上げ時に行うようにする
async function initTeachableMachine() {
  const modelURL = URL + 'model.json';
  const metadataURL = URL + 'metadata.json';
  // モデルデータのロード
  model = await tmImage.load(modelURL, metadataURL);

  // クラスのリストを取得
  // const classes = model.getClassLabels();
  // console.log(classes);
}
initTeachableMachine();

async function predict(imgPath) {
  // canvasに画像をロードする
  const image = await canvas.loadImage(imgPath);

  // 判定する
  const predictions = await model.predict(image);

  // 一番近いもの順でソート
  predictions.sort((a, b) => {
    return b.probability - a.probability;
  });

  return predictions;
}

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

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

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
  // 受信したWebhookが「画像以外」であればnullを返すことで無視します
  if (event.message.type === 'image') {
    console.log("画像が送られてきた");

    // 画像を保存
    const downloadPath = './01.png'; //左記の名前で使用中のフォルダ内に送信された画像が保存される
    const getContent = await downloadContent(event.message.id, downloadPath);

    const result = await predict(downloadPath);

    // AIメーカーAPIの結果から、返信するメッセージを組み立てる
    let text = '';
    let name = '';
    name = result[0].className
    // 判定結果をテキストに代入
    text = 'あなたは『' + name + "』に最も似ています！";
    // これまでの結果を確認するためにコンソールに表示
    console.log(result);
    console.log(name);
    console.log(text);
    // 判定結果に応じた画像を送信
    if(name === 'ジース'){
        return client.replyMessage(event.replyToken, [{
            type: 'text',
            text: text
        },{
            type: 'image',
            originalContentUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/gisu.jpg', 
            previewImageUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/gisu.jpg'
        }]);
     }else if(name === 'リクーム'){
        return client.replyMessage(event.replyToken,[{
            type: 'text',
            text: text
        },{
            type: 'image',
            originalContentUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/rikumu.jpg', 
            previewImageUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/rikumu.jpg'
        }]);
     }else if(name === 'グルド'){
        return client.replyMessage(event.replyToken, [{
            type: 'text',
            text: text
        },{
            type: 'image',
            originalContentUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/gurudo.jpg', 
            previewImageUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/gurudo.jpg'
        }]);
     }else if(name === 'バータ'){
        return client.replyMessage(event.replyToken, [{
            type: 'text',
            text: text
        },{
            type: 'image',
            originalContentUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/bata.jpg', 
            previewImageUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/bata.jpg'
        }]);
     }else if(name === 'ギニュー隊長'){
        return client.replyMessage(event.replyToken, [{
            type: 'text',
            text: text
        },{
            type: 'image',
            originalContentUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/ginyu.jpg', 
            previewImageUrl: 'https://sleepy-mirzakhani-f338f8.netlify.app/ginyu.jpg'
        }]);
    }
  }

  // 「テキストメッセージ」であれば、受信したテキストをそのまま返事します
  if (event.message.type === 'text') {
    return client.replyMessage(event.replyToken, {
      type: 'text',
      text: event.message.text // ← ここに入れた言葉が実際に返信されます
    });
  }
}

// ########################################
//          LINEで送られた画像を保存する部分
// ########################################

function downloadContent(messageId, downloadPath) {
  const data = [];
  return client.getMessageContent(messageId)
    .then((stream) => new Promise((resolve, reject) => {
      const writable = fs.createWriteStream(downloadPath);
      stream.on('data', (chunk) => data.push(Buffer.from(chunk)));
      stream.pipe(writable);
      stream.on('end', () => resolve(Buffer.concat(data)));
      stream.on('error', reject);
    }));
}

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

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

// HTTP GETによって '/' のパスにアクセスがあったときに 'Hello LINE BOT! (HTTP GET)' と返事します
// これはMessaging APIとは関係のない確認用のものです
app.get('/', (req, res) => res.send('<h1>Hello LINE BOT! (HTTP GET)</h1>'));

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
  // Webhookの中身を確認用にターミナルに表示します
  console.log(req.body.events);

  // 空っぽの場合、検証ボタンをクリックしたときに飛んできた"接続確認"用
  // 削除しても問題ありません
  if (req.body.events.length == 0) {
    res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します
    console.log('検証イベントを受信しました！'); // ターミナルに表示します
    return; // これより下は実行されません
  }

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

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// （環境によってはローカルネットワーク内にも公開されます）
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
```
# 実行に必要なパッケージのインストルール
下記のコードを入力し、実行に必要なパッケージを入力する。

2) Teachable Machineを動かすのに必要なパッケージ

```:terminalコマンド
$ npm i @line/bot-sdk express
```

１) LINEbotを動かすのに必要なパッケージ

```:terminalコマンド
$ npm i @teachablemachine/image @tensorflow/tfjs canvas jsdom
```

#Herokuへのデプロイ
node.jsが動作する無料のクラウドサーバーであるHerokuにデプロイします。

## 下準備
１）　Heroku CLIのインストール
https://devcenter.heroku.com/articles/heroku-cli

２）　Herokuアカウントの作成
https://signup.heroku.com/

## デプロイ
ターミナルで下記コマンドを実行します。

```:terminalコマンド
$ heroku login
```
実行するとブラウザが自動的に起動するので、ログインします。
すると、ターミナル上でもログインが完了します。

その後、package.jsonの"scripts"の中に、"start"を下記のように追記し保存します。

```:package.json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node ginyuforce.js"
  },
```
Heroku内にデプロイ先のディレクトリを作成します。
下記の順でターミナルにコマンドを入力します。

```:terminalコマンド
$ git init
```

```:terminalコマンド
$ heroku create 名付けたいデプロイ先の名前　//付けたい名前がなければ入力なしでもOK
```
Heroku内にデプロイ先のディレクトリが作成されると、ターミナルに下記のように表示されます。

```:terminal
Creating app... done, ⬢ デプロイ先の名前
https://デプロイ先の名前.com/ | https://git.heroku.com/デプロイ先の名前.git
```
このデプロイ先ディレクトリに、作成したディレクトリを入れていきます。
下記の順でターミナルにコマンドを入力します。

```:terminalコマンド
$ git add .
```

```:terminalコマンド
$ git commit -m 'init'
```

```:terminalコマンド
$ git push heroku master
```
最終的に下記のように表示されれば完了です。

```:terminal
remote:        https://デプロイ先の名前.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/デプロイ先の名前.git
 * [new branch]      master -> master
```
これでデプロイ完了です。
## ファイルを更新する時
まず、更新するファイルが入ったディレクトリがherokuのデプロイ先のディレクトリと紐づいているかを確認します。

```:terminalコマンド
$ git remote -v
```

### 紐づいている場合
紐づいている場合は、ターミナルに下記のように表記されます。

```:terminal
heroku https://git.heroku.com/heroku上のデプロイ先の名前 (fetch)
heroku https://git.heroku.com/heroku上のデプロイ先の名前 (push)
```

### 紐づいていない場合
紐づいていない場合は、ターミナルに下記のように表記されます。

```:terminal
fatal: not a git repository (or any of the parent directories): .git
```

その場合は、ターミナルに下記の順に入力し、空のディレクトリを作成し、heroku上のデプロイしたいディレクトリと紐付けを行います。

```:terminalコマンド
$ git init　//空のディレクトリを作成
```
```:terminalコマンド
$ heroku git:remote -a heroku上のデプロイしたいディレクトリ名　
　　//herokuのデプロイ先と先ほど作成した空のtディレクトリを紐付け
```

その上で、下記の順にコマンドを入力し、ファイルを更新します。

```:terminalコマンド
$ git add .
```

```:terminalコマンド
$ git commit -m 'upd'
```

```:terminalコマンド
$ git push heroku master
```

# LINE Developersからbot作成
１）　LINE Developersにアクセスし、LINEアカウントでログイン
２）　プロバイダー作成
３）　新規チャンネルの作成
４）　チャネルアクセストークンとチャネルシークレットの取得
１）〜４）までは下記を参考に進みました。
[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)
５）　webhookの設定
Herokuから取得したデプロイ先のURLを入力。
/webhookをつけることを忘れずに。
![スクリーンショット 2020-11-22 12.20.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/964dc27f-002a-37b3-f795-7d8a6007afaf.png)

# QRコード
 完成したギニュー特戦隊判定LINEbotのQRコードがこちらです。
(*2021.7.24時点でherokuとの接続を終了しております。申し訳ありません。)
![スクリーンショット 2020-11-29 17.01.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/e4c4aabc-3397-a6c9-d026-29f17c6f6c8c.png)

どうでしたか皆さん？
ギニュー特戦隊に成る願いは叶えらましたか？？
![神龍.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/bb4ffa90-2150-05d7-7024-4ba76ffb9fa0.jpeg)



# その他の記事
[近すぎると小池都知事が『密です。』と連呼するデバイスを作ったら腹筋が崩壊したので、皆さんにも試して欲しい。](https://qiita.com/Teru_3/items/4570bd76d8feeaa37db4)

[誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。](https://qiita.com/Teru_3/items/80cecd138860fbd0c924)