1
1

More than 3 years have passed since last update.

node.js expressでWebの画面で使えるオウム返しチャットボットを作成する

Posted at

はじめに

今回はWebの画面で動く、オウム返しチャットボットをnode.js expressで作成します。
これを土台として、次回と次々回で以下のことをやっていきたいと思っています。

次回:
 エクセルをナレッジベースとしたQ&A機能追加
 
次々回:
 Herokuにデプロイして、Google Analytics(これが一番やりたい)と連携して
 質問分析とメンテナンス

image.png

完成品はこちらからご確認いただけます
https://ponkotsueasychatbot.herokuapp.com/

参考にしたUIのソースコード
https://codepen.io/sajadhsm/pen/odaBdd

いざ、作成

ベースを作成する

nodeアプリの作成

npm init --y

 
今回~次々回までに必要なライブラリを設定

npm install dotenv express ejs xlsx elasticlunr date-utils --save

説明

ライブラリ 用途 備考
dotenv 設定ファイルの読み込みに必要
express サーバサイドJavascriptの実行環境として活用
ejs サーバサイドで作成した情報を画面表示するのに活用
xlsx エクセルファイルの読み込みに必要 次回活用
elasticlunr 全文検索に活用 次回活用
date-utils サーバサイドで日付処理を行うのに必要

 

index.jsを作成

index.jsのソースコード(クリックして展開)

// ******************************************************************//
// 初期設定関連                                  
// ******************************************************************//
var express = require("express");
var app = express();
require('date-utils');
require('dotenv').config();
app.set("view engine", "ejs");
app.use('/static', express.static('public'));

//////////////////////////////////////////////////////
// 初期表示 『 / 』にアクセスしたときの処理
app.get('/', function (req, res,next) {
  (async () => {
    // 現在時間の取得
    var dt = new Date();
    var nowTime = dt.toFormat("HH24:MI");
    // 画面を表示する
    res.render("index", { 
      title: process.env.APPLICATION_TITLE, 
      nowTime: nowTime, 
      userName: process.env.USER_NAME, 
      botName: process.env.BOT_NAME, 
      sorry_message: process.env.SORRY_MESSAGE, 
      bot1stMessage: process.env.BOT_1ST_MESSAGE});
    })().catch(next);
});

//////////////////////////////////////////////////////
// メッセージを受け取ったときにオウム返しする
app.get('/sendMsg', function (req, res,next) {
  (async () => {
    // 入力されたメッセージ
    var originalKeyword = req.query.keyword;
    // ここでオウム返しを実装
    res.send(originalKeyword + "って本当ですか?");
  })().catch(next);
});

//////////////////////////////////////////////////////
// サーバ起動
app.listen(process.env.PORT, "0.0.0.0", function() {
  console.log("server starting on " + process.env.PORT);
});

image.png

index.ejsを作成

