使用 TypeScript 寫機器人
雖然本書是以 Node.js 為主要的程式開發語言,不過開發階段也可以使用 TypeScript,再將它編譯成 JavaScript 語法讓 Node.js 執行。這個範例說明幾個使用 TypeScript 的經驗與作法。
安裝 TypeScript 相關套件
可以安裝下列套件到 devDependencies 區段:
- typescript: TypeScript 的編譯器及其相關工具。
- ts-node: 可以用來直接執行 *.ts 檔案,它會自己編譯。
- nodemon: 在開發階段的好用工具,它偵測到檔案修改後會重新執行 node 應用程式,加快開發測試的步驟。
- @types/node: 讓 TypeScript 的工具(包含像是 Visual Studio Code)這樣的 IDE 環境可以知道 node 的資料型別。
- (選擇性) @types/restify: 讓 TypeScript 的工具認認識 restify 的資料型別。
- (選擇性) tslint: 想自虐的話可以裝這個工具檢查寫的 typescript 語法。
設定 TypeScript 編譯選項
要設定 TypeScript 的編譯行為,可以在目錄中加入一個 tsconfig.json 的檔案來進行設定,例如可以設定成這樣:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist"
},
"files": [
"app.ts"
]
}
這裡主要設定了幾個項目:
- target: 設定在 TypeScript 中用的語法標準。
- module: 設定產生模組的程式碼的語法標準。
- moduleResolution: 設定用哪一種標準解析模組。可以參考這頁說明。
- outDir: 設定編譯後產生的檔案要放在哪個目錄中。
更多的選項可以參考 TypeScript 的 Compiler Options 頁面說明。
設定常用且方便的 Scripts
因為是使用 TypeScript 做開發,最終還是要編譯過後再用 node 執行,所以可以新增幾個方便的 scripts 來簡化操作:
build
: 要編譯 typescript 的程式碼的指令,最簡單就是執行tsc
如果有其它的參數也可以加在這裡。postinstall
: 在執行完npm install
指令後自動跑一次npm run build
的指令讓它編譯一次。satch
: 使用 nodemon 搭配 ts-node 來執行 node 程式,ts 檔案一經修改就會自動重啟程式,在 debug 階段很有用。start
: 普通的啟動指令,先跑一次 build 確定都編譯了再執行。
所以 package.json 檔案內容可能會像這樣:
{
"name": "my-ts-bot",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"build": "tsc",
"postinstall": "npm run build",
"watch": "nodemon -x ts-node -- app.ts",
"start": "npm run build && node dist/app.js"
},
"dependencies": {
"botbuilder": "^3.8.4",
"request": "^2.81.0",
"restify": "^5.0.1"
},
"devDependencies": {
"@types/node": "^8.0.15",
"@types/request": "^2.0.0",
"@types/restify": "^5.0.1",
"nodemon": "^1.11.0",
"ts-node": "^3.3.0",
"tslint": "^5.5.0",
"typescript": "^2.4.2"
}
}
撰寫程式碼
以 2.3 的範例為例:讓使用者輸入一個關鍵字,然後在 Bing 搜尋相關的圖片,再回覆給使用者。
import * as builder from 'botbuilder';
import * as restify from 'restify';
import * as request from 'request';
const IMGSEARCH_URL: string = 'https://api.cognitive.microsoft.com/bing/v5.0/images/search'
const BING_KEY: string = process.env.BING_API_KEY;
let server: restify.Server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, () => {
console.log('%s listening to %s', server.name, server.url);
});
let connector: builder.ChatConnector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
server.post('/api/messages', connector.listen());
let defaultDialog: builder.IDialogWaterfallStep = (session: builder.Session): void => {
let msg: builder.IMessage = session.message;
let postOpt: request.Options = {
url: `${IMGSEARCH_URL}?q=${msg.text}`,
method: 'POST',
json: true,
headers: {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': BING_KEY
}
};
session.sendTyping();
request(postOpt, (error, response: request.RequestResponse, body: any): void => {
// 建立回傳的訊息
let retMsg: builder.Message = new builder.Message(session);
let imgArr: Array<builder.HeroCard> = [];
body.value.forEach((img: any) => {
// 將資料製作成 Hero Card
let imgCard: builder.HeroCard = new builder.HeroCard(session)
.title(img.name)
.images([builder.CardImage.create(session, img.thumbnailUrl)]);
imgArr.push(imgCard);
});
retMsg.attachments(imgArr);
retMsg.attachmentLayout(builder.AttachmentLayout.carousel);
session.send(retMsg);
});
};
let bot: builder.UniversalBot = new builder.UniversalBot(connector, defaultDialog);
加上一些型別的資訊,當使用支援 TypeScript 語法解析的 IDE (如: Visual Studio Code) 時就會有很好的語法提示效果。