Bot Framework v4 (node.js) をイチから学ぶ (1) オウム返しができるまで

今更ながら、Bot Framework v4 (node.js) を **やりたいこと** を実装できることを目標に、イチから学んでみることにしました。

## 執筆時点のバージョン: 

- Bot Framework (javascript): v4.9.3
- node.js: v12.16.3
- npm: v6.14.4

※ Bot Framework SDK (C#) はある程度触っている & node.js はそれほど詳しくないため、分かりにくいところや間違っているところあればご指摘ください。(プルリク的に模範解答を添えて修正依頼をくださると助かります)

## Bot Framework v4 (node.js) をイチから学ぶ シリーズ
- (1) オウム返しができるまで(←イマココ)
- [(2) Dialog で処理をブロック化して呼び出し実行](https://qiita.com/annie/items/c85aba4007e7aa9c374c)

# 必要なコード

```
EchoBot
+- bot.js          // Chatbot としての挙動
+- index.js        // API としての基本動作
+- package.json    // 必要なライブラリーや依存関係など
+- .env 　　　　　　// 環境変数を設定
```

Bot Framework の [Bot Builder Samples](https://github.com/microsoft/BotBuilder-Samples) にある [EchoBot](https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs/02.echo-bot) から上記ファイルをダウンロードするなどして入手、編集するベースとすれば OK です。

# 手順

## チャットボットの稼働に必要なライブラリーやファイルを確認する

node.js の実行ファイルとして index.js が設定されていますので、こちらで必要なライブラリーやファイルを確認しておきます。(※編集作業ナシ)

ライブラリーはBot Framework を使う上で必要となる **botbuilder** の他、**restify**, **dotenv**, **path** が必要です。
Azure Bot Service の公開に必要な ID などは **.env** で設定します。
チャットボットの挙動を記述する **EchoBot** (ファイル名は *bot.js*) を読み込んでいます。

```index.js
const path = require('path');
const restify = require('restify');

// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
const { BotFrameworkAdapter } = require('botbuilder');

// This bot's main dialog.
const { EchoBot } = require('./bot');

// Read environment variables from .env file
const dotenv = require('dotenv');
// Import required bot configuration.
const ENV_FILE = path.join(__dirname, '.env');
dotenv.config({ path: ENV_FILE });

// Create adapter.
// See https://aka.ms/about-bot-adapter to learn more about how bots work.
const adapter = new BotFrameworkAdapter({
    appId: process.env.MicrosoftAppId,
    appPassword: process.env.MicrosoftAppPassword
});

 :
(後略)
```

チャットボット本体は Web API で、ローカルで実行した場合は localhost:3978/api/messages で待ち受けます。
EchoBot から bot インスタンスを作成し、HTTP リクエストがあった場合に作動します。

```index.js
(前略)
 :

// Create HTTP server
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, () => {
    console.log(`\n${ server.name } listening to ${ server.url }`);
    console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
    console.log('\nTo talk to your bot, open the emulator select "Open Bot"');
});

 :
(中略)
 :

// Create the main dialog.
const bot = new EchoBot();

// Listen for incoming requests.
server.post('/api/messages', (req, res) => {
    adapter.processActivity(req, res, async (context) => {
        // Route to main dialog.
        await bot.run(context);
    });
});

 :
(後略)
```

## チャットボットを起動する

コマンドラインから *npm install* で必要なライブラリーをインストールします。
その後、 *node index.js*、Package.json をサンプルからそのままで使っている場合は *npm start* で index.js が起動します。

![01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/650147ff-4f7e-93c6-2b53-c7deb0d11c9f.jpeg)

Bot Framework Emulator から https://localhost:3978/api/messages にアクセスして挙動を確認します。

![02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/27a74512-9606-9275-6301-a2cdeca4270c.jpeg)

[EchoBot](https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs/02.echo-bot) をそのまま使っている場合は、以下のように初期メッセージと、ユーザー入力に対するオウム返しが返答されます。

![03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/1dc6c9a6-49b7-1801-4767-45ab2db941d1.jpeg)


## ユーザーからメッセージが来たら返信する

ユーザーからのメッセージ (HTTPリクエスト) に対する動作は **EchoBot** (ファイル名は *bot.js*) に記述します。

- **Activity**: ユーザーの接続やメッセージの受信&返信を含む、すべての動作(アクティビティ)
- **TurnContext**: ボットの制御(とその情報)。ユーザーとの受信、返信の内容を履歴として保持しています

ユーザーの動作は Activity で取得することができ、メッセージを受信したときは ActivityHander.onMessage が参照されます。
ActivityHander で onMessage を検知したとき、TurnContext.sendActivity メソッドで返信することができます。 

ここではユーザーからメッセージを受信したとき、「hello!」と返信する処理のみを記述するように、既存の *bot.js* を修正します。next() でユーザーからの受信を待ちます;

```bot.js
const { ActivityHandler } = require('botbuilder');

class EchoBot extends ActivityHandler {
    constructor() {
        super();
        // メッセージを受信したとき
        this.onMessage(async (turnContext, next) => {
            // 返信して返答を待つ
            await turnContext.sendActivity('hello!');
            await next();
        });
    }
}

module.exports.EchoBot = EchoBot;
```

起動して Emulator から動作を確認します。

![11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/e034afb9-6bc5-39bc-2392-366bb2c5efc6.jpeg)


## ユーザーからのメッセージ内容を取得する

TurnContext にユーザーとの受信、返信情報を保持しており、直前のユーザーからのメッセージは TurnContext.activity プロパティから取得できます。text(テキストメッセージ) または attatchment((あれば)添付ファイル) が取得できます。
取得したメッセージをそのままオウム返しするように記述します。

```bot.js
const { ActivityHandler } = require('botbuilder');

class EchoBot extends ActivityHandler {
    constructor() {
        super();
        this.onMessage(async (turnContext, next) => {
            // ユーザーのメッセージ内容を取得して、おうむ返し(Echo)の返答を送信する
            const replyText = turnContext.activity.text + 'って言ったね！';
            await turnContext.sendActivity(replyText);
            await next();
        });
    }
}

module.exports.EchoBot = EchoBot;
```

![12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/0f92a566-8080-ef09-a0d3-d1302cc57447.jpeg)


## Welcome メッセージを送信する

ActivityHander.onMembersAdded でユーザーが接続したことを検出できるので、その際にメッセージを送信する動作を追加します。(※接続したユーザークライアントによりできない場合アリ)

```bot.js
const { ActivityHandler } = require('botbuilder');

class EchoBot extends ActivityHandler {
    constructor() {
        super();
        this.onMembersAdded(async (turnContext, next) =>{
            await turnContext.sendActivity('はじめまして！');
            await next();
        });
        this.onMessage(async (turnContext, next) => {
            const replyText = turnContext.activity.text + 'って言ったね！';
            await turnContext.sendActivity(replyText);
            await next();
        });
    }
}

module.exports.EchoBot = EchoBot;
```

![13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/ca0f5040-97ec-3b31-e2bf-d55950b3855a.jpeg)