index.ejsのソースコード(クリックして展開)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title><%= title %></title>
</head>
<body>
  <section class="msger">
    <!-- チャットウィンドウのヘッダ -->
    <header class="msger-header">
      <div class="msger-header-title">
        <i class="fas fa-comment-alt"></i> <%= title %>
      </div>
      <div class="msger-header-options">
        <span><i class="fas fa-cog"></i></span>
      </div>
    </header>

    <!-- チャットウィンドウのメイン部分 -->
    <main class="msger-chat">
      <div class="msg left-msg">
        <div
        class="msg-img"
        style="background-image: url(/static/image/neko.jpg)">
        </div>

        <div class="msg-bubble">
          <div class="msg-info">
            <div class="msg-info-name"><%= botName %></div>
            <div class="msg-info-time"><%= nowTime %></div>
          </div>
          <div class="msg-text">
            <%= bot1stMessage %>
          </div>
        </div>
      </div>
    </main>
    <!-- メッセージ送信フォーム -->
    <form class="msger-inputarea">
      <input type="text" class="msger-input" placeholder="メッセージを入力してください">
      <button type="submit" class="msger-send-btn">送信</button>
    </form>
  </section>

  <!-- メッセージ送信スクリプト -->
  <script>
    const msgerForm = get(".msger-inputarea");
    const msgerInput = get(".msger-input");
    const msgerChat = get(".msger-chat");
    // アイコン
    const BOT_IMG = "/static/image/neko.jpg";
    const PERSON_IMG = "/static/image/you.jpg";
    // 名前
    const BOT_NAME = "<%= botName %>";
    const PERSON_NAME = "<%= userName %>";
    // メッセージ送信時の処理
    msgerForm.addEventListener("submit", event => {
      event.preventDefault();
      const msgText = msgerInput.value;
      if (!msgText) return;
      appendMessage(PERSON_NAME, PERSON_IMG, "right", msgText);
      msgerInput.value = "";
      // API送信
      var url = "/sendMsg?keyword="+msgText;
      var request = new XMLHttpRequest();
      request.open('GET', url, true);
      request.responseType = 'text';
      // APIメッセージ取得時の処理
      request.onload = function () {
        var msgText = this.response;
        setTimeout(() => {
          appendMessage(BOT_NAME, BOT_IMG, "left", msgText);
        }, 900);
      };
      // APIメッセージ送信
      request.send();
    });
    // メッセージウィンドウ更新処理
    function appendMessage(name, img, side, text) {
      const msgHTML = `
        <div class="msg ${side}-msg">
          <div class="msg-img" style="background-image: url(${img})"></div>
          <div class="msg-bubble">
            <div class="msg-info">
              <div class="msg-info-name">${name}</div>
              <div class="msg-info-time">${formatDate(new Date())}</div>
            </div>
            <div class="msg-text">${text}</div>
          </div>
        </div>
      `;
      msgerChat.insertAdjacentHTML("beforeend", msgHTML);
      msgerChat.scrollTop += 500;
    }
    function get(selector, root = document) {
      return root.querySelector(selector);
    }
    function formatDate(date) {
      const h = "0" + date.getHours();
      const m = "0" + date.getMinutes();
      return `${h.slice(-2)}:${m.slice(-2)}`;
    }  
  </script>

  <!--スタイル-->
  <style type="text/css">
    :root {
      --body-bg: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
      --msger-bg: #fff;
      --border: 2px solid #ddd;
      --left-msg-bg: #ececec;
      --right-msg-bg: #579ffb;
    }
    html {
      box-sizing: border-box;
    }
    *,
    *:before,
    *:after {
      margin: 0;
      padding: 0;
      box-sizing: inherit;
    }
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background-image: var(--body-bg);
      font-family: Helvetica, sans-serif;
    }
    .msger {
      display: flex;
      flex-flow: column wrap;
      justify-content: space-between;
      width: 100%;
      max-width: 867px;
      margin: 25px 10px;
      height: calc(100% - 50px);
      border: var(--border);
      border-radius: 5px;
      background: var(--msger-bg);
      box-shadow: 0 15px 15px -5px rgba(0, 0, 0, 0.2);
    }
    .msger-header {
      display: flex;
      justify-content: space-between;
      padding: 10px;
      border-bottom: var(--border);
      background: #eee;
      color: #666;
    }
    .msger-chat {
      flex: 1;
      overflow-y: auto;
      padding: 10px;
    }
    .msger-chat::-webkit-scrollbar {
      width: 6px;
    }
    .msger-chat::-webkit-scrollbar-track {
      background: #ddd;
    }
    .msger-chat::-webkit-scrollbar-thumb {
      background: #bdbdbd;
    }
    .msg {
      display: flex;
      align-items: flex-end;
      margin-bottom: 10px;
    }
    .msg:last-of-type {
      margin: 0;
    }
    .msg-img {
      width: 50px;
      height: 50px;
      margin-right: 10px;
      background: #ddd;
      background-repeat: no-repeat;
      background-position: center;
      background-size: cover;
      border-radius: 50%;
    }
    .msg-bubble {
      max-width: 450px;
      padding: 15px;
      border-radius: 15px;
      background: var(--left-msg-bg);
    }
    .msg-info {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    .msg-info-name {
      margin-right: 10px;
      font-weight: bold;
    }
    .msg-info-time {
      font-size: 0.85em;
    }
    .left-msg .msg-bubble {
      border-bottom-left-radius: 0;
    }
    .right-msg {
      flex-direction: row-reverse;
    }
    .right-msg .msg-bubble {
      background: var(--right-msg-bg);
      color: #fff;
      border-bottom-right-radius: 0;
    }
    .right-msg .msg-img {
      margin: 0 0 0 10px;
    }
    .msger-inputarea {
      display: flex;
      padding: 10px;
      border-top: var(--border);
      background: #eee;
    }
    .msger-inputarea * {
      padding: 10px;
      border: none;
      border-radius: 3px;
      font-size: 1em;
    }
    .msger-input {
      flex: 1;
      background: #ddd;
    }
    .msger-send-btn {
      margin-left: 10px;
      background: rgb(0, 196, 65);
      color: #fff;
      font-weight: bold;
      cursor: pointer;
      transition: background 0.23s;
    }
    .msger-send-btn:hover {
      background: rgb(0, 180, 50);
    }
    .msger-chat {
      background-color: #fcfcfe;
      background-image: url("/static/image/bkimg.jpg");
    }
    </style>
</body>
</html>

image.png

.envを作成

.envのソースコード(クリックして展開)
# アプリの公開ポート番号
PORT=8085

# 回答がないとき
SORRY_MESSAGE=すみません。わかりませんでした。

# アプリケーションのタイトル
APPLICATION_TITLE=ぽんこつ猫ちゃんオウム返し

# ユーザー名
USER_NAME=You

# ボット名
BOT_NAME=ぽんこつ猫ちゃん

# ボットの初回メッセージ
BOT_1ST_MESSAGE=こんにちは。どんなことでも僕に相談してくださいね😄

image.png

 

package.jsonのscriptsに『"start": "node index.js",』を追加

package.jsonのscriptsに『"start": "node index.js",』を追加(参考ファイルをクリックして展開)
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "date-utils": "^1.2.21",
    "dotenv": "^8.2.0",
    "ejs": "^2.7.1",
    "elasticlunr": "^0.9.5",
    "express": "^4.17.1",
    "xlsx": "^0.16.9"
  }
}


image.png

 

public/imageに画像ファイルを配置

bkimg.jpg
bkimg.jpg

neko.jpg
neko.jpg

you.jpg
you.jpg

image.png

動作確認してみる

npm start

http://localhost:8085/』にアクセスして、動作確認してみます。
投げかけたメッセージに対して、『<メッセージ>って本当ですか?』とオウム返しされるはずです。

image.png

 
 

herokuにデプロイする

※ponkotsueasychatbotの部分はこのアプリの独自の名前

$ heroku login
Create a new Git repository
Initialize a git repository in a new or existing directory

$ cd my-project/
$ git init
$ heroku git:remote -a ponkotsueasychatbot
Deploy your application
Commit your code to the repository and deploy it to Heroku using Git.

$ git add .
$ git commit -am "make it better"
$ git push heroku master

 
 

herokuでの動作確認

こちらのURLから確認できます
https://ponkotsueasychatbot.herokuapp.com/

 
 
Twitterはじめました!
フォロー技術QAいただけたら嬉しいです!

1
1
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
1
1