Azure の Functions Bot を TypeScript で作る(+ VSCodeでデバッグする)

Bot Application が Azure Functions で作れるようになっていました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/8227/d28995cf-41ac-c440-351d-1a06bbf0662d.png)

Node.js か C# のいくつかのテンプレートから選択できます。

Node.js → Basic を選択して作った Functions は JavaScript なので、これを TypeScript に変えてみます。

## Node.js のサンプルをダウンロードする

Bot を作成したあと、ビルド → zip ファイルをダウンロード でソースコード一式がダウンロードできます。

そのディレクトリ構成は次の図に。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/8227/a4583c7c-3c57-a984-295c-f3a31efb618c.png)

## TypeScript に書き換える

``/messages`` ディレクトリが Function のソースなので、ターミナルでここに移動し、

```
tsc init
```

を実行します。すると ``tsconfig.json`` が作成されるので、それを次のように書き換えます。

**messages/tsconfig.json**

```json
{
  "compilerOptions": {
    "target": "es2015",
    "module": "commonjs",
    "sourceMap": true,
    "strict": false,
    "esModuleInterop": true
  }
}
```

コメント行や説明のコメントは省略しています。既定値から変えたのは次の通り。

* ``"target": "es2015"`` - Azure Functions の実行環境は [node.js v8.4.0 以上を推奨](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-node#node-version-and-package-management) とのことで、node v8.2.0 は ES2016 までの API をすべてサポートしています
* ``"sourceMap": true`` - ソースマップを有効にし、ローカルでの TypeScript デバッグを可能にします。
* ``"strict": false`` - とりあえず厳密な型チェックはOFFで

次に TypeScript で必要なモジュールをインストールします。

ターミナルで ``messages`` ディレクトリに移動し、

```
npm install @types/node --save-dev
```

を実行します。実行後、 ``package.json`` に ``@types/node`` が追加されます。

**messages/package.json**

```json
{
  "name": "emptybot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "botbuilder": "^3.13.1",
    "botbuilder-azure": "^3.0.4"
  },
  "devDependencies": {
    "@types/node": "^10.5.1",   <-- ここが追加された
    "restify": "^5.0.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

次に ``index.js`` をリネームし ``index.ts`` とし、内容を次のように書き換えます。

**messages/index.ts**

```typescript
import { ChatConnector, MemoryBotStorage, UniversalBot, Session } from 'botbuilder';
import { BotServiceConnector, AzureTableClient, AzureBotStorage } from 'botbuilder-azure';
import * as path from 'path';

const useEmulator = (process.env.NODE_ENV == 'development');

const connector = useEmulator ? new ChatConnector() : new BotServiceConnector({
    appId: process.env['MicrosoftAppId'],
    appPassword: process.env['MicrosoftAppPassword'],
    openIdMetadata: process.env['BotOpenIdMetadata']
});

/*----------------------------------------------------------------------------------------
* Bot Storage: This is a great spot to register the private state storage for your bot. 
* We provide adapters for Azure Table, CosmosDb, SQL Azure, or you can implement your own!
* For samples and documentation, see: https://github.com/Microsoft/BotBuilder-Azure
* ---------------------------------------------------------------------------------------- */

const tableName = 'botdata';
const azureTableClient = new AzureTableClient(tableName, process.env['AzureWebJobsStorage']);
const storage = useEmulator ? new MemoryBotStorage() : new AzureBotStorage({ gzipData: false }, azureTableClient);

const bot = new UniversalBot(connector);
bot.localePath(path.join(__dirname, './locale'));
bot.set('storage', storage);

bot.dialog('/', function (session: Session) {
    session.send('あなたは ' + session.message.text + 'と言いましたね。');
});

if (useEmulator) {
    const restify = require('restify');
    const server = restify.createServer();
    server.listen(3978, function() {
        console.log('test bot endpont at http://localhost:3978/api/messages');
    });
    server.post('/api/messages', connector.listen());    
} else {
    module.exports = connector.listen();
}
```

処理内容はほぼ変えず、文法を TypeScript にしただけです（``MemoryBotStorage`` を使うとこだけ処理を追加してます、これをしないとローカルで動かなかったので）。

``messages`` ディレクトリで

```
tsc
```

を実行します。すると同じディレクトリに ``index.js`` と ``index.js.mmap`` が生成されます。

この ``index.js`` をこれまでの ``index.js`` の代わりに Azure にアップロードすれば、関数はいままでどおり動作します。

## ローカルでデバッグする（VSCode使用)

``launch.json`` の定義が古いのでちょっと追記します。

**messages/.vscode/launch.json**

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch",
            "type": "node",
            "request": "launch",
            "protocol": "inspector",  <-- この行を追加
            "program": "${workspaceRoot}/index.js",
            "cwd": "${workspaceRoot}",
            "env": {
                "NODE_ENV": "development"
            }
        }
    ]
}
```

``index.ts``(.js じゃないよ) の適当な行にブレークポイントを仕掛けて、メニュー -> デバッグ -> デバッグの開始 をします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/8227/9daa452f-fa27-666d-e0c9-5bacfdd4a2b5.png)

ちゃんと止まるはずです。

[BotFramework-Emulator](https://github.com/Microsoft/BotFramework-Emulator/) にローカルのエンドポイントである ``http://localhost:3978/api/messages`` を指定すれば、ボットのデバッグができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/8227/77955829-c30e-6890-3c39-cd29d3274b6e.png)

## 環境

* macOS - 10.13.5
* Node.js - v8.9.4
* npm - 5.6.0
* TypeScript - 2.9.2
* Visual Studio Code(VSCode) - 1.24.1
* BotFramework-Emulator - 3.5.36

## 参考

* [祝GA！Azure Bot Service 概略&作成～公開手順 - Qiita](https://qiita.com/annie/items/cf93a7a832123946a7da)
* [Azure Functions を TypeScript で書いてみる。 - Qiita](https://qiita.com/TsuyoshiUshio@github/items/41c085a5e68fea0ff9bb)
* [Debug Node.js Apps using VS Code](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_supported-nodelike-runtimes)