42
27

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.

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

Last updated at Posted at 2020-11-29

ギニュー特戦隊に入りたい

皆さんも(特に男性なら)、人生で一度はギニュー特戦隊に入隊したいと思いましたよね?
今回はその願いを少しでも叶えるべく、次のようなボットを作成しました。
* まさかそんな人はいないと思いますが、ギニュー特戦隊を知らない方はこちらのwikipediaをご確認ください。

早速使ってみたい方は、一番下にQRコードを貼っておきますので、ご利用ください。
ちなみに、万が一、リクームとジース以外の方に似ていると判定されたら、その方は人間(ホモ・サピエンス)という概念を超えている方ですので、すぐにご連絡ください!

開発環境の下準備

1) VScodeのインストール
VScodeのインストールついては、googleなどで他の記事を検索してください。
2) node.jsとnpmのインストール
Macでの環境作りは、こちらの別の記事にまとめてあります。
参考にしてください。
Macにnode.js,npmのインストール方法

開発環境の準備

1) ginyuforceというフォルダを作成
2) VSCodeで上記フォルダを開き、フォルダ内にginyuforce.jsを作成
3) VSCode内でターミナルを開き、フォルダをnpm管理できるように初期化

terminalコマンド
$ npm init -y

#システムの概要
スクリーンショット 2020-11-29 17.11.59.png

Teachable Machineによる画像認識AIの作成

以前の記事(誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。)では画像判定AIをAIメーカーで作成したので、今回はTeachable Machineを使用し、画像判定AIを作成しました。
スクリーンショット 2020-11-29 14.57.23.png

* 使い方については、こちらの記事>【備忘録】Teachable Machineの利用方法が参考になりました。ぜひ一度お読みください。(よかったらLGTMをお願いします。)

ginyuforce.jsのコード

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サーバーを実行中です…`);

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

下記のコードを入力し、実行に必要なパッケージを入力する。

  1. Teachable Machineを動かすのに必要なパッケージ
terminalコマンド
$ npm i @line/bot-sdk express

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

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

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

下準備

1) Heroku CLIのインストール
https://devcenter.heroku.com/articles/heroku-cli

2) 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作成

1) LINE Developersにアクセスし、LINEアカウントでログイン
2) プロバイダー作成
3) 新規チャンネルの作成
4) チャネルアクセストークンとチャネルシークレットの取得
1)〜4)までは下記を参考に進みました。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest
5) webhookの設定
Herokuから取得したデプロイ先のURLを入力。
/webhookをつけることを忘れずに。
スクリーンショット 2020-11-22 12.20.39.png

QRコード

完成したギニュー特戦隊判定LINEbotのQRコードがこちらです。
(*2021.7.24時点でherokuとの接続を終了しております。申し訳ありません。)
スクリーンショット 2020-11-29 17.01.09.png

どうでしたか皆さん?
ギニュー特戦隊に成る願いは叶えらましたか??
神龍.jpg

その他の記事

近すぎると小池都知事が『密です。』と連呼するデバイスを作ったら腹筋が崩壊したので、皆さんにも試して欲しい。

誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。

42
27
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
42
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?