From 7fdbfc73730b855043f7a6d0936bde0e986f999e Mon Sep 17 00:00:00 2001 From: Steven Ickman Date: Wed, 16 Mar 2016 20:29:23 -0700 Subject: [PATCH] ported BBNode project to VS Code and restructured things a bit for GIT --- Node/package.json | 27 + Node/src/.vscode/tasks.json | 190 ++ .../typings/chrono-node/chrono-node.d.ts | 25 + Node/src/Scripts/typings/express/express.d.ts | 1817 ++++++++++++++ .../Scripts/typings/form-data/form-data.d.ts | 16 + .../typings/node-uuid/node-uuid-base.d.ts | 46 + .../typings/node-uuid/node-uuid-cjs.d.ts | 15 + .../Scripts/typings/node-uuid/node-uuid.d.ts | 36 + Node/src/Scripts/typings/node/node.d.ts | 2093 +++++++++++++++++ Node/src/Scripts/typings/request/request.d.ts | 186 ++ .../typings/skype-botkit/skype-botkit.d.ts | 144 ++ .../typings/sprintf-js/sprintf-js.d.ts | 78 + Node/src/Session.ts | 245 ++ Node/src/botbuilder.d.ts | 1627 +++++++++++++ Node/src/botbuilder.ts | 39 + Node/src/bots/BotConnectorBot.ts | 281 +++ Node/src/bots/SkypeBot.ts | 226 ++ Node/src/bots/SlackBot.ts | 201 ++ Node/src/bots/TextBot.ts | 170 ++ Node/src/consts.ts | 19 + Node/src/dialogs/CommandDialog.ts | 138 ++ Node/src/dialogs/Dialog.ts | 38 + Node/src/dialogs/DialogAction.ts | 95 + Node/src/dialogs/DialogCollection.ts | 63 + Node/src/dialogs/EntityRecognizer.ts | 207 ++ Node/src/dialogs/IntentDialog.ts | 263 +++ Node/src/dialogs/LuisDialog.ts | 124 + Node/src/dialogs/Prompts.ts | 317 +++ Node/src/dialogs/SimpleDialog.ts | 24 + Node/src/interfaces.d.ts | 127 + Node/src/storage/Storage.ts | 31 + Node/src/tsconfig.json | 13 + Node/src/utils.ts | 17 + 33 files changed, 8938 insertions(+) create mode 100644 Node/package.json create mode 100644 Node/src/.vscode/tasks.json create mode 100644 Node/src/Scripts/typings/chrono-node/chrono-node.d.ts create mode 100644 Node/src/Scripts/typings/express/express.d.ts create mode 100644 Node/src/Scripts/typings/form-data/form-data.d.ts create mode 100644 Node/src/Scripts/typings/node-uuid/node-uuid-base.d.ts create mode 100644 Node/src/Scripts/typings/node-uuid/node-uuid-cjs.d.ts create mode 100644 Node/src/Scripts/typings/node-uuid/node-uuid.d.ts create mode 100644 Node/src/Scripts/typings/node/node.d.ts create mode 100644 Node/src/Scripts/typings/request/request.d.ts create mode 100644 Node/src/Scripts/typings/skype-botkit/skype-botkit.d.ts create mode 100644 Node/src/Scripts/typings/sprintf-js/sprintf-js.d.ts create mode 100644 Node/src/Session.ts create mode 100644 Node/src/botbuilder.d.ts create mode 100644 Node/src/botbuilder.ts create mode 100644 Node/src/bots/BotConnectorBot.ts create mode 100644 Node/src/bots/SkypeBot.ts create mode 100644 Node/src/bots/SlackBot.ts create mode 100644 Node/src/bots/TextBot.ts create mode 100644 Node/src/consts.ts create mode 100644 Node/src/dialogs/CommandDialog.ts create mode 100644 Node/src/dialogs/Dialog.ts create mode 100644 Node/src/dialogs/DialogAction.ts create mode 100644 Node/src/dialogs/DialogCollection.ts create mode 100644 Node/src/dialogs/EntityRecognizer.ts create mode 100644 Node/src/dialogs/IntentDialog.ts create mode 100644 Node/src/dialogs/LuisDialog.ts create mode 100644 Node/src/dialogs/Prompts.ts create mode 100644 Node/src/dialogs/SimpleDialog.ts create mode 100644 Node/src/interfaces.d.ts create mode 100644 Node/src/storage/Storage.ts create mode 100644 Node/src/tsconfig.json create mode 100644 Node/src/utils.ts diff --git a/Node/package.json b/Node/package.json new file mode 100644 index 000000000..858c8f703 --- /dev/null +++ b/Node/package.json @@ -0,0 +1,27 @@ +{ + "name": "botbuilder", + "author": "Microsoft Corp.", + "description": "Bot Builder is a dialog system for building rich bots on virtually any platform.", + "version": "0.5.0", + "license": "MIT", + "keywords": [ + "bots", + "chatbots" + ], + "bugs": { + "url": "https://github.com/Microsoft/BotBuilder/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/BotBuilder.git" + }, + "main": "./lib/botbuilder.js", + "typings": "./lib/botbuilder.d.ts", + "dependencies": { + "chrono-node": "^1.1.3", + "node-uuid": "^1.4.7", + "request": "^2.69.0", + "skype-botkit": "http://skypetriviabot.blob.core.windows.net/botkit/skype-botkit.tar.gz", + "sprintf-js": "^1.0.3" + } +} diff --git a/Node/src/.vscode/tasks.json b/Node/src/.vscode/tasks.json new file mode 100644 index 000000000..60b851d84 --- /dev/null +++ b/Node/src/.vscode/tasks.json @@ -0,0 +1,190 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process + +// A task runner that calls the Typescript compiler (tsc) and +// Compiles a HelloWorld.ts program +/* +{ + "version": "0.1.0", + + // The command is tsc. Assumes that tsc has been installed using npm install -g typescript + "command": "tsc", + + // The command is a shell script + "isShellCommand": true, + + // Show the output window only if unrecognized errors occur. + "showOutput": "silent", + + // args is the HelloWorld program to compile. + "args": ["HelloWorld.ts"], + + // use the standard tsc problem matcher to find compile problems + // in the output. + "problemMatcher": "$tsc" +} +*/ + +// A task runner that calls the Typescript compiler (tsc) and +// compiles based on a tsconfig.json file that is present in +// the root of the folder open in VSCode +{ + "version": "0.1.0", + + // The command is tsc. Assumes that tsc has been installed using npm install -g typescript + "command": "tsc", + + // The command is a shell script + "isShellCommand": true, + + // Show the output window only if unrecognized errors occur. + "showOutput": "silent", + + // Tell the tsc compiler to use the tsconfig.json from the open folder. + "args": ["-p", "."], + + // use the standard tsc problem matcher to find compile problems + // in the output. + "problemMatcher": "$tsc" +} + +// A task runner configuration for gulp. Gulp provides a less task +// which compiles less to css. +/* +{ + "version": "0.1.0", + "command": "gulp", + "isShellCommand": true, + "tasks": [ + { + "taskName": "less", + // Make this the default build command. + "isBuildCommand": true, + // Show the output window only if unrecognized errors occur. + "showOutput": "silent", + // Use the standard less compilation problem matcher. + "problemMatcher": "$lessCompile" + } + ] +} +*/ + +// Uncomment the following section to use jake to build a workspace +// cloned from https://github.com/Microsoft/TypeScript.git +/* +{ + "version": "0.1.0", + // Task runner is jake + "command": "jake", + // Need to be executed in shell / cmd + "isShellCommand": true, + "showOutput": "silent", + "tasks": [ + { + // TS build command is local. + "taskName": "local", + // Make this the default build command. + "isBuildCommand": true, + // Show the output window only if unrecognized errors occur. + "showOutput": "silent", + // Use the redefined Typescript output problem matcher. + "problemMatcher": [ + "$tsc" + ] + } + ] +} +*/ + +// Uncomment the section below to use msbuild and generate problems +// for csc, cpp, tsc and vb. The configuration assumes that msbuild +// is available on the path and a solution file exists in the +// workspace folder root. +/* +{ + "version": "0.1.0", + "command": "msbuild", + "args": [ + // Ask msbuild to generate full paths for file names. + "/property:GenerateFullPaths=true" + ], + "taskSelector": "/t:", + "showOutput": "silent", + "tasks": [ + { + "taskName": "build", + // Show the output window only if unrecognized errors occur. + "showOutput": "silent", + // Use the standard MS compiler pattern to detect errors, warnings + // and infos in the output. + "problemMatcher": "$msCompile" + } + ] +} +*/ + +// Uncomment the following section to use msbuild which compiles Typescript +// and less files. +/* +{ + "version": "0.1.0", + "command": "msbuild", + "args": [ + // Ask msbuild to generate full paths for file names. + "/property:GenerateFullPaths=true" + ], + "taskSelector": "/t:", + "showOutput": "silent", + "tasks": [ + { + "taskName": "build", + // Show the output window only if unrecognized errors occur. + "showOutput": "silent", + // Use the standard MS compiler pattern to detect errors, warnings + // and infos in the output. + "problemMatcher": [ + "$msCompile", + "$lessCompile" + ] + } + ] +} +*/ +// A task runner example that defines a problemMatcher inline instead of using +// a predefined one. +/* +{ + "version": "0.1.0", + "command": "tsc", + "isShellCommand": true, + "args": ["HelloWorld.ts"], + "showOutput": "silent", + "problemMatcher": { + // The problem is owned by the typescript language service. Ensure that the problems + // are merged with problems produced by Visual Studio's language service. + "owner": "typescript", + // The file name for reported problems is relative to the current working directory. + "fileLocation": ["relative", "${cwd}"], + // The actual pattern to match problems in the output. + "pattern": { + // The regular expression. Matches HelloWorld.ts(2,10): error TS2339: Property 'logg' does not exist on type 'Console'. + "regexp": "^([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", + // The match group that denotes the file containing the problem. + "file": 1, + // The match group that denotes the problem location. + "location": 2, + // The match group that denotes the problem's severity. Can be omitted. + "severity": 3, + // The match group that denotes the problem code. Can be omitted. + "code": 4, + // The match group that denotes the problem's message. + "message": 5 + } + } +} +*/ \ No newline at end of file diff --git a/Node/src/Scripts/typings/chrono-node/chrono-node.d.ts b/Node/src/Scripts/typings/chrono-node/chrono-node.d.ts new file mode 100644 index 000000000..b9385bd6c --- /dev/null +++ b/Node/src/Scripts/typings/chrono-node/chrono-node.d.ts @@ -0,0 +1,25 @@ + +declare module chrono_node { + export class ParsedResult { + start: ParsedComponents; + end: ParsedComponents; + index: number; + text: string; + ref: Date; + } + + export class ParsedComponents { + assign(component: string, value: number): void; + imply(component: string, value: number): void; + get(component: string): number; + isCertain(component: string): boolean; + date(): Date; + } + + export function parseDate(text: string, refDate?: Date, opts?: any): Date; + export function parse(text: string, refDate?: Date, opts?: any): ParsedResult[]; +} + +declare module "chrono-node" { + export = chrono_node; +} \ No newline at end of file diff --git a/Node/src/Scripts/typings/express/express.d.ts b/Node/src/Scripts/typings/express/express.d.ts new file mode 100644 index 000000000..160046435 --- /dev/null +++ b/Node/src/Scripts/typings/express/express.d.ts @@ -0,0 +1,1817 @@ +// Type definitions for Express 3.1 +// Project: http://expressjs.com +// Definitions by: Boris Yankov +// DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped + +/* =================== USAGE =================== + + import express = require('express'); + var app = express(); + + =============================================== */ + +declare module "express" { + import http = require('http'); + + // Merged declaration, e is both a callable function and a namespace + function e(): e.Express; + + module e { + interface IRoute { + path: string; + + method: string; + + callbacks: Function[]; + + regexp: any; + + /** + * Check if this route matches `path`, if so + * populate `.params`. + */ + match(path: string): boolean; + } + + class Route implements IRoute { + path: string; + + method: string; + + callbacks: Function[]; + + regexp: any; + match(path: string): boolean; + + /** + * Initialize `Route` with the given HTTP `method`, `path`, + * and an array of `callbacks` and `options`. + * + * Options: + * + * - `sensitive` enable case-sensitive routes + * - `strict` enable strict matching for trailing slashes + * + * @param method + * @param path + * @param callbacks + * @param options + */ + new(method: string, path: string, callbacks: Function[], options: any): Route; + } + + interface IRouter { + /** + * Map the given param placeholder `name`(s) to the given callback(s). + * + * Parameter mapping is used to provide pre-conditions to routes + * which use normalized placeholders. For example a _:user_id_ parameter + * could automatically load a user's information from the database without + * any additional code, + * + * The callback uses the samesignature as middleware, the only differencing + * being that the value of the placeholder is passed, in this case the _id_ + * of the user. Once the `next()` function is invoked, just like middleware + * it will continue on to execute the route, or subsequent parameter functions. + * + * app.param('user_id', function(req, res, next, id){ + * User.find(id, function(err, user){ + * if (err) { + * next(err); + * } else if (user) { + * req.user = user; + * next(); + * } else { + * next(new Error('failed to load user')); + * } + * }); + * }); + * + * @param name + * @param fn + */ + param(name: string, fn: Function): T; + + param(name: string[], fn: Function): T; + + /** + * Special-cased "all" method, applying the given route `path`, + * middleware, and callback to _every_ HTTP method. + * + * @param path + * @param fn + */ + all(path: string, fn?: (req: Request, res: Response, next: Function) => any): T; + + all(path: string, ...callbacks: Function[]): void; + + get(name: string): string; + + get(name: string, ...handlers: RequestFunction[]): T; + + get(name: RegExp, ...handlers: RequestFunction[]): T; + + post(name: string, ...handlers: RequestFunction[]): T; + + post(name: RegExp, ...handlers: RequestFunction[]): T; + + put(name: string, ...handlers: RequestFunction[]): T; + + put(name: RegExp, ...handlers: RequestFunction[]): T; + + del(name: string, ...handlers: RequestFunction[]): T; + + del(name: RegExp, ...handlers: RequestFunction[]): T; + + patch(name: string, ...handlers: RequestFunction[]): T; + + patch(name: RegExp, ...handlers: RequestFunction[]): T; + } + + export class Router implements IRouter { + new(options?: any): Router; + + middleware(): any; + + param(name: string, fn: Function): Router; + + param(name: any[], fn: Function): Router; + + all(path: string, fn?: (req: Request, res: Response, next: Function) => any): Router; + + all(path: string, ...callbacks: Function[]): void; + + get(name: string): string; + + get(name: string, ...handlers: RequestFunction[]): Router; + + get(name: RegExp, ...handlers: RequestFunction[]): Router; + + post(name: string, ...handlers: RequestFunction[]): Router; + + post(name: RegExp, ...handlers: RequestFunction[]): Router; + + put(name: string, ...handlers: RequestFunction[]): Router; + + put(name: RegExp, ...handlers: RequestFunction[]): Router; + + del(name: string, ...handlers: RequestFunction[]): Router; + + del(name: RegExp, ...handlers: RequestFunction[]): Router; + + patch(name: string, ...handlers: RequestFunction[]): Router; + + patch(name: RegExp, ...handlers: RequestFunction[]): Router; + } + + interface Handler { + (req: Request, res: Response, next?: Function): void; + } + + interface CookieOptions { + maxAge?: number; + signed?: boolean; + expires?: Date; + httpOnly?: boolean; + path?: string; + domain?: string; + secure?: boolean; + } + + interface Errback { (err: Error): void; } + + interface Session { + /** + * Update reset `.cookie.maxAge` to prevent + * the cookie from expiring when the + * session is still active. + * + * @return {Session} for chaining + * @api public + */ + touch(): Session; + + /** + * Reset `.maxAge` to `.originalMaxAge`. + */ + resetMaxAge(): Session; + + /** + * Save the session data with optional callback `fn(err)`. + */ + save(fn: Function): Session; + + /** + * Re-loads the session data _without_ altering + * the maxAge properties. Invokes the callback `fn(err)`, + * after which time if no exception has occurred the + * `req.session` property will be a new `Session` object, + * although representing the same session. + */ + reload(fn: Function): Session; + + /** + * Destroy `this` session. + */ + destroy(fn: Function): Session; + + /** + * Regenerate this request's session. + */ + regenerate(fn: Function): Session; + + user: any; + + error: string; + + success: string; + + views: any; + + count: number; + } + + interface Request { + + session: Session; + + /** + * Return request header. + * + * The `Referrer` header field is special-cased, + * both `Referrer` and `Referer` are interchangeable. + * + * Examples: + * + * req.get('Content-Type'); + * // => "text/plain" + * + * req.get('content-type'); + * // => "text/plain" + * + * req.get('Something'); + * // => undefined + * + * Aliased as `req.header()`. + * + * @param name + */ + get(name: string): string; + + header(name: string): string; + + headers: string[]; + + /** + * Check if the given `type(s)` is acceptable, returning + * the best match when true, otherwise `undefined`, in which + * case you should respond with 406 "Not Acceptable". + * + * The `type` value may be a single mime type string + * such as "application/json", the extension name + * such as "json", a comma-delimted list such as "json, html, text/plain", + * or an array `["json", "html", "text/plain"]`. When a list + * or array is given the _best_ match, if any is returned. + * + * Examples: + * + * // Accept: text/html + * req.accepts('html'); + * // => "html" + * + * // Accept: text/*, application/json + * req.accepts('html'); + * // => "html" + * req.accepts('text/html'); + * // => "text/html" + * req.accepts('json, text'); + * // => "json" + * req.accepts('application/json'); + * // => "application/json" + * + * // Accept: text/*, application/json + * req.accepts('image/png'); + * req.accepts('png'); + * // => undefined + * + * // Accept: text/*;q=.5, application/json + * req.accepts(['html', 'json']); + * req.accepts('html, json'); + * // => "json" + */ + accepts(type: string): string; + + accepts(type: string[]): string; + + /** + * Check if the given `charset` is acceptable, + * otherwise you should respond with 406 "Not Acceptable". + * + * @param charset + */ + acceptsCharset(charset: string): boolean; + + /** + * Check if the given `lang` is acceptable, + * otherwise you should respond with 406 "Not Acceptable". + * + * @param lang + */ + acceptsLanguage(lang: string): boolean; + + /** + * Parse Range header field, + * capping to the given `size`. + * + * Unspecified ranges such as "0-" require + * knowledge of your resource length. In + * the case of a byte range this is of course + * the total number of bytes. If the Range + * header field is not given `null` is returned, + * `-1` when unsatisfiable, `-2` when syntactically invalid. + * + * NOTE: remember that ranges are inclusive, so + * for example "Range: users=0-3" should respond + * with 4 users when available, not 3. + * + * @param size + */ + range(size: number): any[]; + + /** + * Return an array of Accepted media types + * ordered from highest quality to lowest. + */ + accepted: MediaType[]; + + /** + * Return an array of Accepted languages + * ordered from highest quality to lowest. + * + * Examples: + * + * Accept-Language: en;q=.5, en-us + * ['en-us', 'en'] + */ + acceptedLanguages: any[]; + + /** + * Return an array of Accepted charsets + * ordered from highest quality to lowest. + * + * Examples: + * + * Accept-Charset: iso-8859-5;q=.2, unicode-1-1;q=0.8 + * ['unicode-1-1', 'iso-8859-5'] + */ + acceptedCharsets: any[]; + + /** + * Return the value of param `name` when present or `defaultValue`. + * + * - Checks route placeholders, ex: _/user/:id_ + * - Checks body params, ex: id=12, {"id":12} + * - Checks query string params, ex: ?id=12 + * + * To utilize request bodies, `req.body` + * should be an object. This can be done by using + * the `connect.bodyParser()` middleware. + * + * @param name + * @param defaultValue + */ + param(name: string, defaultValue?: any): string; + + /** + * Check if the incoming request contains the "Content-Type" + * header field, and it contains the give mime `type`. + * + * Examples: + * + * // With Content-Type: text/html; charset=utf-8 + * req.is('html'); + * req.is('text/html'); + * req.is('text/*'); + * // => true + * + * // When Content-Type is application/json + * req.is('json'); + * req.is('application/json'); + * req.is('application/*'); + * // => true + * + * req.is('html'); + * // => false + * + * @param type + */ + is(type: string): boolean; + + /** + * Return the protocol string "http" or "https" + * when requested with TLS. When the "trust proxy" + * setting is enabled the "X-Forwarded-Proto" header + * field will be trusted. If you're running behind + * a reverse proxy that supplies https for you this + * may be enabled. + */ + protocol: string; + + /** + * Short-hand for: + * + * req.protocol == 'https' + */ + secure: boolean; + + /** + * Return the remote address, or when + * "trust proxy" is `true` return + * the upstream addr. + */ + ip: string; + + /** + * When "trust proxy" is `true`, parse + * the "X-Forwarded-For" ip address list. + * + * For example if the value were "client, proxy1, proxy2" + * you would receive the array `["client", "proxy1", "proxy2"]` + * where "proxy2" is the furthest down-stream. + */ + ips: string[]; + + /** + * Return basic auth credentials. + * + * Examples: + * + * // http://tobi:hello@example.com + * req.auth + * // => { username: 'tobi', password: 'hello' } + */ + auth: any; + + /** + * Return subdomains as an array. + * + * Subdomains are the dot-separated parts of the host before the main domain of + * the app. By default, the domain of the app is assumed to be the last two + * parts of the host. This can be changed by setting "subdomain offset". + * + * For example, if the domain is "tobi.ferrets.example.com": + * If "subdomain offset" is not set, req.subdomains is `["ferrets", "tobi"]`. + * If "subdomain offset" is 3, req.subdomains is `["tobi"]`. + */ + subdomains: string[]; + + /** + * Short-hand for `url.parse(req.url).pathname`. + */ + path: string; + + /** + * Parse the "Host" header field hostname. + */ + host: string; + + /** + * Check if the request is fresh, aka + * Last-Modified and/or the ETag + * still match. + */ + fresh: boolean; + + /** + * Check if the request is stale, aka + * "Last-Modified" and / or the "ETag" for the + * resource has changed. + */ + stale: boolean; + + /** + * Check if the request was an _XMLHttpRequest_. + */ + xhr: boolean; + + //body: { username: string; password: string; remember: boolean; title: string; }; + body: any; + + //cookies: { string; remember: boolean; }; + cookies: any; + + /** + * Used to generate an anti-CSRF token. + * Placed by the CSRF protection middleware. + */ + csrfToken(): string; + + method: string; + + params: any; + + user: any; + + authenticatedUser: any; + + files: any; + + /** + * Clear cookie `name`. + * + * @param name + * @param options + */ + clearCookie(name: string, options?: any): Response; + + query: any; + + route: any; + + signedCookies: any; + + originalUrl: string; + + url: string; + } + + interface MediaType { + value: string; + quality: number; + type: string; + subtype: string; + } + + interface Send { + (status: number, body?: any): Response; + (body: any): Response; + } + + interface Response extends http.ServerResponse { + /** + * Set status `code`. + * + * @param code + */ + status(code: number): Response; + + /** + * Set Link header field with the given `links`. + * + * Examples: + * + * res.links({ + * next: 'http://api.example.com/users?page=2', + * last: 'http://api.example.com/users?page=5' + * }); + * + * @param links + */ + links(links: any): Response; + + /** + * Send a response. + * + * Examples: + * + * res.send(new Buffer('wahoo')); + * res.send({ some: 'json' }); + * res.send('

some html

'); + * res.send(404, 'Sorry, cant find that'); + * res.send(404); + */ + send: Send; + + /** + * Send JSON response. + * + * Examples: + * + * res.json(null); + * res.json({ user: 'tj' }); + * res.json(500, 'oh noes!'); + * res.json(404, 'I dont have that'); + */ + json: Send; + + /** + * Send JSON response with JSONP callback support. + * + * Examples: + * + * res.jsonp(null); + * res.jsonp({ user: 'tj' }); + * res.jsonp(500, 'oh noes!'); + * res.jsonp(404, 'I dont have that'); + */ + jsonp: Send; + + /** + * Transfer the file at the given `path`. + * + * Automatically sets the _Content-Type_ response header field. + * The callback `fn(err)` is invoked when the transfer is complete + * or when an error occurs. Be sure to check `res.sentHeader` + * if you wish to attempt responding, as the header and some data + * may have already been transferred. + * + * Options: + * + * - `maxAge` defaulting to 0 + * - `root` root directory for relative filenames + * + * Examples: + * + * The following example illustrates how `res.sendfile()` may + * be used as an alternative for the `static()` middleware for + * dynamic situations. The code backing `res.sendfile()` is actually + * the same code, so HTTP cache support etc is identical. + * + * app.get('/user/:uid/photos/:file', function(req, res){ + * var uid = req.params.uid + * , file = req.params.file; + * + * req.user.mayViewFilesFrom(uid, function(yes){ + * if (yes) { + * res.sendfile('/uploads/' + uid + '/' + file); + * } else { + * res.send(403, 'Sorry! you cant see that.'); + * } + * }); + * }); + */ + sendfile(path: string): void; + + sendfile(path: string, options: any): void; + + sendfile(path: string, fn: Errback): void; + + sendfile(path: string, options: any, fn: Errback): void; + + /** + * Transfer the file at the given `path` as an attachment. + * + * Optionally providing an alternate attachment `filename`, + * and optional callback `fn(err)`. The callback is invoked + * when the data transfer is complete, or when an error has + * ocurred. Be sure to check `res.headerSent` if you plan to respond. + * + * This method uses `res.sendfile()`. + */ + download(path: string): void; + + download(path: string, filename: string): void; + + download(path: string, fn: Errback): void; + + download(path: string, filename: string, fn: Errback): void; + + /** + * Set _Content-Type_ response header with `type` through `mime.lookup()` + * when it does not contain "/", or set the Content-Type to `type` otherwise. + * + * Examples: + * + * res.type('.html'); + * res.type('html'); + * res.type('json'); + * res.type('application/json'); + * res.type('png'); + * + * @param type + */ + contentType(type: string): Response; + + /** + * Set _Content-Type_ response header with `type` through `mime.lookup()` + * when it does not contain "/", or set the Content-Type to `type` otherwise. + * + * Examples: + * + * res.type('.html'); + * res.type('html'); + * res.type('json'); + * res.type('application/json'); + * res.type('png'); + * + * @param type + */ + type(type: string): Response; + + /** + * Respond to the Acceptable formats using an `obj` + * of mime-type callbacks. + * + * This method uses `req.accepted`, an array of + * acceptable types ordered by their quality values. + * When "Accept" is not present the _first_ callback + * is invoked, otherwise the first match is used. When + * no match is performed the server responds with + * 406 "Not Acceptable". + * + * Content-Type is set for you, however if you choose + * you may alter this within the callback using `res.type()` + * or `res.set('Content-Type', ...)`. + * + * res.format({ + * 'text/plain': function(){ + * res.send('hey'); + * }, + * + * 'text/html': function(){ + * res.send('

hey

'); + * }, + * + * 'appliation/json': function(){ + * res.send({ message: 'hey' }); + * } + * }); + * + * In addition to canonicalized MIME types you may + * also use extnames mapped to these types: + * + * res.format({ + * text: function(){ + * res.send('hey'); + * }, + * + * html: function(){ + * res.send('

hey

'); + * }, + * + * json: function(){ + * res.send({ message: 'hey' }); + * } + * }); + * + * By default Express passes an `Error` + * with a `.status` of 406 to `next(err)` + * if a match is not made. If you provide + * a `.default` callback it will be invoked + * instead. + * + * @param obj + */ + format(obj: any): Response; + + /** + * Set _Content-Disposition_ header to _attachment_ with optional `filename`. + * + * @param filename + */ + attachment(filename?: string): Response; + + /** + * Set header `field` to `val`, or pass + * an object of header fields. + * + * Examples: + * + * res.set('Foo', ['bar', 'baz']); + * res.set('Accept', 'application/json'); + * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); + * + * Aliased as `res.header()`. + */ + set(field: any): Response; + + set(field: string, value?: string): Response; + + header(field: any): Response; + + header(field: string, value?: string): Response; + + /** + * Get value for header `field`. + * + * @param field + */ + get(field: string): string; + + /** + * Clear cookie `name`. + * + * @param name + * @param options + */ + clearCookie(name: string, options?: any): Response; + + /** + * Set cookie `name` to `val`, with the given `options`. + * + * Options: + * + * - `maxAge` max-age in milliseconds, converted to `expires` + * - `signed` sign the cookie + * - `path` defaults to "/" + * + * Examples: + * + * // "Remember Me" for 15 minutes + * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); + * + * // save as above + * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) + */ + cookie(name: string, val: string, options: CookieOptions): Response; + + cookie(name: string, val: any, options: CookieOptions): Response; + + cookie(name: string, val: any): Response; + + /** + * Set the location header to `url`. + * + * The given `url` can also be the name of a mapped url, for + * example by default express supports "back" which redirects + * to the _Referrer_ or _Referer_ headers or "/". + * + * Examples: + * + * res.location('/foo/bar').; + * res.location('http://example.com'); + * res.location('../login'); // /blog/post/1 -> /blog/login + * + * Mounting: + * + * When an application is mounted and `res.location()` + * is given a path that does _not_ lead with "/" it becomes + * relative to the mount-point. For example if the application + * is mounted at "/blog", the following would become "/blog/login". + * + * res.location('login'); + * + * While the leading slash would result in a location of "/login": + * + * res.location('/login'); + * + * @param url + */ + location(url: string): Response; + + /** + * Redirect to the given `url` with optional response `status` + * defaulting to 302. + * + * The resulting `url` is determined by `res.location()`, so + * it will play nicely with mounted apps, relative paths, + * `"back"` etc. + * + * Examples: + * + * res.redirect('/foo/bar'); + * res.redirect('http://example.com'); + * res.redirect(301, 'http://example.com'); + * res.redirect('http://example.com', 301); + * res.redirect('../login'); // /blog/post/1 -> /blog/login + */ + redirect(url: string): void; + + redirect(status: number, url: string): void; + + redirect(url: string, status: number): void; + + /** + * Render `view` with the given `options` and optional callback `fn`. + * When a callback function is given a response will _not_ be made + * automatically, otherwise a response of _200_ and _text/html_ is given. + * + * Options: + * + * - `cache` boolean hinting to the engine it should cache + * - `filename` filename of the view being rendered + */ + + render(view: string, options?: Object, callback?: (err: Error, html: string) => void): void; + + render(view: string, callback?: (err: Error, html: string) => void): void; + + locals: any; + + charset: string; + } + + interface RequestFunction { + (req: Request, res: Response, next: Function): any; + } + + interface Application extends IRouter { + /** + * Initialize the server. + * + * - setup default configuration + * - setup default middleware + * - setup route reflection methods + */ + init(): void; + + /** + * Initialize application configuration. + */ + defaultConfiguration(): void; + + /** + * Proxy `connect#use()` to apply settings to + * mounted applications. + **/ + use(route: string, callback?: Function): Application; + + use(route: string, server: Application): Application; + + use(callback: Function): Application; + + use(server: Application): Application; + + /** + * Register the given template engine callback `fn` + * as `ext`. + * + * By default will `require()` the engine based on the + * file extension. For example if you try to render + * a "foo.jade" file Express will invoke the following internally: + * + * app.engine('jade', require('jade').__express); + * + * For engines that do not provide `.__express` out of the box, + * or if you wish to "map" a different extension to the template engine + * you may use this method. For example mapping the EJS template engine to + * ".html" files: + * + * app.engine('html', require('ejs').renderFile); + * + * In this case EJS provides a `.renderFile()` method with + * the same signature that Express expects: `(path, options, callback)`, + * though note that it aliases this method as `ejs.__express` internally + * so if you're using ".ejs" extensions you dont need to do anything. + * + * Some template engines do not follow this convention, the + * [Consolidate.js](https://github.com/visionmedia/consolidate.js) + * library was created to map all of node's popular template + * engines to follow this convention, thus allowing them to + * work seamlessly within Express. + */ + engine(ext: string, fn: Function): Application; + + param(name: string, fn: Function): Application; + + param(name: string[], fn: Function): Application; + + /** + * Assign `setting` to `val`, or return `setting`'s value. + * + * app.set('foo', 'bar'); + * app.get('foo'); + * // => "bar" + * + * Mounted servers inherit their parent server's settings. + * + * @param setting + * @param val + */ + set(setting: string, val: string): Application; + + /** + * Return the app's absolute pathname + * based on the parent(s) that have + * mounted it. + * + * For example if the application was + * mounted as "/admin", which itself + * was mounted as "/blog" then the + * return value would be "/blog/admin". + */ + path(): string; + + /** + * Check if `setting` is enabled (truthy). + * + * app.enabled('foo') + * // => false + * + * app.enable('foo') + * app.enabled('foo') + * // => true + */ + enabled(setting: string): boolean; + + /** + * Check if `setting` is disabled. + * + * app.disabled('foo') + * // => true + * + * app.enable('foo') + * app.disabled('foo') + * // => false + * + * @param setting + */ + disabled(setting: string): boolean; + + /** + * Enable `setting`. + * + * @param setting + */ + enable(setting: string): Application; + + /** + * Disable `setting`. + * + * @param setting + */ + disable(setting: string): Application; + + /** + * Configure callback for zero or more envs, + * when no `env` is specified that callback will + * be invoked for all environments. Any combination + * can be used multiple times, in any order desired. + * + * Examples: + * + * app.configure(function(){ + * // executed for all envs + * }); + * + * app.configure('stage', function(){ + * // executed staging env + * }); + * + * app.configure('stage', 'production', function(){ + * // executed for stage and production + * }); + * + * Note: + * + * These callbacks are invoked immediately, and + * are effectively sugar for the following: + * + * var env = process.env.NODE_ENV || 'development'; + * + * switch (env) { + * case 'development': + * ... + * break; + * case 'stage': + * ... + * break; + * case 'production': + * ... + * break; + * } + * + * @param env + * @param fn + */ + configure(env: string, fn: Function): Application; + + configure(env0: string, env1: string, fn: Function): Application; + + configure(env0: string, env1: string, env2: string, fn: Function): Application; + + configure(env0: string, env1: string, env2: string, env3: string, fn: Function): Application; + + configure(env0: string, env1: string, env2: string, env3: string, env4: string, fn: Function): Application; + + configure(fn: Function): Application; + + + /** + * Render the given view `name` name with `options` + * and a callback accepting an error and the + * rendered template string. + * + * Example: + * + * app.render('email', { name: 'Tobi' }, function(err, html){ + * // ... + * }) + * + * @param name + * @param options or fn + * @param fn + */ + render(name: string, options?: Object, callback?: (err: Error, html: string) => void): void; + + render(name: string, callback: (err: Error, html: string) => void): void; + + + /** + * Listen for connections. + * + * A node `http.Server` is returned, with this + * application (which is a `Function`) as its + * callback. If you wish to create both an HTTP + * and HTTPS server you may do so with the "http" + * and "https" modules as shown here: + * + * var http = require('http') + * , https = require('https') + * , express = require('express') + * , app = express(); + * + * http.createServer(app).listen(80); + * https.createServer({ ... }, app).listen(443); + */ + listen(port: number, hostname: string, backlog: number, callback?: Function): void; + + listen(port: number, hostname: string, callback?: Function): void; + + listen(port: number, callback?: Function): void; + + listen(path: string, callback?: Function): void; + + listen(handle: any, listeningListener?: Function): void; + + route: IRoute; + + router: string; + + settings: any; + + resource: any; + + map: any; + + locals: any; + + /** + * The app.routes object houses all of the routes defined mapped by the + * associated HTTP verb. This object may be used for introspection + * capabilities, for example Express uses this internally not only for + * routing but to provide default OPTIONS behaviour unless app.options() + * is used. Your application or framework may also remove routes by + * simply by removing them from this object. + */ + routes: any; + } + + interface Express extends Application { + /** + * Framework version. + */ + version: string; + + /** + * Expose mime. + */ + mime: string; + + (): Application; + + /** + * Create a Node.js Express application. + */ + createApplication(): Application; + + createServer(): Application; + + application: any; + + request: Request; + + response: Response; + } + + /** + * Body parser: + * + * Parse request bodies, supports _application/json_, + * _application/x-www-form-urlencoded_, and _multipart/form-data_. + * + * This is equivalent to: + * + * app.use(connect.json()); + * app.use(connect.urlencoded()); + * app.use(connect.multipart()); + * + * Examples: + * + * connect() + * .use(connect.bodyParser()) + * .use(function(req, res) { + * res.end('viewing user ' + req.body.user.name); + * }); + * + * $ curl -d 'user[name]=tj' http://local/ + * $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://local/ + * + * View [json](json.html), [urlencoded](urlencoded.html), and [multipart](multipart.html) for more info. + * + * @param options + */ + function bodyParser(options?: any): Handler; + + /** + * Error handler: + * + * Development error handler, providing stack traces + * and error message responses for requests accepting text, html, + * or json. + * + * Text: + * + * By default, and when _text/plain_ is accepted a simple stack trace + * or error message will be returned. + * + * JSON: + * + * When _application/json_ is accepted, connect will respond with + * an object in the form of `{ "error": error }`. + * + * HTML: + * + * When accepted connect will output a nice html stack trace. + */ + function errorHandler(opts?: any): Handler; + + /** + * Method Override: + * + * Provides faux HTTP method support. + * + * Pass an optional `key` to use when checking for + * a method override, othewise defaults to _\_method_. + * The original method is available via `req.originalMethod`. + * + * @param key + */ + function methodOverride(key?: string): Handler; + + /** + * Cookie parser: + * + * Parse _Cookie_ header and populate `req.cookies` + * with an object keyed by the cookie names. Optionally + * you may enabled signed cookie support by passing + * a `secret` string, which assigns `req.secret` so + * it may be used by other middleware. + * + * Examples: + * + * connect() + * .use(connect.cookieParser('optional secret string')) + * .use(function(req, res, next){ + * res.end(JSON.stringify(req.cookies)); + * }) + * + * @param secret + */ + function cookieParser(secret?: string): Handler; + + /** + * Session: + * + * Setup session store with the given `options`. + * + * Session data is _not_ saved in the cookie itself, however + * cookies are used, so we must use the [cookieParser()](cookieParser.html) + * middleware _before_ `session()`. + * + * Examples: + * + * connect() + * .use(connect.cookieParser()) + * .use(connect.session({ secret: 'keyboard cat', key: 'sid', cookie: { secure: true }})) + * + * Options: + * + * - `key` cookie name defaulting to `connect.sid` + * - `store` session store instance + * - `secret` session cookie is signed with this secret to prevent tampering + * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }` + * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto") + * + * Cookie option: + * + * By default `cookie.maxAge` is `null`, meaning no "expires" parameter is set + * so the cookie becomes a browser-session cookie. When the user closes the + * browser the cookie (and session) will be removed. + * + * ## req.session + * + * To store or access session data, simply use the request property `req.session`, + * which is (generally) serialized as JSON by the store, so nested objects + * are typically fine. For example below is a user-specific view counter: + * + * connect() + * .use(connect.favicon()) + * .use(connect.cookieParser()) + * .use(connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})) + * .use(function(req, res, next){ + * var sess = req.session; + * if (sess.views) { + * res.setHeader('Content-Type', 'text/html'); + * res.write('

views: ' + sess.views + '

'); + * res.write('

expires in: ' + (sess.cookie.maxAge / 1000) + 's

'); + * res.end(); + * sess.views++; + * } else { + * sess.views = 1; + * res.end('welcome to the session demo. refresh!'); + * } + * } + * )).listen(3000); + * + * ## Session#regenerate() + * + * To regenerate the session simply invoke the method, once complete + * a new SID and `Session` instance will be initialized at `req.session`. + * + * req.session.regenerate(function(err){ + * // will have a new session here + * }); + * + * ## Session#destroy() + * + * Destroys the session, removing `req.session`, will be re-generated next request. + * + * req.session.destroy(function(err){ + * // cannot access session here + * }); + * + * ## Session#reload() + * + * Reloads the session data. + * + * req.session.reload(function(err){ + * // session updated + * }); + * + * ## Session#save() + * + * Save the session. + * + * req.session.save(function(err){ + * // session saved + * }); + * + * ## Session#touch() + * + * Updates the `.maxAge` property. Typically this is + * not necessary to call, as the session middleware does this for you. + * + * ## Session#cookie + * + * Each session has a unique cookie object accompany it. This allows + * you to alter the session cookie per visitor. For example we can + * set `req.session.cookie.expires` to `false` to enable the cookie + * to remain for only the duration of the user-agent. + * + * ## Session#maxAge + * + * Alternatively `req.session.cookie.maxAge` will return the time + * remaining in milliseconds, which we may also re-assign a new value + * to adjust the `.expires` property appropriately. The following + * are essentially equivalent + * + * var hour = 3600000; + * req.session.cookie.expires = new Date(Date.now() + hour); + * req.session.cookie.maxAge = hour; + * + * For example when `maxAge` is set to `60000` (one minute), and 30 seconds + * has elapsed it will return `30000` until the current request has completed, + * at which time `req.session.touch()` is called to reset `req.session.maxAge` + * to its original value. + * + * req.session.cookie.maxAge; + * // => 30000 + * + * Session Store Implementation: + * + * Every session store _must_ implement the following methods + * + * - `.get(sid, callback)` + * - `.set(sid, session, callback)` + * - `.destroy(sid, callback)` + * + * Recommended methods include, but are not limited to: + * + * - `.length(callback)` + * - `.clear(callback)` + * + * For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo. + * + * @param options + */ + function session(options?: any): Handler; + + /** + * Hash the given `sess` object omitting changes + * to `.cookie`. + * + * @param sess + */ + function hash(sess: string): string; + + /** + * Static: + * + * Static file server with the given `root` path. + * + * Examples: + * + * var oneDay = 86400000; + * + * connect() + * .use(connect.static(__dirname + '/public')) + * + * connect() + * .use(connect.static(__dirname + '/public', { maxAge: oneDay })) + * + * Options: + * + * - `maxAge` Browser cache maxAge in milliseconds. defaults to 0 + * - `hidden` Allow transfer of hidden files. defaults to false + * - `redirect` Redirect to trailing "/" when the pathname is a dir. defaults to true + * + * @param root + * @param options + */ + function static(root: string, options?: any): Handler; + + /** + * Basic Auth: + * + * Enfore basic authentication by providing a `callback(user, pass)`, + * which must return `true` in order to gain access. Alternatively an async + * method is provided as well, invoking `callback(user, pass, callback)`. Populates + * `req.user`. The final alternative is simply passing username / password + * strings. + * + * Simple username and password + * + * connect(connect.basicAuth('username', 'password')); + * + * Callback verification + * + * connect() + * .use(connect.basicAuth(function(user, pass){ + * return 'tj' == user & 'wahoo' == pass; + * })) + * + * Async callback verification, accepting `fn(err, user)`. + * + * connect() + * .use(connect.basicAuth(function(user, pass, fn){ + * User.authenticate({ user: user, pass: pass }, fn); + * })) + * + * @param callback or username + * @param realm + */ + export function basicAuth(callback: (user: string, pass: string, fn: Function) => void, realm?: string): Handler; + + export function basicAuth(callback: (user: string, pass: string) => boolean, realm?: string): Handler; + + export function basicAuth(user: string, pass: string, realm?: string): Handler; + + /** + * Compress: + * + * Compress response data with gzip/deflate. + * + * Filter: + * + * A `filter` callback function may be passed to + * replace the default logic of: + * + * exports.filter = function(req, res){ + * return /json|text|javascript/.test(res.getHeader('Content-Type')); + * }; + * + * Options: + * + * All remaining options are passed to the gzip/deflate + * creation functions. Consult node's docs for additional details. + * + * - `chunkSize` (default: 16*1024) + * - `windowBits` + * - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression + * - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more + * - `strategy`: compression strategy + * + * @param options + */ + function compress(options?: any): Handler; + + /** + * Cookie Session: + * + * Cookie session middleware. + * + * var app = connect(); + * app.use(connect.cookieParser()); + * app.use(connect.cookieSession({ secret: 'tobo!', cookie: { maxAge: 60 * 60 * 1000 }})); + * + * Options: + * + * - `key` cookie name defaulting to `connect.sess` + * - `secret` prevents cookie tampering + * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }` + * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto") + * + * Clearing sessions: + * + * To clear the session simply set its value to `null`, + * `cookieSession()` will then respond with a 1970 Set-Cookie. + * + * req.session = null; + * + * @param options + */ + function cookieSession(options?: any): Handler; + + /** + * Anti CSRF: + * + * CSRF protection middleware. + * + * This middleware adds a `req.csrfToken()` function to make a token + * which should be added to requests which mutate + * state, within a hidden form field, query-string etc. This + * token is validated against the visitor's session. + * + * The default `value` function checks `req.body` generated + * by the `bodyParser()` middleware, `req.query` generated + * by `query()`, and the "X-CSRF-Token" header field. + * + * This middleware requires session support, thus should be added + * somewhere _below_ `session()` and `cookieParser()`. + * + * Options: + * + * - `value` a function accepting the request, returning the token + * + * @param options + */ + export function csrf(options?: { value?: Function }): Handler; + + /** + * Directory: + * + * Serve directory listings with the given `root` path. + * + * Options: + * + * - `hidden` display hidden (dot) files. Defaults to false. + * - `icons` display icons. Defaults to false. + * - `filter` Apply this filter function to files. Defaults to false. + * + * @param root + * @param options + */ + function directory(root: string, options?: any): Handler; + + /** + * Favicon: + * + * By default serves the connect favicon, or the favicon + * located by the given `path`. + * + * Options: + * + * - `maxAge` cache-control max-age directive, defaulting to 1 day + * + * Examples: + * + * Serve default favicon: + * + * connect() + * .use(connect.favicon()) + * + * Serve favicon before logging for brevity: + * + * connect() + * .use(connect.favicon()) + * .use(connect.logger('dev')) + * + * Serve custom favicon: + * + * connect() + * .use(connect.favicon('public/favicon.ico)) + * + * @param path + * @param options + */ + export function favicon(path?: string, options?: any): Handler; + + /** + * JSON: + * + * Parse JSON request bodies, providing the + * parsed object as `req.body`. + * + * Options: + * + * - `strict` when `false` anything `JSON.parse()` accepts will be parsed + * - `reviver` used as the second "reviver" argument for JSON.parse + * - `limit` byte limit disabled by default + * + * @param options + */ + function json(options?: any): Handler; + + /** + * Limit: + * + * Limit request bodies to the given size in `bytes`. + * + * A string representation of the bytesize may also be passed, + * for example "5mb", "200kb", "1gb", etc. + * + * connect() + * .use(connect.limit('5.5mb')) + * .use(handleImageUpload) + */ + function limit(bytes: number): Handler; + + function limit(bytes: string): Handler; + + /** + * Logger: + * + * Log requests with the given `options` or a `format` string. + * + * Options: + * + * - `format` Format string, see below for tokens + * - `stream` Output stream, defaults to _stdout_ + * - `buffer` Buffer duration, defaults to 1000ms when _true_ + * - `immediate` Write log line on request instead of response (for response times) + * + * Tokens: + * + * - `:req[header]` ex: `:req[Accept]` + * - `:res[header]` ex: `:res[Content-Length]` + * - `:http-version` + * - `:response-time` + * - `:remote-addr` + * - `:date` + * - `:method` + * - `:url` + * - `:referrer` + * - `:user-agent` + * - `:status` + * + * Formats: + * + * Pre-defined formats that ship with connect: + * + * - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' + * - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms' + * - `tiny` ':method :url :status :res[content-length] - :response-time ms' + * - `dev` concise output colored by response status for development use + * + * Examples: + * + * connect.logger() // default + * connect.logger('short') + * connect.logger('tiny') + * connect.logger({ immediate: true, format: 'dev' }) + * connect.logger(':method :url - :referrer') + * connect.logger(':req[content-type] -> :res[content-type]') + * connect.logger(function(tokens, req, res){ return 'some format string' }) + * + * Defining Tokens: + * + * To define a token, simply invoke `connect.logger.token()` with the + * name and a callback function. The value returned is then available + * as ":type" in this case. + * + * connect.logger.token('type', function(req, res){ return req.headers['content-type']; }) + * + * Defining Formats: + * + * All default formats are defined this way, however it's public API as well: + * + * connect.logger.format('name', 'string or function') + */ + function logger(options: string): Handler; + + function logger(options: Function): Handler; + + function logger(options?: any): Handler; + + /** + * Compile `fmt` into a function. + * + * @param fmt + */ + function compile(fmt: string): Handler; + + /** + * Define a token function with the given `name`, + * and callback `fn(req, res)`. + * + * @param name + * @param fn + */ + function token(name: string, fn: Function): any; + + /** + * Define a `fmt` with the given `name`. + */ + function format(name: string, str: string): any; + + function format(name: string, str: Function): any; + + /** + * Query: + * + * Automatically parse the query-string when available, + * populating the `req.query` object. + * + * Examples: + * + * connect() + * .use(connect.query()) + * .use(function(req, res){ + * res.end(JSON.stringify(req.query)); + * }); + * + * The `options` passed are provided to qs.parse function. + */ + function query(options: any): Handler; + + /** + * Reponse time: + * + * Adds the `X-Response-Time` header displaying the response + * duration in milliseconds. + */ + function responseTime(): Handler; + + /** + * Static cache: + * + * Enables a memory cache layer on top of + * the `static()` middleware, serving popular + * static files. + * + * By default a maximum of 128 objects are + * held in cache, with a max of 256k each, + * totalling ~32mb. + * + * A Least-Recently-Used (LRU) cache algo + * is implemented through the `Cache` object, + * simply rotating cache objects as they are + * hit. This means that increasingly popular + * objects maintain their positions while + * others get shoved out of the stack and + * garbage collected. + * + * Benchmarks: + * + * static(): 2700 rps + * node-static: 5300 rps + * static() + staticCache(): 7500 rps + * + * Options: + * + * - `maxObjects` max cache objects [128] + * - `maxLength` max cache object length 256kb + */ + function staticCache(options: any): Handler; + + /** + * Timeout: + * + * Times out the request in `ms`, defaulting to `5000`. The + * method `req.clearTimeout()` is added to revert this behaviour + * programmatically within your application's middleware, routes, etc. + * + * The timeout error is passed to `next()` so that you may customize + * the response behaviour. This error has the `.timeout` property as + * well as `.status == 408`. + */ + function timeout(ms: number): Handler; + + /** + * Vhost: + * + * Setup vhost for the given `hostname` and `server`. + * + * connect() + * .use(connect.vhost('foo.com', fooApp)) + * .use(connect.vhost('bar.com', barApp)) + * .use(connect.vhost('*.com', mainApp)) + * + * The `server` may be a Connect server or + * a regular Node `http.Server`. + * + * @param hostname + * @param server + */ + function vhost(hostname: string, server: any): Handler; + + function urlencoded(): any; + + function multipart(): any; + + } + + export = e; +} + diff --git a/Node/src/Scripts/typings/form-data/form-data.d.ts b/Node/src/Scripts/typings/form-data/form-data.d.ts new file mode 100644 index 000000000..2af4565cc --- /dev/null +++ b/Node/src/Scripts/typings/form-data/form-data.d.ts @@ -0,0 +1,16 @@ +// Type definitions for form-data +// Project: https://github.com/felixge/node-form-data +// Definitions by: Carlos Ballesteros Velasco +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +// Imported from: https://github.com/soywiz/typescript-node-definitions/form-data.d.ts + +declare module "form-data" { + export class FormData { + append(key: string, value: any, options?: any): FormData; + getHeaders(): Object; + // TODO expand pipe + pipe(to: any): any; + submit(params: string|Object, callback: (error: any, response: any) => void): any; + } +} diff --git a/Node/src/Scripts/typings/node-uuid/node-uuid-base.d.ts b/Node/src/Scripts/typings/node-uuid/node-uuid-base.d.ts new file mode 100644 index 000000000..0788f27b6 --- /dev/null +++ b/Node/src/Scripts/typings/node-uuid/node-uuid-base.d.ts @@ -0,0 +1,46 @@ +// Type definitions for node-uuid.js +// Project: https://github.com/broofa/node-uuid +// Definitions by: Jeff May +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/** Common definitions for all environments */ +declare namespace __NodeUUID { + interface UUIDOptions { + + /** + * Node id as Array of 6 bytes (per 4.1.6). + * Default: Randomly generated ID. See note 1. + */ + node?: any[]; + + /** + * (Number between 0 - 0x3fff) RFC clock sequence. + * Default: An internally maintained clockseq is used. + */ + clockseq?: number; + + /** + * (Number | Date) Time in milliseconds since unix Epoch. + * Default: The current time is used. + */ + msecs?: number|Date; + + /** + * (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if msecs is unspecified. + * Default: internal uuid counter is used, as per 4.2.1.2. + */ + nsecs?: number; + } + + interface UUID { + v1(options?: UUIDOptions): string; + v1(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + + v4(options?: UUIDOptions): string; + v4(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + + parse(id: string, buffer?: number[], offset?: number): number[]; + + unparse(buffer: number[], offset?: number): string; + } +} diff --git a/Node/src/Scripts/typings/node-uuid/node-uuid-cjs.d.ts b/Node/src/Scripts/typings/node-uuid/node-uuid-cjs.d.ts new file mode 100644 index 000000000..1c765abd7 --- /dev/null +++ b/Node/src/Scripts/typings/node-uuid/node-uuid-cjs.d.ts @@ -0,0 +1,15 @@ +// Type definitions for node-uuid.js +// Project: https://github.com/broofa/node-uuid +// Definitions by: Jeff May +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// + +/** + * Expose as CommonJS module + * For use in node environment or browser environment (using webpack or other module loaders) + */ +declare module "node-uuid" { + var uuid: __NodeUUID.UUID; + export = uuid; +} \ No newline at end of file diff --git a/Node/src/Scripts/typings/node-uuid/node-uuid.d.ts b/Node/src/Scripts/typings/node-uuid/node-uuid.d.ts new file mode 100644 index 000000000..3222ad368 --- /dev/null +++ b/Node/src/Scripts/typings/node-uuid/node-uuid.d.ts @@ -0,0 +1,36 @@ +// Type definitions for node-uuid.js +// Project: https://github.com/broofa/node-uuid +// Definitions by: Jeff May +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// +/// +/// + +/** + * Definitions for use in node environment + * + * !! For browser enviroments, use node-uuid-global or node-uuid-cjs + */ +declare module __NodeUUID { + /** + * Overloads for node environment + * We need to duplicate some declarations because + * interface merging doesn't work with overloads + */ + interface UUID { + v1(options?: UUIDOptions): string; + v1(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + v1(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; + + v4(options?: UUIDOptions): string; + v4(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + v4(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; + + parse(id: string, buffer?: number[], offset?: number): number[]; + parse(id: string, buffer?: Buffer, offset?: number): Buffer; + + unparse(buffer: number[], offset?: number): string; + unparse(buffer: Buffer, offset?: number): string; + } +} diff --git a/Node/src/Scripts/typings/node/node.d.ts b/Node/src/Scripts/typings/node/node.d.ts new file mode 100644 index 000000000..1cab19a6d --- /dev/null +++ b/Node/src/Scripts/typings/node/node.d.ts @@ -0,0 +1,2093 @@ +// Type definitions for Node.js v0.12.0 +// Project: http://nodejs.org/ +// Definitions by: Microsoft TypeScript , DefinitelyTyped +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/************************************************ +* * +* Node.js v0.12.0 API * +* * +************************************************/ + +interface Error { + stack?: string; +} + + +// compat for TypeScript 1.5.3 +// if you use with --target es3 or --target es5 and use below definitions, +// use the lib.es6.d.ts that is bundled with TypeScript 1.5.3. +interface MapConstructor {} +interface WeakMapConstructor {} +interface SetConstructor {} +interface WeakSetConstructor {} + +/************************************************ +* * +* GLOBAL * +* * +************************************************/ +declare var process: NodeJS.Process; +declare var global: NodeJS.Global; + +declare var __filename: string; +declare var __dirname: string; + +declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearTimeout(timeoutId: NodeJS.Timer): void; +declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearInterval(intervalId: NodeJS.Timer): void; +declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; +declare function clearImmediate(immediateId: any): void; + +interface NodeRequireFunction { + (id: string): any; +} + +interface NodeRequire extends NodeRequireFunction { + resolve(id:string): string; + cache: any; + extensions: any; + main: any; +} + +declare var require: NodeRequire; + +interface NodeModule { + exports: any; + require: NodeRequireFunction; + id: string; + filename: string; + loaded: boolean; + parent: any; + children: any[]; +} + +declare var module: NodeModule; + +// Same as module.exports +declare var exports: any; +declare var SlowBuffer: { + new (str: string, encoding?: string): Buffer; + new (size: number): Buffer; + new (size: Uint8Array): Buffer; + new (array: any[]): Buffer; + prototype: Buffer; + isBuffer(obj: any): boolean; + byteLength(string: string, encoding?: string): number; + concat(list: Buffer[], totalLength?: number): Buffer; +}; + + +// Buffer class +interface Buffer extends NodeBuffer {} + +/** + * Raw data is stored in instances of the Buffer class. + * A Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer cannot be resized. + * Valid string encodings: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' + */ +declare var Buffer: { + /** + * Allocates a new buffer containing the given {str}. + * + * @param str String to store in buffer. + * @param encoding encoding to use, optional. Default is 'utf8' + */ + new (str: string, encoding?: string): Buffer; + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + */ + new (size: number): Buffer; + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + new (array: Uint8Array): Buffer; + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + new (array: any[]): Buffer; + prototype: Buffer; + /** + * Returns true if {obj} is a Buffer + * + * @param obj object to test. + */ + isBuffer(obj: any): obj is Buffer; + /** + * Returns true if {encoding} is a valid encoding argument. + * Valid string encodings in Node 0.12: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' + * + * @param encoding string to test. + */ + isEncoding(encoding: string): boolean; + /** + * Gives the actual byte length of a string. encoding defaults to 'utf8'. + * This is not the same as String.prototype.length since that returns the number of characters in a string. + * + * @param string string to test. + * @param encoding encoding used to evaluate (defaults to 'utf8') + */ + byteLength(string: string, encoding?: string): number; + /** + * Returns a buffer which is the result of concatenating all the buffers in the list together. + * + * If the list has no items, or if the totalLength is 0, then it returns a zero-length buffer. + * If the list has exactly one item, then the first item of the list is returned. + * If the list has more than one item, then a new Buffer is created. + * + * @param list An array of Buffer objects to concatenate + * @param totalLength Total length of the buffers when concatenated. + * If totalLength is not provided, it is read from the buffers in the list. However, this adds an additional loop to the function, so it is faster to provide the length explicitly. + */ + concat(list: Buffer[], totalLength?: number): Buffer; + /** + * The same as buf1.compare(buf2). + */ + compare(buf1: Buffer, buf2: Buffer): number; +}; + +/************************************************ +* * +* GLOBAL INTERFACES * +* * +************************************************/ +declare module NodeJS { + export interface ErrnoException extends Error { + errno?: number; + code?: string; + path?: string; + syscall?: string; + stack?: string; + } + + export interface EventEmitter { + addListener(event: string, listener: Function): EventEmitter; + on(event: string, listener: Function): EventEmitter; + once(event: string, listener: Function): EventEmitter; + removeListener(event: string, listener: Function): EventEmitter; + removeAllListeners(event?: string): EventEmitter; + setMaxListeners(n: number): void; + listeners(event: string): Function[]; + emit(event: string, ...args: any[]): boolean; + } + + export interface ReadableStream extends EventEmitter { + readable: boolean; + read(size?: number): string|Buffer; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: string): void; + unshift(chunk: Buffer): void; + wrap(oldStream: ReadableStream): ReadableStream; + } + + export interface WritableStream extends EventEmitter { + writable: boolean; + write(buffer: Buffer|string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + } + + export interface ReadWriteStream extends ReadableStream, WritableStream {} + + export interface Process extends EventEmitter { + stdout: WritableStream; + stderr: WritableStream; + stdin: ReadableStream; + argv: string[]; + execPath: string; + abort(): void; + chdir(directory: string): void; + cwd(): string; + env: any; + exit(code?: number): void; + getgid(): number; + setgid(id: number): void; + setgid(id: string): void; + getuid(): number; + setuid(id: number): void; + setuid(id: string): void; + version: string; + versions: { + http_parser: string; + node: string; + v8: string; + ares: string; + uv: string; + zlib: string; + openssl: string; + }; + config: { + target_defaults: { + cflags: any[]; + default_configuration: string; + defines: string[]; + include_dirs: string[]; + libraries: string[]; + }; + variables: { + clang: number; + host_arch: string; + node_install_npm: boolean; + node_install_waf: boolean; + node_prefix: string; + node_shared_openssl: boolean; + node_shared_v8: boolean; + node_shared_zlib: boolean; + node_use_dtrace: boolean; + node_use_etw: boolean; + node_use_openssl: boolean; + target_arch: string; + v8_no_strict_aliasing: number; + v8_use_snapshot: boolean; + visibility: string; + }; + }; + kill(pid: number, signal?: string): void; + pid: number; + title: string; + arch: string; + platform: string; + memoryUsage(): { rss: number; heapTotal: number; heapUsed: number; }; + nextTick(callback: Function): void; + umask(mask?: number): number; + uptime(): number; + hrtime(time?:number[]): number[]; + + // Worker + send?(message: any, sendHandle?: any): void; + } + + export interface Global { + Array: typeof Array; + ArrayBuffer: typeof ArrayBuffer; + Boolean: typeof Boolean; + Buffer: typeof Buffer; + DataView: typeof DataView; + Date: typeof Date; + Error: typeof Error; + EvalError: typeof EvalError; + Float32Array: typeof Float32Array; + Float64Array: typeof Float64Array; + Function: typeof Function; + GLOBAL: Global; + Infinity: typeof Infinity; + Int16Array: typeof Int16Array; + Int32Array: typeof Int32Array; + Int8Array: typeof Int8Array; + Intl: typeof Intl; + JSON: typeof JSON; + Map: MapConstructor; + Math: typeof Math; + NaN: typeof NaN; + Number: typeof Number; + Object: typeof Object; + Promise: Function; + RangeError: typeof RangeError; + ReferenceError: typeof ReferenceError; + RegExp: typeof RegExp; + Set: SetConstructor; + String: typeof String; + Symbol: Function; + SyntaxError: typeof SyntaxError; + TypeError: typeof TypeError; + URIError: typeof URIError; + Uint16Array: typeof Uint16Array; + Uint32Array: typeof Uint32Array; + Uint8Array: typeof Uint8Array; + Uint8ClampedArray: Function; + WeakMap: WeakMapConstructor; + WeakSet: WeakSetConstructor; + clearImmediate: (immediateId: any) => void; + clearInterval: (intervalId: NodeJS.Timer) => void; + clearTimeout: (timeoutId: NodeJS.Timer) => void; + console: typeof console; + decodeURI: typeof decodeURI; + decodeURIComponent: typeof decodeURIComponent; + encodeURI: typeof encodeURI; + encodeURIComponent: typeof encodeURIComponent; + escape: (str: string) => string; + eval: typeof eval; + global: Global; + isFinite: typeof isFinite; + isNaN: typeof isNaN; + parseFloat: typeof parseFloat; + parseInt: typeof parseInt; + process: Process; + root: Global; + setImmediate: (callback: (...args: any[]) => void, ...args: any[]) => any; + setInterval: (callback: (...args: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timer; + setTimeout: (callback: (...args: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timer; + undefined: typeof undefined; + unescape: (str: string) => string; + gc: () => void; + v8debug?: any; + } + + export interface Timer { + ref() : void; + unref() : void; + } +} + +/** + * @deprecated + */ +interface NodeBuffer { + [index: number]: number; + write(string: string, offset?: number, length?: number, encoding?: string): number; + toString(encoding?: string, start?: number, end?: number): string; + toJSON(): any; + length: number; + equals(otherBuffer: Buffer): boolean; + compare(otherBuffer: Buffer): number; + copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; + slice(start?: number, end?: number): Buffer; + writeUIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeUIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + readUIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readUIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readUInt8(offset: number, noAsset?: boolean): number; + readUInt16LE(offset: number, noAssert?: boolean): number; + readUInt16BE(offset: number, noAssert?: boolean): number; + readUInt32LE(offset: number, noAssert?: boolean): number; + readUInt32BE(offset: number, noAssert?: boolean): number; + readInt8(offset: number, noAssert?: boolean): number; + readInt16LE(offset: number, noAssert?: boolean): number; + readInt16BE(offset: number, noAssert?: boolean): number; + readInt32LE(offset: number, noAssert?: boolean): number; + readInt32BE(offset: number, noAssert?: boolean): number; + readFloatLE(offset: number, noAssert?: boolean): number; + readFloatBE(offset: number, noAssert?: boolean): number; + readDoubleLE(offset: number, noAssert?: boolean): number; + readDoubleBE(offset: number, noAssert?: boolean): number; + writeUInt8(value: number, offset: number, noAssert?: boolean): number; + writeUInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeInt8(value: number, offset: number, noAssert?: boolean): number; + writeInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeFloatLE(value: number, offset: number, noAssert?: boolean): number; + writeFloatBE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleLE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleBE(value: number, offset: number, noAssert?: boolean): number; + fill(value: any, offset?: number, end?: number): Buffer; +} + +/************************************************ +* * +* MODULES * +* * +************************************************/ +declare module "buffer" { + export var INSPECT_MAX_BYTES: number; +} + +declare module "querystring" { + export function stringify(obj: any, sep?: string, eq?: string): string; + export function parse(str: string, sep?: string, eq?: string, options?: { maxKeys?: number; }): any; + export function escape(str: string): string; + export function unescape(str: string): string; +} + +declare module "events" { + export class EventEmitter implements NodeJS.EventEmitter { + static listenerCount(emitter: EventEmitter, event: string): number; + + addListener(event: string, listener: Function): EventEmitter; + on(event: string, listener: Function): EventEmitter; + once(event: string, listener: Function): EventEmitter; + removeListener(event: string, listener: Function): EventEmitter; + removeAllListeners(event?: string): EventEmitter; + setMaxListeners(n: number): void; + listeners(event: string): Function[]; + emit(event: string, ...args: any[]): boolean; + } +} + +declare module "http" { + import * as events from "events"; + import * as net from "net"; + import * as stream from "stream"; + + export interface Server extends events.EventEmitter { + listen(port: number, hostname?: string, backlog?: number, callback?: Function): Server; + listen(port: number, hostname?: string, callback?: Function): Server; + listen(path: string, callback?: Function): Server; + listen(handle: any, listeningListener?: Function): Server; + close(cb?: any): Server; + address(): { port: number; family: string; address: string; }; + maxHeadersCount: number; + } + /** + * @deprecated Use IncomingMessage + */ + export interface ServerRequest extends IncomingMessage { + connection: net.Socket; + } + export interface ServerResponse extends events.EventEmitter, stream.Writable { + // Extended base methods + write(buffer: Buffer): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + write(str: string, encoding?: string, fd?: string): boolean; + + writeContinue(): void; + writeHead(statusCode: number, reasonPhrase?: string, headers?: any): void; + writeHead(statusCode: number, headers?: any): void; + statusCode: number; + statusMessage: string; + setHeader(name: string, value: string): void; + sendDate: boolean; + getHeader(name: string): string; + removeHeader(name: string): void; + write(chunk: any, encoding?: string): any; + addTrailers(headers: any): void; + + // Extended base methods + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + end(data?: any, encoding?: string): void; + } + export interface ClientRequest extends events.EventEmitter, stream.Writable { + // Extended base methods + write(buffer: Buffer): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + write(str: string, encoding?: string, fd?: string): boolean; + + write(chunk: any, encoding?: string): void; + abort(): void; + setTimeout(timeout: number, callback?: Function): void; + setNoDelay(noDelay?: boolean): void; + setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; + + // Extended base methods + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + end(data?: any, encoding?: string): void; + } + export interface IncomingMessage extends events.EventEmitter, stream.Readable { + httpVersion: string; + headers: any; + rawHeaders: string[]; + trailers: any; + rawTrailers: any; + setTimeout(msecs: number, callback: Function): NodeJS.Timer; + /** + * Only valid for request obtained from http.Server. + */ + method?: string; + /** + * Only valid for request obtained from http.Server. + */ + url?: string; + /** + * Only valid for response obtained from http.ClientRequest. + */ + statusCode?: number; + /** + * Only valid for response obtained from http.ClientRequest. + */ + statusMessage?: string; + socket: net.Socket; + } + /** + * @deprecated Use IncomingMessage + */ + export interface ClientResponse extends IncomingMessage { } + + export interface AgentOptions { + /** + * Keep sockets around in a pool to be used by other requests in the future. Default = false + */ + keepAlive?: boolean; + /** + * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000. + * Only relevant if keepAlive is set to true. + */ + keepAliveMsecs?: number; + /** + * Maximum number of sockets to allow per host. Default for Node 0.10 is 5, default for Node 0.12 is Infinity + */ + maxSockets?: number; + /** + * Maximum number of sockets to leave open in a free state. Only relevant if keepAlive is set to true. Default = 256. + */ + maxFreeSockets?: number; + } + + export class Agent { + maxSockets: number; + sockets: any; + requests: any; + + constructor(opts?: AgentOptions); + + /** + * Destroy any sockets that are currently in use by the agent. + * It is usually not necessary to do this. However, if you are using an agent with KeepAlive enabled, + * then it is best to explicitly shut down the agent when you know that it will no longer be used. Otherwise, + * sockets may hang open for quite a long time before the server terminates them. + */ + destroy(): void; + } + + export var METHODS: string[]; + + export var STATUS_CODES: { + [errorCode: number]: string; + [errorCode: string]: string; + }; + export function createServer(requestListener?: (request: IncomingMessage, response: ServerResponse) =>void ): Server; + export function createClient(port?: number, host?: string): any; + export function request(options: any, callback?: (res: IncomingMessage) => void): ClientRequest; + export function get(options: any, callback?: (res: IncomingMessage) => void): ClientRequest; + export var globalAgent: Agent; +} + +declare module "cluster" { + import * as child from "child_process"; + import * as events from "events"; + + export interface ClusterSettings { + exec?: string; + args?: string[]; + silent?: boolean; + } + + export class Worker extends events.EventEmitter { + id: string; + process: child.ChildProcess; + suicide: boolean; + send(message: any, sendHandle?: any): void; + kill(signal?: string): void; + destroy(signal?: string): void; + disconnect(): void; + } + + export var settings: ClusterSettings; + export var isMaster: boolean; + export var isWorker: boolean; + export function setupMaster(settings?: ClusterSettings): void; + export function fork(env?: any): Worker; + export function disconnect(callback?: Function): void; + export var worker: Worker; + export var workers: Worker[]; + + // Event emitter + export function addListener(event: string, listener: Function): void; + export function on(event: string, listener: Function): any; + export function once(event: string, listener: Function): void; + export function removeListener(event: string, listener: Function): void; + export function removeAllListeners(event?: string): void; + export function setMaxListeners(n: number): void; + export function listeners(event: string): Function[]; + export function emit(event: string, ...args: any[]): boolean; +} + +declare module "zlib" { + import * as stream from "stream"; + export interface ZlibOptions { chunkSize?: number; windowBits?: number; level?: number; memLevel?: number; strategy?: number; dictionary?: any; } + + export interface Gzip extends stream.Transform { } + export interface Gunzip extends stream.Transform { } + export interface Deflate extends stream.Transform { } + export interface Inflate extends stream.Transform { } + export interface DeflateRaw extends stream.Transform { } + export interface InflateRaw extends stream.Transform { } + export interface Unzip extends stream.Transform { } + + export function createGzip(options?: ZlibOptions): Gzip; + export function createGunzip(options?: ZlibOptions): Gunzip; + export function createDeflate(options?: ZlibOptions): Deflate; + export function createInflate(options?: ZlibOptions): Inflate; + export function createDeflateRaw(options?: ZlibOptions): DeflateRaw; + export function createInflateRaw(options?: ZlibOptions): InflateRaw; + export function createUnzip(options?: ZlibOptions): Unzip; + + export function deflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function deflateSync(buf: Buffer, options?: ZlibOptions): any; + export function deflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function deflateRawSync(buf: Buffer, options?: ZlibOptions): any; + export function gzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function gzipSync(buf: Buffer, options?: ZlibOptions): any; + export function gunzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function gunzipSync(buf: Buffer, options?: ZlibOptions): any; + export function inflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function inflateSync(buf: Buffer, options?: ZlibOptions): any; + export function inflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function inflateRawSync(buf: Buffer, options?: ZlibOptions): any; + export function unzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function unzipSync(buf: Buffer, options?: ZlibOptions): any; + + // Constants + export var Z_NO_FLUSH: number; + export var Z_PARTIAL_FLUSH: number; + export var Z_SYNC_FLUSH: number; + export var Z_FULL_FLUSH: number; + export var Z_FINISH: number; + export var Z_BLOCK: number; + export var Z_TREES: number; + export var Z_OK: number; + export var Z_STREAM_END: number; + export var Z_NEED_DICT: number; + export var Z_ERRNO: number; + export var Z_STREAM_ERROR: number; + export var Z_DATA_ERROR: number; + export var Z_MEM_ERROR: number; + export var Z_BUF_ERROR: number; + export var Z_VERSION_ERROR: number; + export var Z_NO_COMPRESSION: number; + export var Z_BEST_SPEED: number; + export var Z_BEST_COMPRESSION: number; + export var Z_DEFAULT_COMPRESSION: number; + export var Z_FILTERED: number; + export var Z_HUFFMAN_ONLY: number; + export var Z_RLE: number; + export var Z_FIXED: number; + export var Z_DEFAULT_STRATEGY: number; + export var Z_BINARY: number; + export var Z_TEXT: number; + export var Z_ASCII: number; + export var Z_UNKNOWN: number; + export var Z_DEFLATED: number; + export var Z_NULL: number; +} + +declare module "os" { + export function tmpdir(): string; + export function hostname(): string; + export function type(): string; + export function platform(): string; + export function arch(): string; + export function release(): string; + export function uptime(): number; + export function loadavg(): number[]; + export function totalmem(): number; + export function freemem(): number; + export function cpus(): { model: string; speed: number; times: { user: number; nice: number; sys: number; idle: number; irq: number; }; }[]; + export function networkInterfaces(): any; + export var EOL: string; +} + +declare module "https" { + import * as tls from "tls"; + import * as events from "events"; + import * as http from "http"; + + export interface ServerOptions { + pfx?: any; + key?: any; + passphrase?: string; + cert?: any; + ca?: any; + crl?: any; + ciphers?: string; + honorCipherOrder?: boolean; + requestCert?: boolean; + rejectUnauthorized?: boolean; + NPNProtocols?: any; + SNICallback?: (servername: string) => any; + } + + export interface RequestOptions { + host?: string; + hostname?: string; + port?: number; + path?: string; + method?: string; + headers?: any; + auth?: string; + agent?: any; + pfx?: any; + key?: any; + passphrase?: string; + cert?: any; + ca?: any; + ciphers?: string; + rejectUnauthorized?: boolean; + } + + export interface Agent { + maxSockets: number; + sockets: any; + requests: any; + } + export var Agent: { + new (options?: RequestOptions): Agent; + }; + export interface Server extends tls.Server { } + export function createServer(options: ServerOptions, requestListener?: Function): Server; + export function request(options: RequestOptions, callback?: (res: http.IncomingMessage) =>void ): http.ClientRequest; + export function get(options: RequestOptions, callback?: (res: http.IncomingMessage) =>void ): http.ClientRequest; + export var globalAgent: Agent; +} + +declare module "punycode" { + export function decode(string: string): string; + export function encode(string: string): string; + export function toUnicode(domain: string): string; + export function toASCII(domain: string): string; + export var ucs2: ucs2; + interface ucs2 { + decode(string: string): string; + encode(codePoints: number[]): string; + } + export var version: any; +} + +declare module "repl" { + import * as stream from "stream"; + import * as events from "events"; + + export interface ReplOptions { + prompt?: string; + input?: NodeJS.ReadableStream; + output?: NodeJS.WritableStream; + terminal?: boolean; + eval?: Function; + useColors?: boolean; + useGlobal?: boolean; + ignoreUndefined?: boolean; + writer?: Function; + } + export function start(options: ReplOptions): events.EventEmitter; +} + +declare module "readline" { + import * as events from "events"; + import * as stream from "stream"; + + export interface ReadLine extends events.EventEmitter { + setPrompt(prompt: string): void; + prompt(preserveCursor?: boolean): void; + question(query: string, callback: Function): void; + pause(): void; + resume(): void; + close(): void; + write(data: any, key?: any): void; + } + export interface ReadLineOptions { + input: NodeJS.ReadableStream; + output: NodeJS.WritableStream; + completer?: Function; + terminal?: boolean; + } + export function createInterface(options: ReadLineOptions): ReadLine; +} + +declare module "vm" { + export interface Context { } + export interface Script { + runInThisContext(): void; + runInNewContext(sandbox?: Context): void; + } + export function runInThisContext(code: string, filename?: string): void; + export function runInNewContext(code: string, sandbox?: Context, filename?: string): void; + export function runInContext(code: string, context: Context, filename?: string): void; + export function createContext(initSandbox?: Context): Context; + export function createScript(code: string, filename?: string): Script; +} + +declare module "child_process" { + import * as events from "events"; + import * as stream from "stream"; + + export interface ChildProcess extends events.EventEmitter { + stdin: stream.Writable; + stdout: stream.Readable; + stderr: stream.Readable; + pid: number; + kill(signal?: string): void; + send(message: any, sendHandle?: any): void; + disconnect(): void; + unref(): void; + } + + export function spawn(command: string, args?: string[], options?: { + cwd?: string; + stdio?: any; + custom?: any; + env?: any; + detached?: boolean; + }): ChildProcess; + export function exec(command: string, options: { + cwd?: string; + stdio?: any; + customFds?: any; + env?: any; + encoding?: string; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + }, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function exec(command: string, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, + callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, args?: string[], + callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, args?: string[], options?: { + cwd?: string; + stdio?: any; + customFds?: any; + env?: any; + encoding?: string; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + }, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function fork(modulePath: string, args?: string[], options?: { + cwd?: string; + env?: any; + encoding?: string; + }): ChildProcess; + export function spawnSync(command: string, args?: string[], options?: { + cwd?: string; + input?: string | Buffer; + stdio?: any; + env?: any; + uid?: number; + gid?: number; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + encoding?: string; + }): { + pid: number; + output: string[]; + stdout: string | Buffer; + stderr: string | Buffer; + status: number; + signal: string; + error: Error; + }; + export function execSync(command: string, options?: { + cwd?: string; + input?: string|Buffer; + stdio?: any; + env?: any; + uid?: number; + gid?: number; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + encoding?: string; + }): string | Buffer; + export function execFileSync(command: string, args?: string[], options?: { + cwd?: string; + input?: string|Buffer; + stdio?: any; + env?: any; + uid?: number; + gid?: number; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + encoding?: string; + }): string | Buffer; +} + +declare module "url" { + export interface Url { + href: string; + protocol: string; + auth: string; + hostname: string; + port: string; + host: string; + pathname: string; + search: string; + query: any; // string | Object + slashes: boolean; + hash?: string; + path?: string; + } + + export interface UrlOptions { + protocol?: string; + auth?: string; + hostname?: string; + port?: string; + host?: string; + pathname?: string; + search?: string; + query?: any; + hash?: string; + path?: string; + } + + export function parse(urlStr: string, parseQueryString?: boolean , slashesDenoteHost?: boolean ): Url; + export function format(url: UrlOptions): string; + export function resolve(from: string, to: string): string; +} + +declare module "dns" { + export function lookup(domain: string, family: number, callback: (err: Error, address: string, family: number) =>void ): string; + export function lookup(domain: string, callback: (err: Error, address: string, family: number) =>void ): string; + export function resolve(domain: string, rrtype: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolve(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolve4(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolve6(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveMx(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveTxt(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveSrv(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveNs(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function resolveCname(domain: string, callback: (err: Error, addresses: string[]) =>void ): string[]; + export function reverse(ip: string, callback: (err: Error, domains: string[]) =>void ): string[]; +} + +declare module "net" { + import * as stream from "stream"; + + export interface Socket extends stream.Duplex { + // Extended base methods + write(buffer: Buffer): boolean; + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding?: string, cb?: Function): boolean; + write(str: string, encoding?: string, fd?: string): boolean; + + connect(port: number, host?: string, connectionListener?: Function): void; + connect(path: string, connectionListener?: Function): void; + bufferSize: number; + setEncoding(encoding?: string): void; + write(data: any, encoding?: string, callback?: Function): void; + destroy(): void; + pause(): void; + resume(): void; + setTimeout(timeout: number, callback?: Function): void; + setNoDelay(noDelay?: boolean): void; + setKeepAlive(enable?: boolean, initialDelay?: number): void; + address(): { port: number; family: string; address: string; }; + unref(): void; + ref(): void; + + remoteAddress: string; + remoteFamily: string; + remotePort: number; + localAddress: string; + localPort: number; + bytesRead: number; + bytesWritten: number; + + // Extended base methods + end(): void; + end(buffer: Buffer, cb?: Function): void; + end(str: string, cb?: Function): void; + end(str: string, encoding?: string, cb?: Function): void; + end(data?: any, encoding?: string): void; + } + + export var Socket: { + new (options?: { fd?: string; type?: string; allowHalfOpen?: boolean; }): Socket; + }; + + export interface Server extends Socket { + listen(port: number, host?: string, backlog?: number, listeningListener?: Function): Server; + listen(path: string, listeningListener?: Function): Server; + listen(handle: any, listeningListener?: Function): Server; + close(callback?: Function): Server; + address(): { port: number; family: string; address: string; }; + maxConnections: number; + connections: number; + } + export function createServer(connectionListener?: (socket: Socket) =>void ): Server; + export function createServer(options?: { allowHalfOpen?: boolean; }, connectionListener?: (socket: Socket) =>void ): Server; + export function connect(options: { allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; + export function connect(port: number, host?: string, connectionListener?: Function): Socket; + export function connect(path: string, connectionListener?: Function): Socket; + export function createConnection(options: { allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; + export function createConnection(port: number, host?: string, connectionListener?: Function): Socket; + export function createConnection(path: string, connectionListener?: Function): Socket; + export function isIP(input: string): number; + export function isIPv4(input: string): boolean; + export function isIPv6(input: string): boolean; +} + +declare module "dgram" { + import * as events from "events"; + + interface RemoteInfo { + address: string; + port: number; + size: number; + } + + interface AddressInfo { + address: string; + family: string; + port: number; + } + + export function createSocket(type: string, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): Socket; + + interface Socket extends events.EventEmitter { + send(buf: Buffer, offset: number, length: number, port: number, address: string, callback?: (error: Error, bytes: number) => void): void; + bind(port: number, address?: string, callback?: () => void): void; + close(): void; + address(): AddressInfo; + setBroadcast(flag: boolean): void; + setMulticastTTL(ttl: number): void; + setMulticastLoopback(flag: boolean): void; + addMembership(multicastAddress: string, multicastInterface?: string): void; + dropMembership(multicastAddress: string, multicastInterface?: string): void; + } +} + +declare module "fs" { + import * as stream from "stream"; + import * as events from "events"; + + interface Stats { + isFile(): boolean; + isDirectory(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isSymbolicLink(): boolean; + isFIFO(): boolean; + isSocket(): boolean; + dev: number; + ino: number; + mode: number; + nlink: number; + uid: number; + gid: number; + rdev: number; + size: number; + blksize: number; + blocks: number; + atime: Date; + mtime: Date; + ctime: Date; + birthtime: Date; + } + + interface FSWatcher extends events.EventEmitter { + close(): void; + } + + export interface ReadStream extends stream.Readable { + close(): void; + } + export interface WriteStream extends stream.Writable { + close(): void; + bytesWritten: number; + } + + /** + * Asynchronous rename. + * @param oldPath + * @param newPath + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function rename(oldPath: string, newPath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /** + * Synchronous rename + * @param oldPath + * @param newPath + */ + export function renameSync(oldPath: string, newPath: string): void; + export function truncate(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function truncate(path: string, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function truncateSync(path: string, len?: number): void; + export function ftruncate(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function ftruncate(fd: number, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function ftruncateSync(fd: number, len?: number): void; + export function chown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chownSync(path: string, uid: number, gid: number): void; + export function fchown(fd: number, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchownSync(fd: number, uid: number, gid: number): void; + export function lchown(path: string, uid: number, gid: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchownSync(path: string, uid: number, gid: number): void; + export function chmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function chmodSync(path: string, mode: number): void; + export function chmodSync(path: string, mode: string): void; + export function fchmod(fd: number, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchmod(fd: number, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fchmodSync(fd: number, mode: number): void; + export function fchmodSync(fd: number, mode: string): void; + export function lchmod(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchmod(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function lchmodSync(path: string, mode: number): void; + export function lchmodSync(path: string, mode: string): void; + export function stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function lstat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function fstat(fd: number, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + export function statSync(path: string): Stats; + export function lstatSync(path: string): Stats; + export function fstatSync(fd: number): Stats; + export function link(srcpath: string, dstpath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function linkSync(srcpath: string, dstpath: string): void; + export function symlink(srcpath: string, dstpath: string, type?: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function symlinkSync(srcpath: string, dstpath: string, type?: string): void; + export function readlink(path: string, callback?: (err: NodeJS.ErrnoException, linkString: string) => any): void; + export function readlinkSync(path: string): string; + export function realpath(path: string, callback?: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void; + export function realpath(path: string, cache: {[path: string]: string}, callback: (err: NodeJS.ErrnoException, resolvedPath: string) =>any): void; + export function realpathSync(path: string, cache?: { [path: string]: string }): string; + /* + * Asynchronous unlink - deletes the file specified in {path} + * + * @param path + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function unlink(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Synchronous unlink - deletes the file specified in {path} + * + * @param path + */ + export function unlinkSync(path: string): void; + /* + * Asynchronous rmdir - removes the directory specified in {path} + * + * @param path + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function rmdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Synchronous rmdir - removes the directory specified in {path} + * + * @param path + */ + export function rmdirSync(path: string): void; + /* + * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function mkdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function mkdir(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function mkdir(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Synchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function mkdirSync(path: string, mode?: number): void; + /* + * Synchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ + export function mkdirSync(path: string, mode?: string): void; + export function readdir(path: string, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void; + export function readdirSync(path: string): string[]; + export function close(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function closeSync(fd: number): void; + export function open(path: string, flags: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function open(path: string, flags: string, mode: number, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function open(path: string, flags: string, mode: string, callback?: (err: NodeJS.ErrnoException, fd: number) => any): void; + export function openSync(path: string, flags: string, mode?: number): number; + export function openSync(path: string, flags: string, mode?: string): number; + export function utimes(path: string, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function utimes(path: string, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function utimesSync(path: string, atime: number, mtime: number): void; + export function utimesSync(path: string, atime: Date, mtime: Date): void; + export function futimes(fd: number, atime: number, mtime: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function futimes(fd: number, atime: Date, mtime: Date, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function futimesSync(fd: number, atime: number, mtime: number): void; + export function futimesSync(fd: number, atime: Date, mtime: Date): void; + export function fsync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fsyncSync(fd: number): void; + export function write(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; + export function write(fd: number, buffer: Buffer, offset: number, length: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; + export function write(fd: number, data: any, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; + export function write(fd: number, data: any, offset: number, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; + export function write(fd: number, data: any, offset: number, encoding: string, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; + export function writeSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + export function read(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void; + export function readSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param encoding + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ + export function readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFile returns a string; otherwise it returns a Buffer. + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ + export function readFile(filename: string, options: { encoding: string; flag?: string; }, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFile returns a string; otherwise it returns a Buffer. + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ + export function readFile(filename: string, options: { flag?: string; }, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ + export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; + /* + * Synchronous readFile - Synchronously reads the entire contents of a file. + * + * @param fileName + * @param encoding + */ + export function readFileSync(filename: string, encoding: string): string; + /* + * Synchronous readFile - Synchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFileSync returns a string; otherwise it returns a Buffer. + */ + export function readFileSync(filename: string, options: { encoding: string; flag?: string; }): string; + /* + * Synchronous readFile - Synchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFileSync returns a string; otherwise it returns a Buffer. + */ + export function readFileSync(filename: string, options?: { flag?: string; }): Buffer; + export function writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; + export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; + export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; + export function appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; + export function watchFile(filename: string, listener: (curr: Stats, prev: Stats) => void): void; + export function watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: (curr: Stats, prev: Stats) => void): void; + export function unwatchFile(filename: string, listener?: (curr: Stats, prev: Stats) => void): void; + export function watch(filename: string, listener?: (event: string, filename: string) => any): FSWatcher; + export function watch(filename: string, options: { persistent?: boolean; }, listener?: (event: string, filename: string) => any): FSWatcher; + export function exists(path: string, callback?: (exists: boolean) => void): void; + export function existsSync(path: string): boolean; + /** Constant for fs.access(). File is visible to the calling process. */ + export var F_OK: number; + /** Constant for fs.access(). File can be read by the calling process. */ + export var R_OK: number; + /** Constant for fs.access(). File can be written by the calling process. */ + export var W_OK: number; + /** Constant for fs.access(). File can be executed by the calling process. */ + export var X_OK: number; + /** Tests a user's permissions for the file specified by path. */ + export function access(path: string, callback: (err: NodeJS.ErrnoException) => void): void; + export function access(path: string, mode: number, callback: (err: NodeJS.ErrnoException) => void): void; + /** Synchronous version of fs.access. This throws if any accessibility checks fail, and does nothing otherwise. */ + export function accessSync(path: string, mode ?: number): void; + export function createReadStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: number; + mode?: number; + autoClose?: boolean; + }): ReadStream; + export function createWriteStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: number; + mode?: number; + }): WriteStream; +} + +declare module "path" { + + /** + * A parsed path object generated by path.parse() or consumed by path.format(). + */ + export interface ParsedPath { + /** + * The root of the path such as '/' or 'c:\' + */ + root: string; + /** + * The full directory path such as '/home/user/dir' or 'c:\path\dir' + */ + dir: string; + /** + * The file name including extension (if any) such as 'index.html' + */ + base: string; + /** + * The file extension (if any) such as '.html' + */ + ext: string; + /** + * The file name without extension (if any) such as 'index' + */ + name: string; + } + + /** + * Normalize a string path, reducing '..' and '.' parts. + * When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved. On Windows backslashes are used. + * + * @param p string path to normalize. + */ + export function normalize(p: string): string; + /** + * Join all arguments together and normalize the resulting path. + * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown. + * + * @param paths string paths to join. + */ + export function join(...paths: any[]): string; + /** + * Join all arguments together and normalize the resulting path. + * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown. + * + * @param paths string paths to join. + */ + export function join(...paths: string[]): string; + /** + * The right-most parameter is considered {to}. Other parameters are considered an array of {from}. + * + * Starting from leftmost {from} paramter, resolves {to} to an absolute path. + * + * If {to} isn't already absolute, {from} arguments are prepended in right to left order, until an absolute path is found. If after using all {from} paths still no absolute path is found, the current working directory is used as well. The resulting path is normalized, and trailing slashes are removed unless the path gets resolved to the root directory. + * + * @param pathSegments string paths to join. Non-string arguments are ignored. + */ + export function resolve(...pathSegments: any[]): string; + /** + * Determines whether {path} is an absolute path. An absolute path will always resolve to the same location, regardless of the working directory. + * + * @param path path to test. + */ + export function isAbsolute(path: string): boolean; + /** + * Solve the relative path from {from} to {to}. + * At times we have two absolute paths, and we need to derive the relative path from one to the other. This is actually the reverse transform of path.resolve. + * + * @param from + * @param to + */ + export function relative(from: string, to: string): string; + /** + * Return the directory name of a path. Similar to the Unix dirname command. + * + * @param p the path to evaluate. + */ + export function dirname(p: string): string; + /** + * Return the last portion of a path. Similar to the Unix basename command. + * Often used to extract the file name from a fully qualified path. + * + * @param p the path to evaluate. + * @param ext optionally, an extension to remove from the result. + */ + export function basename(p: string, ext?: string): string; + /** + * Return the extension of the path, from the last '.' to end of string in the last portion of the path. + * If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string + * + * @param p the path to evaluate. + */ + export function extname(p: string): string; + /** + * The platform-specific file separator. '\\' or '/'. + */ + export var sep: string; + /** + * The platform-specific file delimiter. ';' or ':'. + */ + export var delimiter: string; + /** + * Returns an object from a path string - the opposite of format(). + * + * @param pathString path to evaluate. + */ + export function parse(pathString: string): ParsedPath; + /** + * Returns a path string from an object - the opposite of parse(). + * + * @param pathString path to evaluate. + */ + export function format(pathObject: ParsedPath): string; + + export module posix { + export function normalize(p: string): string; + export function join(...paths: any[]): string; + export function resolve(...pathSegments: any[]): string; + export function isAbsolute(p: string): boolean; + export function relative(from: string, to: string): string; + export function dirname(p: string): string; + export function basename(p: string, ext?: string): string; + export function extname(p: string): string; + export var sep: string; + export var delimiter: string; + export function parse(p: string): ParsedPath; + export function format(pP: ParsedPath): string; + } + + export module win32 { + export function normalize(p: string): string; + export function join(...paths: any[]): string; + export function resolve(...pathSegments: any[]): string; + export function isAbsolute(p: string): boolean; + export function relative(from: string, to: string): string; + export function dirname(p: string): string; + export function basename(p: string, ext?: string): string; + export function extname(p: string): string; + export var sep: string; + export var delimiter: string; + export function parse(p: string): ParsedPath; + export function format(pP: ParsedPath): string; + } +} + +declare module "string_decoder" { + export interface NodeStringDecoder { + write(buffer: Buffer): string; + detectIncompleteChar(buffer: Buffer): number; + } + export var StringDecoder: { + new (encoding: string): NodeStringDecoder; + }; +} + +declare module "tls" { + import * as crypto from "crypto"; + import * as net from "net"; + import * as stream from "stream"; + + var CLIENT_RENEG_LIMIT: number; + var CLIENT_RENEG_WINDOW: number; + + export interface TlsOptions { + pfx?: any; //string or buffer + key?: any; //string or buffer + passphrase?: string; + cert?: any; + ca?: any; //string or buffer + crl?: any; //string or string array + ciphers?: string; + honorCipherOrder?: any; + requestCert?: boolean; + rejectUnauthorized?: boolean; + NPNProtocols?: any; //array or Buffer; + SNICallback?: (servername: string) => any; + } + + export interface ConnectionOptions { + host?: string; + port?: number; + socket?: net.Socket; + pfx?: any; //string | Buffer + key?: any; //string | Buffer + passphrase?: string; + cert?: any; //string | Buffer + ca?: any; //Array of string | Buffer + rejectUnauthorized?: boolean; + NPNProtocols?: any; //Array of string | Buffer + servername?: string; + } + + export interface Server extends net.Server { + // Extended base methods + listen(port: number, host?: string, backlog?: number, listeningListener?: Function): Server; + listen(path: string, listeningListener?: Function): Server; + listen(handle: any, listeningListener?: Function): Server; + + listen(port: number, host?: string, callback?: Function): Server; + close(): Server; + address(): { port: number; family: string; address: string; }; + addContext(hostName: string, credentials: { + key: string; + cert: string; + ca: string; + }): void; + maxConnections: number; + connections: number; + } + + export interface ClearTextStream extends stream.Duplex { + authorized: boolean; + authorizationError: Error; + getPeerCertificate(): any; + getCipher: { + name: string; + version: string; + }; + address: { + port: number; + family: string; + address: string; + }; + remoteAddress: string; + remotePort: number; + } + + export interface SecurePair { + encrypted: any; + cleartext: any; + } + + export interface SecureContextOptions { + pfx?: any; //string | buffer + key?: any; //string | buffer + passphrase?: string; + cert?: any; // string | buffer + ca?: any; // string | buffer + crl?: any; // string | string[] + ciphers?: string; + honorCipherOrder?: boolean; + } + + export interface SecureContext { + context: any; + } + + export function createServer(options: TlsOptions, secureConnectionListener?: (cleartextStream: ClearTextStream) =>void ): Server; + export function connect(options: TlsOptions, secureConnectionListener?: () =>void ): ClearTextStream; + export function connect(port: number, host?: string, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; + export function connect(port: number, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; + export function createSecurePair(credentials?: crypto.Credentials, isServer?: boolean, requestCert?: boolean, rejectUnauthorized?: boolean): SecurePair; + export function createSecureContext(details: SecureContextOptions): SecureContext; +} + +declare module "crypto" { + export interface CredentialDetails { + pfx: string; + key: string; + passphrase: string; + cert: string; + ca: any; //string | string array + crl: any; //string | string array + ciphers: string; + } + export interface Credentials { context?: any; } + export function createCredentials(details: CredentialDetails): Credentials; + export function createHash(algorithm: string): Hash; + export function createHmac(algorithm: string, key: string): Hmac; + export function createHmac(algorithm: string, key: Buffer): Hmac; + interface Hash { + update(data: any, input_encoding?: string): Hash; + digest(encoding: 'buffer'): Buffer; + digest(encoding: string): any; + digest(): Buffer; + } + interface Hmac { + update(data: any, input_encoding?: string): Hmac; + digest(encoding: 'buffer'): Buffer; + digest(encoding: string): any; + digest(): Buffer; + } + export function createCipher(algorithm: string, password: any): Cipher; + export function createCipheriv(algorithm: string, key: any, iv: any): Cipher; + interface Cipher { + update(data: Buffer): Buffer; + update(data: string, input_encoding?: string, output_encoding?: string): string; + final(): Buffer; + final(output_encoding: string): string; + setAutoPadding(auto_padding: boolean): void; + } + export function createDecipher(algorithm: string, password: any): Decipher; + export function createDecipheriv(algorithm: string, key: any, iv: any): Decipher; + interface Decipher { + update(data: Buffer): Buffer; + update(data: string, input_encoding?: string, output_encoding?: string): string; + final(): Buffer; + final(output_encoding: string): string; + setAutoPadding(auto_padding: boolean): void; + } + export function createSign(algorithm: string): Signer; + interface Signer extends NodeJS.WritableStream { + update(data: any): void; + sign(private_key: string, output_format: string): string; + } + export function createVerify(algorith: string): Verify; + interface Verify extends NodeJS.WritableStream { + update(data: any): void; + verify(object: string, signature: string, signature_format?: string): boolean; + } + export function createDiffieHellman(prime_length: number): DiffieHellman; + export function createDiffieHellman(prime: number, encoding?: string): DiffieHellman; + interface DiffieHellman { + generateKeys(encoding?: string): string; + computeSecret(other_public_key: string, input_encoding?: string, output_encoding?: string): string; + getPrime(encoding?: string): string; + getGenerator(encoding: string): string; + getPublicKey(encoding?: string): string; + getPrivateKey(encoding?: string): string; + setPublicKey(public_key: string, encoding?: string): void; + setPrivateKey(public_key: string, encoding?: string): void; + } + export function getDiffieHellman(group_name: string): DiffieHellman; + export function pbkdf2(password: string, salt: string, iterations: number, keylen: number, callback: (err: Error, derivedKey: Buffer) => any): void; + export function pbkdf2(password: string, salt: string, iterations: number, keylen: number, digest: string, callback: (err: Error, derivedKey: Buffer) => any): void; + export function pbkdf2Sync(password: string, salt: string, iterations: number, keylen: number) : Buffer; + export function pbkdf2Sync(password: string, salt: string, iterations: number, keylen: number, digest: string) : Buffer; + export function randomBytes(size: number): Buffer; + export function randomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; + export function pseudoRandomBytes(size: number): Buffer; + export function pseudoRandomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; +} + +declare module "stream" { + import * as events from "events"; + + export interface Stream extends events.EventEmitter { + pipe(destination: T, options?: { end?: boolean; }): T; + } + + export interface ReadableOptions { + highWaterMark?: number; + encoding?: string; + objectMode?: boolean; + } + + export class Readable extends events.EventEmitter implements NodeJS.ReadableStream { + readable: boolean; + constructor(opts?: ReadableOptions); + _read(size: number): void; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: any): void; + wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; + push(chunk: any, encoding?: string): boolean; + } + + export interface WritableOptions { + highWaterMark?: number; + decodeStrings?: boolean; + objectMode?: boolean; + } + + export class Writable extends events.EventEmitter implements NodeJS.WritableStream { + writable: boolean; + constructor(opts?: WritableOptions); + _write(chunk: any, encoding: string, callback: Function): void; + write(chunk: any, cb?: Function): boolean; + write(chunk: any, encoding?: string, cb?: Function): boolean; + end(): void; + end(chunk: any, cb?: Function): void; + end(chunk: any, encoding?: string, cb?: Function): void; + } + + export interface DuplexOptions extends ReadableOptions, WritableOptions { + allowHalfOpen?: boolean; + } + + // Note: Duplex extends both Readable and Writable. + export class Duplex extends Readable implements NodeJS.ReadWriteStream { + writable: boolean; + constructor(opts?: DuplexOptions); + _write(chunk: any, encoding: string, callback: Function): void; + write(chunk: any, cb?: Function): boolean; + write(chunk: any, encoding?: string, cb?: Function): boolean; + end(): void; + end(chunk: any, cb?: Function): void; + end(chunk: any, encoding?: string, cb?: Function): void; + } + + export interface TransformOptions extends ReadableOptions, WritableOptions {} + + // Note: Transform lacks the _read and _write methods of Readable/Writable. + export class Transform extends events.EventEmitter implements NodeJS.ReadWriteStream { + readable: boolean; + writable: boolean; + constructor(opts?: TransformOptions); + _transform(chunk: any, encoding: string, callback: Function): void; + _flush(callback: Function): void; + read(size?: number): any; + setEncoding(encoding: string): void; + pause(): void; + resume(): void; + pipe(destination: T, options?: { end?: boolean; }): T; + unpipe(destination?: T): void; + unshift(chunk: any): void; + wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; + push(chunk: any, encoding?: string): boolean; + write(chunk: any, cb?: Function): boolean; + write(chunk: any, encoding?: string, cb?: Function): boolean; + end(): void; + end(chunk: any, cb?: Function): void; + end(chunk: any, encoding?: string, cb?: Function): void; + } + + export class PassThrough extends Transform {} +} + +declare module "util" { + export interface InspectOptions { + showHidden?: boolean; + depth?: number; + colors?: boolean; + customInspect?: boolean; + } + + export function format(format: any, ...param: any[]): string; + export function debug(string: string): void; + export function error(...param: any[]): void; + export function puts(...param: any[]): void; + export function print(...param: any[]): void; + export function log(string: string): void; + export function inspect(object: any, showHidden?: boolean, depth?: number, color?: boolean): string; + export function inspect(object: any, options: InspectOptions): string; + export function isArray(object: any): boolean; + export function isRegExp(object: any): boolean; + export function isDate(object: any): boolean; + export function isError(object: any): boolean; + export function inherits(constructor: any, superConstructor: any): void; + export function debuglog(key:string): (msg:string,...param: any[])=>void; +} + +declare module "assert" { + function internal (value: any, message?: string): void; + module internal { + export class AssertionError implements Error { + name: string; + message: string; + actual: any; + expected: any; + operator: string; + generatedMessage: boolean; + + constructor(options?: {message?: string; actual?: any; expected?: any; + operator?: string; stackStartFunction?: Function}); + } + + export function fail(actual?: any, expected?: any, message?: string, operator?: string): void; + export function ok(value: any, message?: string): void; + export function equal(actual: any, expected: any, message?: string): void; + export function notEqual(actual: any, expected: any, message?: string): void; + export function deepEqual(actual: any, expected: any, message?: string): void; + export function notDeepEqual(acutal: any, expected: any, message?: string): void; + export function strictEqual(actual: any, expected: any, message?: string): void; + export function notStrictEqual(actual: any, expected: any, message?: string): void; + export var throws: { + (block: Function, message?: string): void; + (block: Function, error: Function, message?: string): void; + (block: Function, error: RegExp, message?: string): void; + (block: Function, error: (err: any) => boolean, message?: string): void; + }; + + export var doesNotThrow: { + (block: Function, message?: string): void; + (block: Function, error: Function, message?: string): void; + (block: Function, error: RegExp, message?: string): void; + (block: Function, error: (err: any) => boolean, message?: string): void; + }; + + export function ifError(value: any): void; + } + + export = internal; +} + +declare module "tty" { + import * as net from "net"; + + export function isatty(fd: number): boolean; + export interface ReadStream extends net.Socket { + isRaw: boolean; + setRawMode(mode: boolean): void; + } + export interface WriteStream extends net.Socket { + columns: number; + rows: number; + } +} + +declare module "domain" { + import * as events from "events"; + + export class Domain extends events.EventEmitter { + run(fn: Function): void; + add(emitter: events.EventEmitter): void; + remove(emitter: events.EventEmitter): void; + bind(cb: (err: Error, data: any) => any): any; + intercept(cb: (data: any) => any): any; + dispose(): void; + + addListener(event: string, listener: Function): Domain; + on(event: string, listener: Function): Domain; + once(event: string, listener: Function): Domain; + removeListener(event: string, listener: Function): Domain; + removeAllListeners(event?: string): Domain; + } + + export function create(): Domain; +} + +declare module "constants" { + export var E2BIG: number; + export var EACCES: number; + export var EADDRINUSE: number; + export var EADDRNOTAVAIL: number; + export var EAFNOSUPPORT: number; + export var EAGAIN: number; + export var EALREADY: number; + export var EBADF: number; + export var EBADMSG: number; + export var EBUSY: number; + export var ECANCELED: number; + export var ECHILD: number; + export var ECONNABORTED: number; + export var ECONNREFUSED: number; + export var ECONNRESET: number; + export var EDEADLK: number; + export var EDESTADDRREQ: number; + export var EDOM: number; + export var EEXIST: number; + export var EFAULT: number; + export var EFBIG: number; + export var EHOSTUNREACH: number; + export var EIDRM: number; + export var EILSEQ: number; + export var EINPROGRESS: number; + export var EINTR: number; + export var EINVAL: number; + export var EIO: number; + export var EISCONN: number; + export var EISDIR: number; + export var ELOOP: number; + export var EMFILE: number; + export var EMLINK: number; + export var EMSGSIZE: number; + export var ENAMETOOLONG: number; + export var ENETDOWN: number; + export var ENETRESET: number; + export var ENETUNREACH: number; + export var ENFILE: number; + export var ENOBUFS: number; + export var ENODATA: number; + export var ENODEV: number; + export var ENOENT: number; + export var ENOEXEC: number; + export var ENOLCK: number; + export var ENOLINK: number; + export var ENOMEM: number; + export var ENOMSG: number; + export var ENOPROTOOPT: number; + export var ENOSPC: number; + export var ENOSR: number; + export var ENOSTR: number; + export var ENOSYS: number; + export var ENOTCONN: number; + export var ENOTDIR: number; + export var ENOTEMPTY: number; + export var ENOTSOCK: number; + export var ENOTSUP: number; + export var ENOTTY: number; + export var ENXIO: number; + export var EOPNOTSUPP: number; + export var EOVERFLOW: number; + export var EPERM: number; + export var EPIPE: number; + export var EPROTO: number; + export var EPROTONOSUPPORT: number; + export var EPROTOTYPE: number; + export var ERANGE: number; + export var EROFS: number; + export var ESPIPE: number; + export var ESRCH: number; + export var ETIME: number; + export var ETIMEDOUT: number; + export var ETXTBSY: number; + export var EWOULDBLOCK: number; + export var EXDEV: number; + export var WSAEINTR: number; + export var WSAEBADF: number; + export var WSAEACCES: number; + export var WSAEFAULT: number; + export var WSAEINVAL: number; + export var WSAEMFILE: number; + export var WSAEWOULDBLOCK: number; + export var WSAEINPROGRESS: number; + export var WSAEALREADY: number; + export var WSAENOTSOCK: number; + export var WSAEDESTADDRREQ: number; + export var WSAEMSGSIZE: number; + export var WSAEPROTOTYPE: number; + export var WSAENOPROTOOPT: number; + export var WSAEPROTONOSUPPORT: number; + export var WSAESOCKTNOSUPPORT: number; + export var WSAEOPNOTSUPP: number; + export var WSAEPFNOSUPPORT: number; + export var WSAEAFNOSUPPORT: number; + export var WSAEADDRINUSE: number; + export var WSAEADDRNOTAVAIL: number; + export var WSAENETDOWN: number; + export var WSAENETUNREACH: number; + export var WSAENETRESET: number; + export var WSAECONNABORTED: number; + export var WSAECONNRESET: number; + export var WSAENOBUFS: number; + export var WSAEISCONN: number; + export var WSAENOTCONN: number; + export var WSAESHUTDOWN: number; + export var WSAETOOMANYREFS: number; + export var WSAETIMEDOUT: number; + export var WSAECONNREFUSED: number; + export var WSAELOOP: number; + export var WSAENAMETOOLONG: number; + export var WSAEHOSTDOWN: number; + export var WSAEHOSTUNREACH: number; + export var WSAENOTEMPTY: number; + export var WSAEPROCLIM: number; + export var WSAEUSERS: number; + export var WSAEDQUOT: number; + export var WSAESTALE: number; + export var WSAEREMOTE: number; + export var WSASYSNOTREADY: number; + export var WSAVERNOTSUPPORTED: number; + export var WSANOTINITIALISED: number; + export var WSAEDISCON: number; + export var WSAENOMORE: number; + export var WSAECANCELLED: number; + export var WSAEINVALIDPROCTABLE: number; + export var WSAEINVALIDPROVIDER: number; + export var WSAEPROVIDERFAILEDINIT: number; + export var WSASYSCALLFAILURE: number; + export var WSASERVICE_NOT_FOUND: number; + export var WSATYPE_NOT_FOUND: number; + export var WSA_E_NO_MORE: number; + export var WSA_E_CANCELLED: number; + export var WSAEREFUSED: number; + export var SIGHUP: number; + export var SIGINT: number; + export var SIGILL: number; + export var SIGABRT: number; + export var SIGFPE: number; + export var SIGKILL: number; + export var SIGSEGV: number; + export var SIGTERM: number; + export var SIGBREAK: number; + export var SIGWINCH: number; + export var SSL_OP_ALL: number; + export var SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: number; + export var SSL_OP_CIPHER_SERVER_PREFERENCE: number; + export var SSL_OP_CISCO_ANYCONNECT: number; + export var SSL_OP_COOKIE_EXCHANGE: number; + export var SSL_OP_CRYPTOPRO_TLSEXT_BUG: number; + export var SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: number; + export var SSL_OP_EPHEMERAL_RSA: number; + export var SSL_OP_LEGACY_SERVER_CONNECT: number; + export var SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER: number; + export var SSL_OP_MICROSOFT_SESS_ID_BUG: number; + export var SSL_OP_MSIE_SSLV2_RSA_PADDING: number; + export var SSL_OP_NETSCAPE_CA_DN_BUG: number; + export var SSL_OP_NETSCAPE_CHALLENGE_BUG: number; + export var SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: number; + export var SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: number; + export var SSL_OP_NO_COMPRESSION: number; + export var SSL_OP_NO_QUERY_MTU: number; + export var SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: number; + export var SSL_OP_NO_SSLv2: number; + export var SSL_OP_NO_SSLv3: number; + export var SSL_OP_NO_TICKET: number; + export var SSL_OP_NO_TLSv1: number; + export var SSL_OP_NO_TLSv1_1: number; + export var SSL_OP_NO_TLSv1_2: number; + export var SSL_OP_PKCS1_CHECK_1: number; + export var SSL_OP_PKCS1_CHECK_2: number; + export var SSL_OP_SINGLE_DH_USE: number; + export var SSL_OP_SINGLE_ECDH_USE: number; + export var SSL_OP_SSLEAY_080_CLIENT_DH_BUG: number; + export var SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG: number; + export var SSL_OP_TLS_BLOCK_PADDING_BUG: number; + export var SSL_OP_TLS_D5_BUG: number; + export var SSL_OP_TLS_ROLLBACK_BUG: number; + export var ENGINE_METHOD_DSA: number; + export var ENGINE_METHOD_DH: number; + export var ENGINE_METHOD_RAND: number; + export var ENGINE_METHOD_ECDH: number; + export var ENGINE_METHOD_ECDSA: number; + export var ENGINE_METHOD_CIPHERS: number; + export var ENGINE_METHOD_DIGESTS: number; + export var ENGINE_METHOD_STORE: number; + export var ENGINE_METHOD_PKEY_METHS: number; + export var ENGINE_METHOD_PKEY_ASN1_METHS: number; + export var ENGINE_METHOD_ALL: number; + export var ENGINE_METHOD_NONE: number; + export var DH_CHECK_P_NOT_SAFE_PRIME: number; + export var DH_CHECK_P_NOT_PRIME: number; + export var DH_UNABLE_TO_CHECK_GENERATOR: number; + export var DH_NOT_SUITABLE_GENERATOR: number; + export var NPN_ENABLED: number; + export var RSA_PKCS1_PADDING: number; + export var RSA_SSLV23_PADDING: number; + export var RSA_NO_PADDING: number; + export var RSA_PKCS1_OAEP_PADDING: number; + export var RSA_X931_PADDING: number; + export var RSA_PKCS1_PSS_PADDING: number; + export var POINT_CONVERSION_COMPRESSED: number; + export var POINT_CONVERSION_UNCOMPRESSED: number; + export var POINT_CONVERSION_HYBRID: number; + export var O_RDONLY: number; + export var O_WRONLY: number; + export var O_RDWR: number; + export var S_IFMT: number; + export var S_IFREG: number; + export var S_IFDIR: number; + export var S_IFCHR: number; + export var S_IFLNK: number; + export var O_CREAT: number; + export var O_EXCL: number; + export var O_TRUNC: number; + export var O_APPEND: number; + export var F_OK: number; + export var R_OK: number; + export var W_OK: number; + export var X_OK: number; + export var UV_UDP_REUSEADDR: number; +} diff --git a/Node/src/Scripts/typings/request/request.d.ts b/Node/src/Scripts/typings/request/request.d.ts new file mode 100644 index 000000000..d3bbd5704 --- /dev/null +++ b/Node/src/Scripts/typings/request/request.d.ts @@ -0,0 +1,186 @@ +// Type definitions for request +// Project: https://github.com/mikeal/request +// Definitions by: Carlos Ballesteros Velasco , bonnici , Bart van der Schoor +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +// Imported from: https://github.com/soywiz/typescript-node-definitions/d.ts + +/// +/// + +declare module 'request' { + import stream = require('stream'); + import http = require('http'); + import FormData = require('form-data'); + import url = require('url'); + + export = RequestAPI; + + function RequestAPI(uri: string, options?: RequestAPI.Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): RequestAPI.Request; + function RequestAPI(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): RequestAPI.Request; + function RequestAPI(options: RequestAPI.Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): RequestAPI.Request; + + module RequestAPI { + export function defaults(options: Options): typeof RequestAPI; + + export function request(uri: string, options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function request(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function request(options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + + export function get(uri: string, options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function get(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function get(options: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + + export function post(uri: string, options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function post(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function post(options: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + + export function put(uri: string, options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function put(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function put(options: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + + export function head(uri: string, options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function head(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function head(options: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + + export function patch(uri: string, options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function patch(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function patch(options: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + + export function del(uri: string, options?: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function del(uri: string, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + export function del(options: Options, callback?: (error: any, response: http.IncomingMessage, body: any) => void): Request; + + export function forever(agentOptions: any, optionsArg: any): Request; + export function jar(): CookieJar; + export function cookie(str: string): Cookie; + + export var initParams: any; + + export interface Options { + url?: string; + uri?: string; + callback?: (error: any, response: http.IncomingMessage, body: any) => void; + jar?: any; // CookieJar + formData?: any; // Object + form?: any; // Object or string + auth?: AuthOptions; + oauth?: OAuthOptions; + aws?: AWSOptions; + hawk ?: HawkOptions; + qs?: any; + json?: any; + multipart?: RequestPart[]; + agentOptions?: any; + agentClass?: any; + forever?: any; + host?: string; + port?: number; + method?: string; + headers?: Headers; + body?: any; + followRedirect?: boolean|((response: http.IncomingMessage) => boolean); + followAllRedirects?: boolean; + maxRedirects?: number; + encoding?: string; + pool?: any; + timeout?: number; + proxy?: any; + strictSSL?: boolean; + gzip?: boolean; + } + + export interface RequestPart { + headers?: Headers; + body: any; + } + + export interface Request extends stream.Stream { + readable: boolean; + writable: boolean; + + getAgent(): http.Agent; + //start(): void; + //abort(): void; + pipeDest(dest: any): void; + setHeader(name: string, value: string, clobber?: boolean): Request; + setHeaders(headers: Headers): Request; + qs(q: Object, clobber?: boolean): Request; + form(): FormData.FormData; + form(form: any): Request; + multipart(multipart: RequestPart[]): Request; + json(val: any): Request; + aws(opts: AWSOptions, now?: boolean): Request; + auth(username: string, password: string, sendInmediately?: boolean, bearer?: string): Request; + oauth(oauth: OAuthOptions): Request; + jar(jar: CookieJar): Request; + + on(event: string, listener: Function): Request; + + write(buffer: Buffer, cb?: Function): boolean; + write(str: string, cb?: Function): boolean; + write(str: string, encoding: string, cb?: Function): boolean; + write(str: string, encoding?: string, fd?: string): boolean; + end(): void; + end(chunk: Buffer, cb?: Function): void; + end(chunk: string, cb?: Function): void; + end(chunk: string, encoding: string, cb?: Function): void; + pause(): void; + resume(): void; + abort(): void; + destroy(): void; + toJSON(): string; + } + + export interface Headers { + [key: string]: any; + } + + export interface AuthOptions { + user?: string; + username?: string; + pass?: string; + password?: string; + sendImmediately?: boolean; + bearer?: string; + } + + export interface OAuthOptions { + callback?: string; + consumer_key?: string; + consumer_secret?: string; + token?: string; + token_secret?: string; + verifier?: string; + } + + export interface HawkOptions { + credentials: any; + } + + export interface AWSOptions { + secret: string; + bucket?: string; + } + + export interface CookieJar { + setCookie(cookie: Cookie, uri: string|url.Url, options?: any): void + getCookieString(uri: string|url.Url): string + getCookies(uri: string|url.Url): Cookie[] + } + + export interface CookieValue { + name: string; + value: any; + httpOnly: boolean; + } + + export interface Cookie extends Array { + constructor(name: string, req: Request): void; + str: string; + expires: Date; + path: string; + toString(): string; + } + } +} diff --git a/Node/src/Scripts/typings/skype-botkit/skype-botkit.d.ts b/Node/src/Scripts/typings/skype-botkit/skype-botkit.d.ts new file mode 100644 index 000000000..39a90cbb8 --- /dev/null +++ b/Node/src/Scripts/typings/skype-botkit/skype-botkit.d.ts @@ -0,0 +1,144 @@ + +declare module 'skype-botkit' { + export function messagingHandler(botService: BotService): (req: any, res: any) => void; + function ensureHttps(redirect: any, errorStatus: any): (req: any, res: any, next: Function) => void; + function verifySkypeCert(options: any): (req: any, res: any, next: Function) => void; + + export interface IBotServiceConfiguration { + messaging?: { + username: string; + appId: string; + appSecret: string; + }; + requestTimeout?: number; + calling?: { + callbackUri: string; + }; + } + + export interface IMessage { + type: string; + from: string; + to: string; + content: any; + messageId: number; + contentType: string; + eventTime: number; + } + + export interface IAttachment { + type: string; + from: string; + to: string; + id: string; + attachmentName: string; + attachmentType: string; + views: IAttachmentViewInfo[]; + eventTime: string; + } + + export interface IAttachmentViewInfo { + } + + export interface IContactNotification { + type: string; + from: string; + to: string; + fromUserDisplayName: string; + action: string; + } + + export interface IHistoryDisclosed { + type: string; + from: string; + to: string; + historyDisclosed: boolean; + eventTime: number; + } + + export interface ITopicUpdated { + type: string; + from: string; + to: string; + topic: string; + eventTime: number; + } + + export interface IUserAdded { + type: string; + from: string; + to: string; + targets: string[]; + eventTime: number; + } + + export interface IUserRemoved { + type: string; + from: string; + to: string; + targets: string[]; + eventTime: number; + } + + export interface ICommandCallback { + (bot: Bot, request: IMessage): void; + } + + export interface ISendMessageCallback { + } + + export interface ICallNotificationHandler { + conversationResult: IConversationResult; + workflow: IWorkflow; + callback: IFinishEventHandling; + } + + export interface IConversationResult { + } + + export interface IWorkflow { + } + + export interface IFinishEventHandling { + error?: Error; + workflow?: IWorkflow; + } + + export interface IIncomingCallHandler { + conversation: IConversation; + workflow: IWorkflow; + callback: IFinishEventHandling; + } + + export interface IConversation { + } + + export interface IProcessCallCallback { + error?: Error; + responseMessage?: string; + } + + export class BotService { + constructor(configuration: IBotServiceConfiguration); + on(event: string, listener: Function): void; + onPersonalCommand(regex: RegExp, callback: ICommandCallback): void; + onGroupCommand(regex: RegExp, callback: ICommandCallback): void; + send(to: string, content: string, callback: ISendMessageCallback): void; + onAnswerCompleted(handler: ICallNotificationHandler): void; + onIncomingCall(handler: IIncomingCallHandler): void; + onCallStateChange(handler: Function): void; + onHangupCompleted(handler: Function): void; + onPlayPromptCompleted(handler: Function): void; + onRecognizeCompleted(handler: Function): void; + onRecordCompleted(handler: Function): void; + onRejectCompleted(handler: Function): void; + onWorkflowValidationCompleted(handler: Function): void; + processCall(content: any, callback: IProcessCallCallback): void; + processCallback(content: any, additionalData: any, callback: Function): void; + } + + export class Bot { + reply(content: string, callback: ISendMessageCallback): void; + send(to: string, content: string, callback: ISendMessageCallback): void; + } +} \ No newline at end of file diff --git a/Node/src/Scripts/typings/sprintf-js/sprintf-js.d.ts b/Node/src/Scripts/typings/sprintf-js/sprintf-js.d.ts new file mode 100644 index 000000000..89377b83d --- /dev/null +++ b/Node/src/Scripts/typings/sprintf-js/sprintf-js.d.ts @@ -0,0 +1,78 @@ +// Type definitions for sprintf-js +// Project: https://www.npmjs.com/package/sprintf-js +// Definitions by: Jason Swearingen +// Definitions: https://github.com/borisyankov/DefinitelyTyped + + +/** sprintf.js is a complete open source JavaScript sprintf implementation for the browser and node.js. + +Its prototype is simple: + +string sprintf(string format , [mixed arg1 [, mixed arg2 [ ,...]]]) +*/ +declare module sprintf_js { + /** sprintf.js is a complete open source JavaScript sprintf implementation for the browser and node.js. +Its prototype is simple: + string sprintf(string format , [mixed arg1 [, mixed arg2 [ ,...]]]) + +==Placeholders== + The placeholders in the format string are marked by % and are followed by one or more of these elements. see "fmt" arg for more docs on placeholders. + +==Argument swapping== +You can also swap the arguments. That is, the order of the placeholders doesn't have to match the order of the arguments. You can do that by simply indicating in the format string which arguments the placeholders refer to: + sprintf("%2$s %3$s a %1$s", "cracker", "Polly", "wants") + And, of course, you can repeat the placeholders without having to increase the number of arguments. + +==Named arguments== +Format strings may contain replacement fields rather than positional placeholders. Instead of referring to a certain argument, you can now refer to a certain key within an object. Replacement fields are surrounded by rounded parentheses - ( and ) - and begin with a keyword that refers to a key: + var user = {name: "Dolly"} + sprintf("Hello %(name)s", user) // Hello Dolly +Keywords in replacement fields can be optionally followed by any number of keywords or indexes: + var users = [{name: "Dolly"},{name: "Molly"},{name: "Polly"}] + sprintf("Hello %(users[0].name)s, %(users[1].name)s and %(users[2].name)s", {users: users}) // Hello Dolly, Molly and Polly +Note: mixing positional and named placeholders is not (yet) supported + +==Computed values== +You can pass in a function as a dynamic value and it will be invoked (with no arguments) in order to compute the value on-the-fly. + sprintf("Current timestamp: %d", Date.now) // Current timestamp: 1398005382890 + sprintf("Current date and time: %s", function() { return new Date().toString() }) + */ + export function sprintf( + /** The placeholders in the format string are marked by % and are followed by one or more of these elements, in this order: + +An optional number followed by a $ sign that selects which argument index to use for the value. If not specified, arguments will be placed in the same order as the placeholders in the input string. +An optional + sign that forces to preceed the result with a plus or minus sign on numeric values. By default, only the - sign is used on negative numbers. +An optional padding specifier that says what character to use for padding (if specified). Possible values are 0 or any other character precedeed by a ' (single quote). The default is to pad with spaces. +An optional - sign, that causes sprintf to left-align the result of this placeholder. The default is to right-align the result. +An optional number, that says how many characters the result should have. If the value to be returned is shorter than this number, the result will be padded. +An optional precision modifier, consisting of a . (dot) followed by a number, that says how many digits should be displayed for floating point numbers. When used on a string, it causes the result to be truncated. +A type specifier that can be any of: +% - yields a literal % character +b - yields an integer as a binary number +c - yields an integer as the character with that ASCII value +d or i - yields an integer as a signed decimal number +e - yields a float using scientific notation +u - yields an integer as an unsigned decimal number +f - yields a float as is +o - yields an integer as an octal number +s - yields a string as is +x - yields an integer as a hexadecimal number (lower-case) +X - yields an integer as a hexadecimal number (upper-case) + */ + fmt: string, + /** */ + ...args: any[] + ): string; + /** vsprintf is the same as sprintf except that it accepts an array of arguments, rather than a variable number of arguments: + + vsprintf("The first 4 letters of the english alphabet are: %s, %s, %s and %s", ["a", "b", "c", "d"]) +*/ + export function vsprintf(fmt: string, args: any[]): string; +} + +declare module "sprintf-js" { + export =sprintf_js; +} + +declare var sprintf: typeof sprintf_js.sprintf; +declare var vsprintf: typeof sprintf_js.vsprintf; diff --git a/Node/src/Session.ts b/Node/src/Session.ts new file mode 100644 index 000000000..50a3a4a38 --- /dev/null +++ b/Node/src/Session.ts @@ -0,0 +1,245 @@ +import collection = require('./dialogs/DialogCollection'); +import dialog = require('./dialogs/Dialog'); +import consts = require('./Consts'); +import sprintf = require('sprintf-js'); +import events = require('events'); + +export interface ISessionArgs { + dialogs: collection.DialogCollection; + dialogId: string; + dialogArgs?: any; + localizer?: ILocalizer; +} + +export class Session extends events.EventEmitter implements ISession { + private msgSent = false; + private _isReset = false; + + constructor(protected args: ISessionArgs) { + super(); + this.dialogs = args.dialogs; + } + + public dispatch(sessionState: ISessionState, message: IMessage): ISession { + var index = 0; + var handlers: { (session: Session, next: Function): void; }[]; + var session = this; + var next = () => { + var handler = index < handlers.length ? handlers[index] : null; + if (handler) { + index++; + handler(session, next); + } else { + this.routeMessage(); + } + }; + + // Dispatch message + this.sessionState = sessionState || { callstack: [], lastAccess: 0 }; + this.sessionState.lastAccess = new Date().getTime(); + this.message = message || { text: '' }; + if (!this.message.type) { + this.message.type = 'Message'; + } + handlers = this.dialogs.getMiddleware(); + next(); + return this; + } + + public dialogs: collection.DialogCollection; + public sessionState: ISessionState; + public message: IMessage; + public userData: any; + public dialogData: any; + + public error(err: Error): ISession { + err = err instanceof Error ? err : new Error(err.toString()); + console.error('Session Error: ' + err.message); + this.emit('error', err); + return this; + } + + public gettext(msgid: string, ...args: any[]): string { + return this.vgettext(msgid, args); + } + + public ngettext(msgid: string, msgid_plural: string, count: number): string { + var tmpl: string; + if (this.args.localizer && this.message) { + tmpl = this.args.localizer.ngettext(this.message.language || '', msgid, msgid_plural, count); + } else if (count == 1) { + tmpl = msgid; + } else { + tmpl = msgid_plural; + } + return sprintf.sprintf(tmpl, count); + } + + public send(): ISession; + public send(msg: string, ...args: any[]): ISession; + public send(msg: IMessage): ISession; + public send(msg?: any, ...args: any[]): ISession { + // Update dialog state + // - Deals with a situation where the user assigns a whole new object to dialogState. + var ss = this.sessionState; + if (ss.callstack.length > 0) { + ss.callstack[ss.callstack.length - 1].state = this.dialogData || {}; + } + + // Compose message + this.msgSent = true; + var message: IMessage = typeof msg == 'string' ? this.createMessage(msg, args) : msg; + this.emit('send', message); + return this; + } + + public messageSent(): boolean { + return this.msgSent; + } + + public beginDialog(id: string, args?: T): ISession { + var dialog = this.dialogs.getDialog(id); + if (!dialog) { + throw new Error('Dialog[' + id + '] not found.'); + } + var ss = this.sessionState; + var cur: IDialogState = { id: id, state: {} }; + ss.callstack.push(cur); + this.dialogData = cur.state; + dialog.begin(this, args); + return this; + } + + public endDialog(result?: any): ISession { + var ss = this.sessionState; + var r: dialog.IDialogResult = result || { resumed: dialog.ResumeReason.completed }; + r.childId = ss.callstack[ss.callstack.length - 1].id; + ss.callstack.pop(); + if (ss.callstack.length > 0) { + var cur = ss.callstack[ss.callstack.length - 1]; + var d = this.dialogs.getDialog(cur.id); + this.dialogData = cur.state; + d.dialogResumed(this, r); + } else if (!this.msgSent) { + this.send(); + this.emit('quit'); + } + return this; + } + + public compareConfidence(language: string, utterance: string, score: number, callback: (handled: boolean) => void): void { + var comparer = new SessionConfidenceComparor(this, language, utterance, score, callback); + comparer.next(); + } + + public reset(dialogId: string, dialogArgs?: any): ISession { + this._isReset = true; + this.sessionState.callstack = []; + this.beginDialog(dialogId, dialogArgs); + return this; + } + + public isReset(): boolean { + return this._isReset; + } + + public createMessage(text: string, args?: any[]): IMessage { + var message: IMessage = { + text: this.vgettext(text, args) + }; + if (this.message.language) { + message.language = this.message.language + } + return message; + } + + private routeMessage(): void { + try { + // Route message to dialog. + var ss = this.sessionState; + if (ss.callstack.length == 0) { + this.beginDialog(this.args.dialogId, this.args.dialogArgs); + } else if (this.validateCallstack()) { + var cur = ss.callstack[ss.callstack.length - 1]; + var dialog = this.dialogs.getDialog(cur.id); + this.dialogData = cur.state; + dialog.replyReceived(this); + } else { + console.error('Callstack is invalid, resetting session.'); + this.reset(this.args.dialogId, this.args.dialogArgs); + } + } catch (e) { + this.error(e); + } + } + + private vgettext(msgid: string, args?: any[]): string { + var tmpl: string; + if (this.args.localizer && this.message) { + tmpl = this.args.localizer.gettext(this.message.language || '', msgid); + } else { + tmpl = msgid; + } + return args && args.length > 0 ? sprintf.vsprintf(tmpl, args) : tmpl; + } + + /** Checks for any unsupported dialogs on the callstack. */ + private validateCallstack(): boolean { + var ss = this.sessionState; + for (var i = 0; i < ss.callstack.length; i++) { + var id = ss.callstack[i].id; + if (!this.dialogs.hasDialog(id)) { + return false; + } + } + return true; + } +} + +class SessionConfidenceComparor implements ISessionAction { + private index: number; + + constructor( + private session: Session, + private language: string, + private utterance: string, + private score: number, + private callback: (handled: boolean) => void) { + this.index = session.sessionState.callstack.length - 1; + this.userData = session.userData; + } + + public userData: any; + public dialogData: any; + + public next(): void { + this.index--; + if (this.index >= 0) { + this.getDialog().compareConfidence(this, this.language, this.utterance, this.score); + } else { + this.callback(false); + } + } + + public endDialog(result?: dialog.IDialogResult): void { + // End dialog up to current point in the stack. + this.session.sessionState.callstack.splice(this.index + 1); + this.getDialog().dialogResumed(this.session, result || { resumed: dialog.ResumeReason.childEnded }); + this.callback(true); + } + + public send(msg: string, ...args: any[]): void; + public send(msg: IMessage): void; + public send(msg: any, ...args: any[]): void { + // Send a message to the user. + args.splice(0, 0, [msg]); + Session.prototype.send.apply(this.session, args); + this.callback(true); + } + + private getDialog(): dialog.IDialog { + var cur = this.session.sessionState.callstack[this.index]; + this.dialogData = cur.state; + return this.session.dialogs.getDialog(cur.id); + } +} \ No newline at end of file diff --git a/Node/src/botbuilder.d.ts b/Node/src/botbuilder.d.ts new file mode 100644 index 000000000..d64aa2f23 --- /dev/null +++ b/Node/src/botbuilder.d.ts @@ -0,0 +1,1627 @@ +//============================================================================= +// +// INTERFACES +// +//============================================================================= + +/** A communication message recieved from a User or sent out of band from a Bot. */ +export interface IMessage { + /** What kind of message is this. */ + type?: string; + + /** Bot.Connector Id for the message (always assigned by transport.) */ + id?: string; + + /** Bot.Connector ConverationId id for the conversation (always assigned by transport.) */ + conversationId?: string; + + /** Timestamp of when the message was created. */ + created?: string; + + /** (if translated) The OriginalText of the message. */ + sourceText?: string; + + /** (if translated) The language of the OriginalText of the message. */ + sourceLanguage?: string; + + /** The language that the Text is expressed in. */ + language?: string; + + /** The text of the message (this will be target language depending on flags and destination.)*/ + text?: string; + + /** Array of attachments that can be anything. */ + attachments?: IAttachment[]; + + /** ChannelIdentity that sent the message. */ + from?: IChannelAccount; + + /** ChannelIdentity the message is sent to. */ + to?: IChannelAccount; + + /** Account to send replies to (for example, a group account that the message was part of.) */ + replyTo?: IChannelAccount; + + /** The message Id that this message is a reply to. */ + replyToMessageId?: string; + + /** List of ChannelAccounts in the conversation (NOTE: this is not for delivery means but for information.) */ + participants?: IChannelAccount[]; + + /** Total participants in the conversation. 2 means 1:1 message. */ + totalParticipants?: number; + + /** Array of mentions from the channel context. */ + mentions?: IMention[]; + + /** Place in user readable format: For example: "Starbucks, 140th Ave NE, Bellevue, WA" */ + place?: string; + + /** Channel Message Id. */ + channelMessageId?: string; + + /** Channel Conversation Id. */ + channelConversationId?: string; + + /** Channel specific properties. For example: Email channel may pass the Subject field as a property. */ + channelData?: any; + + /** Location information (see https://dev.onedrive.com/facets/location_facet.htm) */ + location?: ILocation; + + /** Hashtags for the message. */ + hashtags?: string[]; + + /** Required to modify messages when manually reading from a store. */ + eTag?: string; +} + +/** An attachment. */ +export interface IAttachment { + /** (REQUIRED) mimetype/Contenttype for the file, either ContentUrl or Content must be set depending on the mimetype. */ + contentType: string; + + /** Url to content. */ + contentUrl?: string; + + /** Content Payload (for example, lat/long for contentype="location". */ + content?: any; + + /** (OPTIONAL-CARD) FallbackText - used for downlevel clients, should be simple markup with links. */ + fallbackText?: string; + + /** (OPTIONAL-CARD) Title. */ + title?: string; + + /** (OPTIONAL-CARD) link to use for the title. */ + titleLink?: string; + + /** (OPTIONAL-CARD) The Text description the attachment. */ + text?: string; + + /** (OPTIONAL-CARD) Thumbnail associated with attachment. */ + thumbnailUrl?: string; +} + +/** Information needed to route a message. */ +export interface IChannelAccount { + /** Display friendly name of the user. */ + name?: string; + + /** Channel Id that the channelAccount is to be communicated with (Example: Twitter.) */ + channelId: string; + + /** Channel Address for the channelAccount (Example: @thermous.) */ + address: string; + + /** Id - global intercom id. */ + id?: string; + + /** Is this account id an bot? */ + isBot?: boolean; +} + +/** Mention information. */ +export interface IMention { + /** The mentioned user. */ + mentioned?: IChannelAccount; + + /** Sub Text which represents the mention (can be null or empty.) */ + text?: string; +} + +/** A GEO location. */ +export interface ILocation { + /** Altitude. */ + altitude?: number; + + /** Latitude for the user when the message was created. */ + latitude: number; + + /** Longitude for the user when the message was created. */ + longitude: number; +} + +/** Address info passed to Bot.beginDialog() calls. Specifies the address of the user to start a conversation with. */ +export interface IBeginDialogAddress { + /** Address of user to begin dialog with. */ + to: IChannelAccount; + + /** Optional "from" address for the bot. Required if IConnectorSession.defaultFrom hasn't been specified. */ + from?: IChannelAccount; + + /** Optional language to use when messaging the user. */ + language?: string; + + /** Optional text to initialize the dialogs message with. Useful for scenarios where the dialog being called is expecting to be replying to something the user said. */ + text?: string; +} + +/** Plugin for localizing messages sent to the user by a bot. */ +export interface ILocalizer { + /** + * Loads a localized string for the specified language. + * @param language Desired language of the string to return. + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + */ + gettext(language: string, msgid: string): string; + + /** + * Loads the plural form of a localized string for the specified language. + * @param language Desired language of the string to return. + * @param msgid Singular form of the string to use as a key in the localized string table. + * @param msgid_plural Plural form of the string to use as a key in the localized string table. + * @param count Count to use when determining whether the singular or plural form of the string should be used. + */ + ngettext(language: string, msgid: string, msgid_plural: string, count: number): string; +} + +/** + * Action object which exposes a partial set of session functionality and can be used to capture + * messages sent to a child dialog. + */ +interface ISessionAction { + /** Data for the user that's persisted across all conversations with the bot. */ + userData: any; + + /** Data that's only visible to the current dialog. */ + dialogData: any; + + /** Does not capture anything and proceedes to the next parent dialog in the callstack. */ + next(): void; + + /** + * Ends all of the dialogs children and returns control to the current dialog. This permanently + * captures back the users replies. + * @param result Optional results to pass to dialogResumed(). + */ + endDialog(result?: IDialogResult): void; + + /** + * Sends a simple text message to the user. The message will be localized using the sessions + * configured ILocalizer and if arguments are passed in the message will be formatted using + * sprintf-js. See https://github.com/alexei/sprintf.js for documentation. + * @param msg Text of the message to send. + * @param args Optional arguments used to format the final output string. See https://github.com/alexei/sprintf.js for documentation. + */ + send(msg: string, ...args: any[]): void; + /** + * Sends a message to the user. + * @param msg Message to send. + */ + send(msg: IMessage): void; +} + +/** Persisted session state used to track a conversations dialog stack. */ +export interface ISessionState { + /** Dialog stack for the current session. */ + callstack: IDialogState[]; + + /** Timestamp of when the session was last accessed. */ + lastAccess: number; +} + +/** An entry on the sessions dialog stack. */ +export interface IDialogState { + /** ID of the dialog. */ + id: string; + + /** Persisted state for the dialog. */ + state: any; +} + +/** + * Results returned by a child dialog to its parent via a call to session.endDialog(). + */ +export interface IDialogResult { + /** The reason why the current dialog is being resumed. */ + resumed: ResumeReason; + + /** ID of the child dialog thats ending. */ + childId?: string; + + /** If an error occured the child dialog can return the error to the parent. */ + error?: Error; + + /** The users response. */ + response?: T; +} + +/** Arguments passed to the system prompts beginDialog() call. */ +export interface IPromptArgs { + /** Type of prompt invoked. */ + promptType: PromptType; + + /** Initial message to send to user. */ + prompt: string; + + /** Optional retry prompt to send if the users response isn't understood. */ + retryPrompt?: string; + + /** Enum values for a choice prompt. */ + enumsValues?: string[]; + + /** Optional reference date when recognizing times. Date expressed in ticks using Date.getTime(). */ + refDate?: number; + + /** Maximum number of times to reprompt. */ + maxRetries?: number; +} + +/** Dialog result returned by a system prompt. */ +export interface IPromptResult extends IDialogResult { + /** Type of prompt completing. */ + promptType?: PromptType; +} + +/** Result returned from an IPromptRecognizer. */ +export interface IPromptRecognizerResult extends IPromptResult { + /** Returned from a prompt recognizer to indicate that a parent dialog handled (or captured) the utterance. */ + handled?: boolean; +} + +/** Strongly typed Text Prompt Result. */ +export interface IPromptTextResult extends IPromptResult { } + +/** Strongly typed Number Prompt Result. */ +export interface IPromptNumberResult extends IPromptResult { } + +/** Strongly typed Confirm Prompt Result. */ +export interface IPromptConfirmResult extends IPromptResult { } + +/** Strongly typed Choice Prompt Result. */ +export interface IPromptChoiceResult extends IPromptResult { } + +/** Strongly typed Time Prompt Result. */ +export interface IPromptTimeResult extends IPromptResult { } + +/** Plugin for recognizing prompt responses recieved by a user. */ +export interface IPromptRecognizer { + /** + * Attempts to match a users reponse to a given prompt. + * @param args Arguments passed to the recognizer including that language, text, and prompt choices. + * @param callback Function to invoke with the result of the recognition attempt. + */ + recognize(args: IPromptRecognizerArgs, callback: (result: IPromptRecognizerResult) => void): void; +} + +/** Arguments passed to the IPromptRecognizer.recognize() method.*/ +export interface IPromptRecognizerArgs { + /** Type of prompt being responded to. */ + promptType: PromptType; + + /** Text of the users response to the prompt. */ + text: string; + + /** Language of the text if known. */ + language?: string; + + /** For choice prompts the list of possible choices. */ + enumValues?: string[]; + + /** Optional reference date when recognizing times. */ + refDate?: number; + + /** + * Lets a prompt recognizer compare its confidence that it understood an utterance with the prompts parent. + * The callback will return true if the utterance was processed by the parent. This function lets a + * parent of the prompt handle utterances like "what can I say?" or "nevermind". + * @param language The langauge of the utterance taken from IMessage.language. + * @param utterance The users utterance taken from IMessage.text. + * @param score The dialogs confidence level on a scale of 0 to 1.0 that it understood the users intent. + * @param callback Function to invoke with the result of the comparison. If handled is true the dialog should not process the utterance. + */ + compareConfidence(language: string, utterance: string, score: number, callback: (handled: boolean) => void): void; +} + +/** Global configuration options for the Prompts dialog. */ +export interface IPromptsOptions { + /** Replaces the default recognizer (SimplePromptRecognizer) used to recognize prompt replies. */ + recognizer?: IPromptRecognizer +} + + +/** A recognized intent. */ +export interface IIntent { + /** Intent that was recognized. */ + intent: string; + + /** Confidence on a scale from 0.0 - 1.0 that the proper intent was recognized. */ + score: number; +} + +/** A recognized entity. */ +export interface IEntity { + /** Type of entity that was recognized. */ + type: string; + + /** Value of the recognized entity. */ + entity: string; + + /** Start position of entity within text utterance. */ + startIndex?: number; + + /** End position of entity within text utterance. */ + endIndex?: number; + + /** Confidence on a scale from 0.0 - 1.0 that the proper entity was recognized. */ + score?: number; +} + +/** Arguments passed to intent handlers when invoked. */ +export interface IIntentArgs { + /** Array of intents that were recognized. */ + intents: IIntent[]; + + /** Array of entities that were recognized. */ + entities: IEntity[]; +} + +/** Arguments passed to command handlers when invoked. */ +export interface ICommandArgs { + /** Compiled expression that was matched. */ + expression: RegExp; + + /** List of values that matched the expression. */ + matches: RegExpExecArray; +} + +/** Additional data parameters supported by the BotConnectorBot. */ +export interface IBotConnectorMessage extends IMessage { + /** Private Bot opaque data associated with a user (across all channels and conversations.) */ + botUserData?: any; + + /** Private Bot opaque state data associated with a conversation. */ + botConversationData?: any; + + /** Private Bot opaque state data associated with a user in a conversation. */ + botPerUserInConversationData?: any; +} + +/** Arguments padded to the constructor of a session. */ +export interface ISessionArgs { + /** Collection of dialogs to use for routing purposes. Typically this is just the bot. */ + dialogs: DialogCollection; + + /** Unique ID of the dialog to use when starting a new conversation with a user. */ + dialogId: string; + + /** Optional arguments to pass to the conversations initial dialog. */ + dialogArgs?: any; + + /** Optional localizer to use when localizing the bots responses. */ + localizer?: ILocalizer; +} + +/** Signature of error events fired from a session. */ +export interface ISessionErrorEvent { + (err: Error): void; +} + +/** Signature of message related events fired from a session. */ +export interface ISessionMessageEvent { + (message: IMessage): void; +} + +/** Signature of error events fired from bots. */ +export interface IBotErrorEvent { + (err: Error, message): void; +} + +/** Signature of message related events fired from BotConnetcorBot. */ +export interface IBotMessageEvent { + (message): void; +} + +/** result returnd from a call to EntityRecognizer.findBestMatch() or EntityRecognizer.findAllMatches(). */ +export interface IFindMatchResult { + /** Index of the matched value. */ + index: number; + + /** Value that was matched. */ + entity: string; + + /** Confidence score on a scale from 0.0 - 1.0 that an value matched the users utterance. */ + score: number; +} + +/** Storage abstraction used to persist session state & user data. */ +export interface IStorage { + /** + * Loads a value from storage. + * @param id ID of the value being loaded. + * @param callaback Function used to receive the loaded value. + */ + get(id: string, callback: (err: Error, data: any) => void): void; + + /** + * Saves a value to storage. + * @param id ID of the value to save. + * @param data Value to save. + * @param callback Optional function to invoke with the success or failure of the save. + */ + save(id: string, data: any, callback?: (err: Error) => void): void; +} + +/** Options used to configure the BotConnectorBot. */ +export interface IBotConnectorOptions { + /** URL of API endpoint to connect to for outgoing messages. */ + endpoint?: string; + + /** Bots application ID. */ + appId?: string; + + /** Bots application secret. */ + appSecret?: string; + + /** Bot developers subscription key. */ + subscriptionKey?: string; + + /** Default "from" address used in calls to ConnectorSession.beginDialog(). */ + defaultFrom?: IChannelAccount; + + /** Optional localizer used to localize the bots responses to the user. */ + localizer?: ILocalizer; + + /** Dialog to launch when a user initiates a new conversation with a bot. Default value is '/'. */ + defaultDialogId?: string; + + /** Optional arguments to pass to the initial dialog for a conversation. */ + defaultDialogArgs?: any; + + /** Sets a welcome message to send anytime a bot is added to a group conversation like a slack channel. */ + groupWelcomeMessage?: string; + + /** Sets a welcome message to send anytime a user is added to a group conversation the bots a member of like a slack channel. */ + userWelcomeMessage?: string; + + /** Sets a goodbye message to send anytime a user asks to end a conversation. */ + goodbyeMessage?: string; +} + +/** Options used to configure the SkypeBot. */ +export interface ISkypeBotOptions { + /** Storage system to use for persisting Session.userData values. By default the MemoryStorage is used. */ + userStore?: IStorage; + + /** Storage system to use for persisting Session.sessionState values. By default the MemoryStorage is used. */ + sessionStore?: IStorage; + + /** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */ + maxSessionAge?: number; + + /** Optional localizer used to localize the bots responses to the user. */ + localizer?: ILocalizer; + + /** Dialog to launch when a user initiates a new conversation with a bot. Default value is '/'. */ + defaultDialogId?: string; + + /** Optional arguments to pass to the initial dialog for a conversation. */ + defaultDialogArgs?: any; + + /** Sets the message to send when a user adds the bot as a contact. */ + contactAddedmessage?: string; + + /** Sets the message to send when the bot is added to a group chat. */ + botAddedMessage?: string; + + /** Sets the message to send when a bot is removed from a group chat. */ + botRemovedMessage?: string; + + /** Sets the message to send when a user joins a group chat monitored by the bot. */ + memberAddedMessage?: string; + + /** Sets the message to send when a user leaves a group chat monitored by the bot. */ + memberRemovedMessage?: string; +} + +/** Options used to configure the SlackBot. */ +export interface ISlackBotOptions { + /** Storage system to use for persisting Session.userData values. By default the MemoryStorage is used. */ + userStore?: IStorage; + + /** Storage system to use for persisting Session.sessionState values. By default the MemoryStorage is used. */ + sessionStore?: IStorage; + + /** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */ + maxSessionAge?: number; + + /** Optional localizer used to localize the bots responses to the user. */ + localizer?: ILocalizer; + + /** Dialog to launch when a user initiates a new conversation with a bot. Default value is '/'. */ + defaultDialogId?: string; + + /** Optional arguments to pass to the initial dialog for a conversation. */ + defaultDialogArgs?: any; +} + +/** Options used to configure the TextBot. */ +export interface ITextBotOptions { + /** Storage system to use for persisting Session.userData values. By default the MemoryStorage is used. */ + userStore?: IStorage; + + /** Storage system to use for persisting Session.sessionState values. By default the MemoryStorage is used. */ + sessionStore?: IStorage; + + /** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */ + maxSessionAge?: number; + + /** Optional localizer used to localize the bots responses to the user. */ + localizer?: ILocalizer; + + /** Dialog to launch when a user initiates a new conversation with a bot. Default value is '/'. */ + defaultDialogId?: string; + + /** Optional arguments to pass to the initial dialog for a conversation. */ + defaultDialogArgs?: any; +} + +/** Signature for function passed as a step to DialogAction.waterfall(). */ +export interface IDialogWaterfallStep { + (session: Session, result?: T, skip?: (count?: number, results?: IDialogResult) => void): any; + (session: Session, result?: IDialogResult, skip?: (count?: number, results?: IDialogResult) => void): any; +} + + +//============================================================================= +// +// ENUMS +// +//============================================================================= + +/** Reason codes for why a dialog was resumed. */ +export enum ResumeReason { + /** The user completed the child dialog and a result was returned. */ + completed, + + /** The user did not complete the child dialog for some reason. They may have exceeded maxRetries or canceled. */ + notCompleted, + + /** The user requested to cancel the current operation. */ + canceled, + + /** The user requested to return to the previous step in a dialog flow. */ + back, + + /** The user requested to skip the current step of a dialog flow. */ + forward, + + /** A captured utterance that resulted in a new child dialog being pushed onto the stack is completing. */ + captureCompleted, + + /** The child was forcibly ended by a parent. */ + childEnded +} + +/** + * Type of prompt invoked. + */ +export enum PromptType { + /** The user is prompted for a string of text. */ + text, + + /** The user is prompted to enter a number. */ + number, + + /** The user is prompted to confirm an action with a yes/no response. */ + confirm, + + /** The user is prompted to select from a list of choices. */ + choice, + + /** The user is prompted to enter a time. */ + time +} + +//============================================================================= +// +// CLASSES +// +//============================================================================= + +/** + * Manages the bots conversation with a user. + */ +export class Session { + /** + * Registers an event listener. Events: + * - error: An error occured. [ISessionErrorEvent] + * - send: A message should be sent to the user. [ISessionMessageEvent] + * - quit: The bot would like to end the conversation. [Function] + * @param event Name of the event. + * @param listener Function to invoke. + */ + on(event: string, listener: Function): void; + + /** Sessions configuration args. */ + protected args: ISessionArgs; + + /** Provides derived classes with access to the sessions localizer. */ + protected localizer: ILocalizer; + + /** ID of the dialog to start for any new conversations. */ + protected dialogId: string; + + /** Optional arguments to pass to the dialog when starting a new conversation. */ + protected dialogArgs: any; + + /** + * Creates an instance of the session. + * @param args Sessions configuration options. + */ + constructor(args: ISessionArgs); + + /** + * Dispatches a message for processing. The session will call thr appropriate middleware based + * on the messages type. Consumers can install middleware to either intercept or augment certain + * messages. + * @param sessionState The current session state. + * @param message The message to dispatch. + */ + dispatch(sessionState: ISessionState, message: IMessage): Session; + + /** The sessions collection of available dialogs & middleware for message routing purposes. */ + dialogs: DialogCollection; + + /** Sessions current state information. */ + ISessionState: ISessionState; + + /** The message recieved from the user. For bot originated messages this may only contain the "to" & "from" fields. */ + message: IMessage; + + /** Data for the user that's persisted across all conversations with the bot. */ + userData: any; + + /** Data that's only visible to the current dialog. */ + dialogData: any; + + /** + * Signals that an error occured. + * @param err Error that occured. + */ + error(err: Error): Session; + + /** + * Loads a localized string for the messages language. If arguments are passed the localized string + * will be treated as a template and formatted using sprintf-js. See https://github.com/alexei/sprintf.js for documentation. + * @param msgid String to use as a key in the localized string table. Typically this will just be the english version of the string. + * @param args Optional arguments used to format the final output string. See https://github.com/alexei/sprintf.js for documentation. + */ + gettext(msgid: string, ...args: any[]): string; + + /** + * Loads the plural form of a localized string for the messages language. The output string will be formatted to + * include the count by replacing %d in the string with the count. + * @param msgid Singular form of the string to use as a key in the localized string table. Use %d to specify where the count should go. + * @param msgid_plural Plural form of the string to use as a key in the localized string table. Use %d to specify where the count should go. + * @param count Count to use when determining whether the singular or plural form of the string should be used. + */ + ngettext(msgid: string, msgid_plural: string, count: number): string; + + /** + * Ends the session without sending a message. For user originated conversations the bot always + * needs to reply with something, even if it's an empty message. + */ + send(): Session; + /** + * Sends a simple text message to the user. The message will be localized using the sessions + * configured ILocalizer and if arguments are passed in the message will be formatted using + * sprintf-js. See https://github.com/alexei/sprintf.js for documentation. + * @param msg Text of the message to send. + * @param args Optional arguments used to format the final output string. See https://github.com/alexei/sprintf.js for documentation. + */ + send(msg: string, ...args: any[]): Session; + /** + * Sends a message to the user. + * @param msg Message to send. + */ + send(msg: IMessage): Session; + + /** + * Returns true if a message has been sent for this session. + */ + messageSent(): boolean; + + /** + * Passes control of the conversation to a new dialog. The current dialog will be suspended + * until the child dialog completes. Once the child ends the current dialog will receive a + * call to dialogResumed() where it can inspect any results returned from the child. + * @param id Unique ID of the dialog to start. + * @param args Optional arguments to pass to the dialogs begin() method. + */ + beginDialog(id: string, args?: T): Session + + /** + * Ends the current dialog. The dialogs parent will be resumed. + * @param result Optional results to pass to the parent dialog. + */ + endDialog(result?: IDialogResult): Session; + + /** + * Lets a dialog compare its confidence that it understood an utterance with it's parent. The + * callback will return true if the utterance was processed by the parent. This function lets a + * parent of the dialog handle messages not understood by the dialog. + * @param language The langauge of the utterance taken from IMessage.language. + * @param utterance The users utterance taken from IMessage.text. + * @param score The dialogs confidence level on a scale of 0 to 1.0 that it understood the users intent. + * @param callback Function to invoke with the result of the comparison. If handled is true the dialog should not process the utterance. + */ + compareConfidence(language: string, utterance: string, score: number, callback: (handled: boolean) => void): void; + + /** + * Clears the sessions callstack and restarts the conversation with the default (root) dialog. + * @param dialogId Unique ID of the dialog to start. + * @param dialogArgs Optional arguments to pass to the dialogs begin() method. + */ + reset(dialogId: string, dialogArgs?: T): Session; + + /** + * Returns true if the session has been reset. + */ + isReset(): boolean; + + /** + * Creates a reply message object with a formatted text string. The text will be localized and + * the languge of the original message will be copied over. Derived classes can use this to + * manually format a reply message. + * @param text Text or template string for the reply. This will be localized using session.gettext(). + * @param args Optional arguments used to format the message text when Text is a template. + */ + public createMessage(text: string, args?: any[]): IMessage; +} + +/** + * Base class for all dialogs. Dialogs are the core component of the BotBuilder + * framework. Bots use Dialogs to manage arbitrarily complex conversations with + * a user. + */ +export abstract class Dialog { + /** + * Called when a new dialog session is being started. + * @param session Session object for the current conversation. + * @param args Optional arguments passed to the dialog by its parent. + */ + begin(session: Session, args?: T): void; + + /** + * Called when a new reply message has been recieved from a user. + * + * Derived classes should implement this to process the message recieved from the user. + * @param session Session object for the current conversation. + */ + abstract replyReceived(session: Session): void; + + /** + * A child dialog has ended and the current one is being resumed. + * @param session Session object for the current conversation. + * @param result Result returned by the child dialog. + */ + dialogResumed(session: Session, result: IDialogResult): void; + + /** + * Called when a child dialog is wanting to compare its confidence for an utterance with its parent. + * This lets the parent determine if it can do a better job of responding to the utterance then + * the child can. This is useful for handling things like "quit" or "what can I say?". + * @param action Methods to lookup dialog state data and control what happens as a result of the + * comparison. Dialogs should at least call action.next() to signal a non-match. + * @param language The langauge of the utterance taken from IMessage.language. + * @param utterance The users utterance taken from IMessage.text. + * @param score The childs confidence level on a scale of 0 to 1.0 that it understood the users intent. + */ + compareConfidence(action: ISessionAction, language: string, utterance: string, score: number): void; +} + +/** + * A collection of dialogs & middleware that's used for routing purposes. Bots typically derive from this class. + */ +export class DialogCollection { + /** + * Adds a set of dialogs to the collection. + * @param dialogs Map of dialogs to add to the collection. The map should be keyed off the dialogs ID. + */ + add(dialogs: { [id: string]: Dialog; }): DialogCollection; + /** + * Adds a simple dialog to the collection thats based on the passed in closure. + * @param id Unique ID of the dialog. + * @param fn Closure to base dialog on. The closure will be called anytime a message is recieved + * from the user or when the dialog is being resumed. You can check for args.resumed to tell that + * your being resumed. + */ + add(id: string, fn: (session: Session, args?: any) => void): DialogCollection; + /** + * Adds a simple dialog to the collection thats based on the passed in waterfall. See DialogAction.waterfall() + * for details. + * @param id Unique ID of the dialog. + * @param waterfall Waterfall of steps to execute. + */ + add(id: string, waterfall: IDialogWaterfallStep[]): DialogCollection; + /** + * Adds a dialog to the collection. + * @param id Unique ID of the dialog. + * @param dialog Dialog to add. + */ + add(id: string, dialog: Dialog): DialogCollection; + + /** + * Returns a dialog given its ID. + * @param id ID of the dialog to lookup. + */ + getDialog(id: string): Dialog; + + /** + * Returns an array of middleware to invoke. + */ + getMiddleware(): { (session: Session, next: Function): void; }[]; + + /** + * Returns true if a dialog with a given ID exists within the collection. + * @param id ID of the dialog to lookup. + */ + hasDialog(id: string): boolean; + + /** + * Registers a piece of middleware that will be called for every message receieved. + */ + use(middleware: (session: Session, next: Function) => void): void; +} + +export class DialogAction { + /** + * Returns a closure that will send a simple text message to the user. + * @param msg Text of the message to send. + * @param args Optional arguments used to format the final output string. See https://github.com/alexei/sprintf.js for documentation. + */ + static send(msg: string, ...args: any[]): (session: Session) => void; + + /** + * Returns a closure that will passes control of the conversation to a new dialog. + * @param id Unique ID of the dialog to start. + * @param args Optional arguments to pass to the dialogs begin() method. + */ + static beginDialog(id: string, args?: T): (session: Session, args: T) => void; + + /** + * Returns a closure that will end the current dialog. + * @param result Optional results to pass to the parent dialog. + */ + static endDialog(result?: any): (session: Session) => void; + + /** + * Returns a closure that will prompt the user for information in an async waterfall like + * sequence. When the closure is first invoked it will execute the first function in the + * waterfall and the results of that prompt will be passed as input to the second function + * and the result of the second passed to the third and so on. + * + * Each step within the waterfall may optionally return a ResumeReson to influence the flow + * of the waterfall: + * - ResumeReason.forward: skips the next function in the waterfall. + * - ResumeReason.back: returns to the previous function in the waterfall. + * - ResumeReason.canceled: ends the waterfall all together. + * + * Calling other dialog like system prompts can influence the flow as well. If a child dialog + * returns either ResumeReason.forward or ResumeReason.back it will automatically be handled. + * If ResumeReason.canceled is returnd it will be handed to the step for processing which can + * then decide to cancel the action or not. + * @param steps Steps of a waterfall. + */ + static waterfall(steps: IDialogWaterfallStep[]): (session: Session, args: any) => void; +} + +/** + * Built in system prompts that can be called from any dialog. + */ +export class Prompts extends Dialog { + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + */ + replyReceived(session: Session): void; + + /** + * Updates global options for the Prompts dialog. + * @param options Options to set. + */ + static configure(options: IPromptsOptions): void; + + /** + * Captures from the user a raw string of text. + * @param session Session object for the current conversation. + * @param prompt Message to send to the user. + */ + static text(session: Session, prompt: string): void; + + /** + * Manually calls the current text prompt recognizer. Useful when needing to manually parse + * entity values or user utterances. + * @param language Language of the text if known. + * @param text Text to recognize. + * @param callback Function to call with the recognition results. + */ + static recognizeText(language: string, text: string, callback: (result: IPromptResult) => void): void; + + /** + * Prompts the user to enter a number. + * @param session Session object for the current conversation. + * @param prompt Initial message to send the user. + * @param retryPrompt Message to send should the user send the wrong value. + * @param maxRetries Maximum number of times to reprompt the user. Pass 0 to end the dialog immediately on failed input. + */ + static number(session: Session, prompt: string, retryPrompt?: string, maxRetries?: number): void; + + /** + * Manually calls the current number prompt recognizer. Useful when needing to manually parse + * entity values or user utterances. + * @param language Language of the text if known. + * @param text Text to recognize. + * @param callback Function to call with the recognition results. + */ + static recognizeNumber(language: string, text: string, callback: (result: IPromptResult) => void): void; + + /** + * Prompts the user to confirm an action with a yes/no response. + * @param session Session object for the current conversation. + * @param prompt Initial message to send the user. + * @param retryPrompt Message to send should the user send the wrong value. + * @param maxRetries Maximum number of times to reprompt the user. Pass 0 to end the dialog immediately on failed input. + */ + static confirm(session: Session, prompt: string, retryPrompt?: string, maxRetries?: number): void; + + /** + * Manually calls the current confirm prompt recognizer. Useful when needing to manually parse + * entity values or user utterances. + * @param language Language of the text if known. + * @param text Text to recognize. + * @param callback Function to call with the recognition results. + */ + static recognizeConfirm(language: string, text: string, callback: (result: IPromptResult) => void): void; + + /** + * Prompts the user to chose from a list of options. + * @param session Session object for the current conversation. + * @param prompt Initial message to send the user. + * @param enumValues List of valid values. + * @param retryPrompt Message to send should the user send the wrong value. + * @param maxRetries Maximum number of times to reprompt the user. Pass 0 to end the dialog immediately on failed input. + */ + static choice(session: Session, prompt: string, enumValues: string[], retryPrompt?: string, maxRetries?: number): void; + + /** + * Manually calls the current choice prompt recognizer. Useful when needing to manually parse + * entity values or user utterances. + * @param language Language of the text if known. + * @param text Text to recognize. + * @param enumValues List of possible choices. + * @param callback Function to call with the recognition results. + */ + static recognizeChoice(language: string, text: string, enumValues: string[], callback: (result: IPromptResult) => void): void; + + /** + * Prompts the user to enter a time. + * @param session Session object for the current conversation. + * @param prompt Initial message to send the user. + * @param refDate Optional date to use as reference date when recognizing the users response. + * @param retryPrompt Message to send should the user send the wrong value. + * @param maxRetries Maximum number of times to reprompt the user. Pass 0 to end the dialog immediately on failed input. + */ + static time(session: Session, prompt: string, refDate?: Date, retryPrompt?: string, maxRetries?: number): void; + + /** + * Manually calls the current time prompt recognizer. Useful when needing to manually parse + * entity values or user utterances. + * @param language Language of the text if known. + * @param text Text to recognize. + * @param refDate Optional date (can be null) to use as reference date when recognizing the users response. + * @param callback Function to call with the recognition results. + */ + static recognizeTime(language: string, text: string, refDate: Date, callback: (result: IPromptResult) => void): void; +} + +/** + * Implements a simple pattern based recognizer for parsing the system prompts. Derived classes can + * inherit from SimplePromptRecognizer and override the recognize() method to change the recognition + * of one or more prompt types. + */ +export class SimplePromptRecognizer implements IPromptRecognizer { + /** + * Attempts to match a users reponse to a given prompt. + * @param args Arguments passed to the recognizer including that language, text, and prompt choices. + * @param callback Function to invoke with the result of the recognition attempt. + */ + recognize(args: IPromptRecognizerArgs, callback: (result: IPromptResult) => void): void; +} + +/** + * Base class for an intent based dialog where the incoming message is sent to an intent recognizer + * to first identify any intents & entities. The top intent will be used to lookup a handler that + * will be used process the recieved message. +*/ +export abstract class IntentDialog extends Dialog { + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + */ + replyReceived(session: Session): void; + + /** + * Adds a IntentGroup to the dialog. + * @param group Group to add to dialog. + */ + addGroup(group: IntentGroup): IntentDialog; + + /** + * The handler will be called anytime the dialog is started for a session. + * @param fn Handler to invoke when the dialog is started. + */ + onBegin(fn: (session: Session, args: any, next: (handled: boolean) => void) => void): IntentDialog; + + /** + * Executes a block of code when the given intent is recognized. Use DialogAction.send() or + * DialogEnd.endDialog() to implement common actions. + * @param intent Intent to trigger on. + * @param fn Handler to invoke when the intent is triggered. The handler will be passed any + * recognized intents & entities via the args. The handler will also be invoked when a dialog + * started by the handler returns. Check for args.resumed to detect that you're being resumed. + */ + on(intent: string, fn: (session: Session, args?: IIntentArgs) => void): IntentDialog; + /** + * Executes a waterfall of steps when an intent is triggered. See DialogAction.waterfall() for + * details. + * @param intent Intent to trigger on. + * @param waterfall Waterfall steps to execute. + */ + on(intent: string, waterfall: IDialogWaterfallStep[]): IntentDialog; + /** + * Begins a dialog anytime the intent is triggered. + * @param intent Intent to trigger on. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs Optional args to pass to the dialog. These will be merged with the IIntentArgs + * generated by the dialog. + */ + on(intent: string, dialogId: string, dialogArgs?: any): IntentDialog; + + /** + * Executes a block of code when an unknown intent is recognized. Use DialogAction.send() or + * DialogAction.endDialog() to implement common actions. + * @param fn Handler to invoke when the intent is triggered. The handler will be passed any + * recognized intents & entities via the args. The handler will also be invoked when a dialog + * started by the handler returns. Check for args.resumed to detect that you're being resumed. + */ + onDefault(fn: (session: Session, args?: IIntentArgs) => void): IntentDialog; + /** + * Executes a waterfall of steps when an unknown intent is recognized. See DialogAction.waterfall() + * for details. + * @param waterfall Waterfall steps to execute. + */ + onDefault(waterfall: IDialogWaterfallStep[]): IntentDialog; + /** + * Begins a dialog when an unknown intent is recognized. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs Optional args to pass to the dialog. These will be merged with the IIntentArgs + * generated by the dialog. + */ + onDefault(dialogId: string, dialogArgs?: any): IntentDialog; + + /** Returns the minimum score needed for an intent to be triggered. */ + getThreshold(): number; + + /** + * Sets the minimum score needed for an intent to be triggered. The default value is 0.3. + * @param score Minimum score needed to trigger an intent. + */ + setThreshold(score: number): IntentDialog; + + /** + * Called to recognize the intents & entities for a received message. + * + * Derived classes should implement this method with the logic needed to perform the actual intent recognition. + * @param session Session object for the current conversation. + * @param callback Callback to invoke with the results of the intent recognition step. + */ + protected abstract recognizeIntents(session: Session, callback: (err: Error, intents?: IIntent[], entities?: IEntity[]) => void): void; +} + +/** + * Defines a related group of intent handlers. Primarily useful for dialogs with a large number of + * intents or for team development where you want seperate developers to more easily work on the same bot. + */ +export class IntentGroup { + /** + * Creates a new IntentGroup. The group needs to be labled with a unique ID for + * routing purposes. + * @param id Unique ID of the intent group. + */ + constructor(id: string); + + /** + * Returns the groups ID. + */ + getId(): string; + + /** + * Executes a block of code when the given intent is recognized. Use DialogAction.send() or + * DialogAction.endDialog() to implement common actions. + * @param intent Intent to trigger on. + * @param fn Handler to invoke when the intent is triggered. The handler will be passed any + * recognized intents & entities via the args. The handler will also be invoked when a dialog + * started by the handler returns. Check for args.resumed to detect that you're being resumed. + */ + on(intent: string, fn: (session: Session, args?: IIntentArgs) => void): IntentDialog; + /** + * Executes a waterfall of steps when an intent is triggered. See DialogAction.waterfall() for + * details. + * @param intent Intent to trigger on. + * @param waterfall Waterfall steps to execute. + */ + on(intent: string, waterfall: IDialogWaterfallStep[]): IntentDialog; + /** + * Begins a dialog anytime the intent is triggered. + * @param intent Intent to trigger on. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs Optional args to pass to the dialog. These will be merged with the IIntentArgs + * generated by the dialog. + */ + on(intent: string, dialogId: string, dialogArgs?: any): IntentDialog; +} + +/** + * Routes incoming messages to a Luis app hosted on http://luis.ai for intent recognition. + * Once a messages intent has been recognized it will reouted to a registered intent handler, along + * with any entities, for further processing. + */ +export class LuisDialog extends IntentDialog { + /** + * Creates a new instance of a LUIS dialog. + * @param serviceUri URI for LUIS App hosted on http://luis.ai. + */ + constructor(serviceUri: string); + + /** + * Performs the step of recognizing intents & entities when a message is recieved vy the dialog. Called by IntentDialog. + * @param session Session object for the current conversation. + * @param callback Callback to invoke with the results of the intent recognition step. + */ + protected recognizeIntents(session: Session, callback: (err: Error, intents?: IIntent[], entities?: IEntity[]) => void): void; +} + +/** + * Utility class used to parse & resolve common entities like datetimes received from LUIS. + */ +export class EntityRecognizer { + /** + * Searches for the first occurance of an specific entity type within a set. + * @param entities Set of entities to search over. + * @param type Type of entity to find. + */ + static findEntity(entities: IEntity[], type: string): IEntity; + + /** + * Finds all occurences of a specific entity type within a set. + * @param entities Set of entities to search over. + * @param type Type of entity to find. + */ + static findAllEntities(entities: IEntity[], type: string): IEntity[]; + + /** + * Parses and resolves a time from a user utterance. + * @param utterance Text utterance to parse. + * @returns A valid Date object if the user spoke a time otherwise null. + */ + static parseTime(utterance: string): Date; + + /** + * Resolves a time from a set of entities. + * @param entities Array of entities. + * @returns A valid Date object if datetime entities were found otherwise null. + */ + static parseTime(entities: IEntity[]): Date; + + /** + * Calculates a Date from a set of datetime entities. + * @param entities List of entities to extract date from. + * @param timezoneOffset Optional offset used to return dates adjusted to a specific timezone. If omitted the bots timezone will be used. + * @returns The successfully calculated Date or null if a date couldn't be determined. + */ + static resolveTime(entities: IEntity[], timezoneOffset?: number): Date; + + /** + * Recognizes a time from a users uetterance. + * @param utterance Text utterance to parse. + * @param refDate Optional reference date user to calculate the finale date. + * @returns An entity containing the resolved date if successfull or null if a date couldn't be determined. + */ + static recognizeTime(utterance: string, refDate?: Date): IEntity; + + /** + * Parses a number from a users utterance. + * @param utterance Text utterance to parse. + * @returns A valid number otherwise undefined. + */ + static parseNumber(utterance: string): number; + + /** + * Resolves a number from a set of entities. + * @param entities List of entities to extract number from. + * @returns A valid number otherwise undefined. + */ + static parseNumber(entities: IEntity[]): number; + + /** + * Parses a boolean from a users utterance. + * @param utterance Text utterance to parse. + * @returns A valid boolean otherwise undefined. + */ + static parseBoolean(utterance: string): boolean; + + /** + * Finds the best match for a users utterance in a list of values. + * @param choices Values to compare again the users utterance. + * @param utterance Text utterance to parse. + * @param threshold Optional minimum score needed for a match to be considered. The default value is 0.6. + */ + static findBestMatch(choices: string[], utterance: string, threshold?: number): IFindMatchResult; + + /** + * Finds all possible matches for a users utterance in a list of values. + * @param choices Values to compare again the users utterance. + * @param utterance Text utterance to parse. + * @param threshold Optional minimum score needed for a match to be considered. The default value is 0.6. + */ + static findAllMatches(choices: string[], utterance: string, threshold?: number): IFindMatchResult[]; +} + +/** + * Enables the building of a /command style bots. Regular expressions are matched against a users + * responses and used to trigger handlers when matched. + */ +export class CommandDialog extends Dialog { + /** + * Processes messages received from the user. Called by the dialog system. + * @param session Session object for the current conversation. + */ + replyReceived(session: Session): void; + + /** + * The handler will be called anytime the dialog is started for a session. + * @param fn Handler to invoke when the dialog is started. + */ + onBegin(fn: (session: Session, args: any, next: (handled: boolean) => void) => void): CommandDialog; + + /** + * Triggers the handler when the pattern is matched. Use DialogAction.send() or + * DialogAction.endDialog() to implement common actions. + * @param pattern A regular expression to match against. + * @param fn Handler to invoke when the pattern is matched. The handler will be passed the expression + * that was matched via the args. The handler will also be invoked when a dialog started by the + * handler returns. Check for args.resumed to detect that you're being resumed. + */ + matches(pattern: string, fn: (session: Session, args?: ICommandArgs) => void): CommandDialog; + /** + * Triggers the handler when the pattern is matched. Use DialogAction.send() or + * DialogAction.endDialog() to implement common actions. + * @param patterns Array of regular expressions to match against. + * @param fn Handler to invoke when the pattern is matched. The handler will be passed the expression + * that was matched via the args. The handler will also be invoked when a dialog started by the + * handler returns. Check for args.resumed to detect that you're being resumed. + */ + matches(patterns: string[], fn: (session: Session, args?: ICommandArgs) => void): CommandDialog; + /** + * Executes a waterfall of steps when the pattern is matched. See DialogAction.waterfall() for + * details. + * @param patterns Array of regular expressions to match against. + * @param waterfall Waterfall steps to execute. + */ + matches(pattern: string, waterfall: IDialogWaterfallStep[]): IntentDialog; + /** + * Executes a waterfall of steps when the pattern is matched. See DialogAction.waterfall() for + * details. + * @param patterns A regular expression to match against. + * @param waterfall Waterfall steps to execute. + */ + matches(patterns: string[], waterfall: IDialogWaterfallStep[]): IntentDialog; + /** + * Begins a dialog when the pattern is matched. + * @param pattern A regular expression to match against. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs Optional args to pass to the dialog. These will be merged with the ICommandArgs + * generated by the dialog. + */ + matches(pattern: string, dialogId: string, dialogArgs?: any): CommandDialog; + /** + * Begins a dialog when one of the specified patterns is matched. + * @param patterns Array of regular expressions to match against. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs Optional args to pass to the dialog. These will be merged with the ICommandArgs + * generated by the dialog. + */ + matches(patterns: string[], dialogId: string, dialogArgs?: any): CommandDialog; + + /** + * Executes a block of code when an unknown pattern is received. + * @param fn Handler to invoke when the pattern is matched. The handler will be passed the expression + * that was matched via the args. The handler will also be invoked when a dialog started by the + * handler returns. Check for args.resumed to detect that you're being resumed. + */ + onDefault(fn: (session: Session, args?: ICommandArgs) => void): CommandDialog; + /** + * Executes a waterfall of steps when an unknown pattern is received. See DialogAction.waterfall() + * for details. + * @param waterfall Waterfall steps to execute. + */ + onDefault(waterfall: IDialogWaterfallStep[]): IntentDialog; + /** + * Begins a dialog when an unknown pattern is received. + * @param dialogId ID of the dialog to begin. + * @param dialogArgs Optional args to pass to the dialog. These will be merged with the ICommandArgs + * generated by the dialog. + */ + onDefault(dialogId: string, dialogArgs?: any): CommandDialog; +} + +/** Default in memory storage implementation for storing user & session state data. */ +export class MemmoryStorage implements IStorage { + /** + * Loads a value from storage. + * @param id ID of the value being loaded. + * @param callaback Function used to receive the loaded value. + */ + get(id: string, callback: (err: Error, data: any) => void): void; + + /** + * Saves a value to storage. + * @param id ID of the value to save. + * @param data Value to save. + * @param callback Optional function to invoke with the success or failure of the save. + */ + save(id: string, data: any, callback?: (err: Error) => void): void; + + /** + * Deletes a value from storage. + * @param id ID of the value to delete. + */ + delete(id: string): void; +} + +/** + * Connects your bots dialogs to the Bot Framework. + */ +export class BotConnectorBot extends DialogCollection { + /** + * @param options Optional configuration settings for the bot. + */ + constructor(options?: ISkypeBotOptions); + + /** + * Registers an event listener to get notified of bot related events. + * The message to passed to events will be of type IBotConnectorMessage. Events: + * - error: An error occured. [IBotErrorEvent] + * - reply: A reply to an existing message was sent. [IBotMessageEvent] + * - send: A new message was sent to start a new conversation. [IBotMessageEvent] + * - quit: The bot has elected to ended the current conversation. [IBotMessageEvent] + * - Message: A user message was received. [IBotMessageEvent] + * - DeleteUserData: The user has requested to have their data deleted. [IBotMessageEvent] + * - BotAddedToConversation: The bot has been added to a conversation. [IBotMessageEvent] + * - BotRemovedFromConversation: The bot has been removed from a conversation. [IBotMessageEvent] + * - UserAddedToConversation: A user has joined a conversation monitored by the bot. [IBotMessageEvent] + * - UserRemovedFromConversation: A user has left a conversation monitored by the bot. [IBotMessageEvent] + * - EndOfConversation: The user has elected to end the current conversation. [IBotMessageEvent] + * @param event Name of event to listen for. + * @param listener Function to invoke. + */ + on(event: string, listener: Function): void; + + /** + * Updates the bots configuration settings. + * @param options Configuration options to set. + */ + configure(options: IBotConnectorOptions): void; + + /** + * Returns a piece of Express or Restify compliant middleware that will ensure only messages from the Bot Framework are processed. + * NOTE: Also requires configuring of the bots appId and appSecret. + * @param options Optional configuration options to pass in. + * @example + *

+     * var bot = new builder.BotConnectorBot();
+     * app.use(bot.verifyBotFramework({ appId: 'your appId', appSecret: 'your appSecret' }));
+     * 
+ */ + verifyBotFramework(options?: IBotConnectorOptions): (req, res, next) => void; + + /** + * Returns a piece of Express or Restify compliant middleware that will route incoming messages to the bot. + * NOTE: The middleware should be mounted to route that receives an HTTPS POST. + * @param options Optional configuration options to pass in. + * @example + *

+     * var bot = new builder.BotConnectorBot();
+     * app.post('/v1/messages', bot.listen());
+     * 
+ */ + listen(options?: IBotConnectorOptions): (req, res) => void; + + /** + * Starts a new conversation with a user. + * @param address Address of the user to begin the conversation with. + * @param dialogId Unique ID of the bots dialog to begin the conversation with. + * @param dialogArgs Optional arguments to pass to the dialog. + */ + beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void; +} + +/** + * Adds additional properties for working with Bot Framework bots. + */ +export class BotConnectorSession extends Session { + /** Group data that's persisted across all members of a conversation. */ + conversationData: any; + + /** User data that's persisted on a per conversation basis. */ + perUserConversationData: any; +} + +/** + * Connects your bots dialogs to Skype. + */ +export class SkypeBot extends DialogCollection { + /** + * @param botService Skype BotService() instance. + * @param options Optional configuration settings for the bot. + */ + constructor(botService: any, options?: ISkypeBotOptions); + + /** + * Registers an event listener to get notified of bot related events. + * The message to passed to events will be a skype message. Events: + * - error: An error occured. [IBotErrorEvent] + * - reply: A reply to an existing message was sent. [IBotMessageEvent] + * - send: A new message was sent to start a new conversation. [IBotMessageEvent] + * - quit: The bot has elected to ended the current conversation. [IBotMessageEvent] + * - message: This event is emitted for every received message. [IBotMessageEvent] + * - personalMessage: This event is emitted for every 1:1 chat message. [IBotMessageEvent] + * - groupMessage: This event is emitted for every group chat message. [IBotMessageEvent] + * - threadBotAdded: This event is emitted when the bot is added to group chat. [IBotMessageEvent] + * - threadAddMember: This event is emitted when some users are added to group chat. [IBotMessageEvent] + * - threadBotRemoved: This event is emitted when the bot is removed from group chat. [IBotMessageEvent] + * - threadRemoveMember: This event is emitted when some users are removed from group chat. [IBotMessageEvent] + * - contactAdded: This event is emitted when users add the bot as a buddy. [IBotMessageEvent] + * - threadTopicUpdated: This event is emitted when the topic of a group chat is updated. [IBotMessageEvent] + * - threadHistoryDisclosedUpdate: This event is emitted when the "history disclosed" option of a group chat is changed. [IBotMessageEvent] + * @param event Name of event to listen for. + * @param listener Function to invoke. + */ + on(event: string, listener: Function): void; + + /** + * Updates the bots configuration settings. + * @param options Configuration options to set. + */ + configure(options: ISkypeBotOptions): void; + + /** + * Starts a new conversation with a user. + * @param address Address of the user to begin the conversation with. + * @param dialogId Unique ID of the bots dialog to begin the conversation with. + * @param dialogArgs Optional arguments to pass to the dialog. + */ + beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void; +} + +/** + * Connects your bots dialogs to Slack via BotKit. See http://howdy.ai/botkit/ for details. + */ +export class SlackBot extends DialogCollection { + /** + * Creates a new instance of the Slack bot using BotKit. + * @param controller Controller created from a call to Botkit.slackbot(). + * @param bot Optional bot created from a call to controller.spawn(). If you don't pass this in + * you won't be able to initiate outgoing conversations using SlackBot.beginDialog(). + * @param options Optional configuration settings for the bot. + */ + constructor(controller: any, bot?: any, options?: ISlackBotOptions); + + /** + * Registers an event listener to get notified of bot related events. + * The message to passed to events will a slack message. Events: + * - error: An error occured. [IBotErrorEvent] + * - reply: A reply to an existing message was sent. [IBotMessageEvent] + * - send: A new message was sent to start a new conversation. [IBotMessageEvent] + * - quit: The bot has elected to ended the current conversation. [IBotMessageEvent] + * - message: This event is emitted for every received message. [IBotMessageEvent] + * @param event Name of event to listen for. + * @param listener Function to invoke. + */ + on(event: string, listener: Function): void; + + /** + * Updates the bots configuration settings. + * @param options Configuration options to set. + */ + configure(options: ISlackBotOptions): void; + + /** + * Starts a new conversation with a user. + * @param address Address of the user to begin the conversation with. + * @param dialogId Unique ID of the bots dialog to begin the conversation with. + * @param dialogArgs Optional arguments to pass to the dialog. + */ + beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void; +} + +/** + * Generic TextBot which lets you drive your bots dialogs from either the console or + * pratically any other bot platform. + * + * There are primarily 2 ways of using the TextBot either purely event driven (preferred) or in + * mixed mode where you pass a callback to the TextBot.processMessage() method and also listen + * for events. In this second mode the first reply or error will be returned via the callback and + * any additonal replies will be delivered as events. Should you decide to ignore the events just + * be aware that any additional replies from the bot will be lost. + */ +export class TextBot extends DialogCollection { + /** + * @param options Optional configuration settings for the bot. + */ + constructor(options?: ITextBotOptions); + + /** + * Registers an event listener to get notified of bot related events. + * The message to passed to events will be an IMessage. Events: + * - error: An error occured. [IBotErrorEvent] + * - reply: A reply to an existing message was sent. [IBotMessageEvent] + * - send: A new message was sent to start a new conversation. [IBotMessageEvent] + * - quit: The bot has elected to ended the current conversation. [IBotMessageEvent] + * - message: This event is emitted for every received message. [IBotMessageEvent] + * @param event Name of event to listen for. + * @param listener Function to invoke. + */ + on(event: string, listener: Function): void; + + /** + * Updates the bots configuration settings. + * @param options Configuration options to set. + */ + configure(options: ITextBotOptions): void; + + /** + * Starts a new conversation with a user. + * @param address Address of the user to begin the conversation with. + * @param dialogId Unique ID of the bots dialog to begin the conversation with. + * @param dialogArgs Optional arguments to pass to the dialog. + */ + beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void; + + /** + * Processes a message received from the user. + * @param message Message to process. + * @param callback Optional callback used to return bots initial reply or an error. If ommited all + * replies and errors will be returned as events. + */ + processMessage(message: IMessage, callback?: (err: Error, reply: IMessage) => void): void; + + /** + * Begins monitoring console input from stdin. The bot can quit using a call to endDialog() to exit the + * app. + */ + listenStdin(): void; +} diff --git a/Node/src/botbuilder.ts b/Node/src/botbuilder.ts new file mode 100644 index 000000000..b2723b2ce --- /dev/null +++ b/Node/src/botbuilder.ts @@ -0,0 +1,39 @@ +import consts = require('./consts'); +import utils = require('./utils'); +import session = require('./Session'); +import dialog = require('./dialogs/Dialog'); +import collection = require('./dialogs/DialogCollection'); +import prompts = require('./dialogs/Prompts'); +import intent = require('./dialogs/IntentDialog'); +import luis = require('./dialogs/LuisDialog'); +import command = require('./dialogs/CommandDialog'); +import simple = require('./dialogs/SimpleDialog'); +import entities = require('./dialogs/EntityRecognizer'); +import storage = require('./storage/Storage'); +import connector = require('./bots/BotConnectorBot'); +import skype = require('./bots/SkypeBot'); +import slack = require('./bots/SlackBot'); +import text = require('./bots/TextBot'); + +declare var exports: any; + +exports.Session = session.Session; +exports.Dialog = dialog.Dialog; +exports.ResumeReason = dialog.ResumeReason; +exports.DialogCollection = collection.DialogCollection; +exports.PromptType = prompts.PromptType; +exports.ListStyle = prompts.ListStyle; +exports.Prompts = prompts.Prompts; +exports.SimplePromptRecognizer = prompts.SimplePromptRecognizer; +exports.IntentDialog = intent.IntentDialog; +exports.IntentGroup = intent.IntentGroup; +exports.LuisDialog = luis.LuisDialog; +exports.CommandDialog = command.CommandDialog; +exports.EntityRecognizer = entities.EntityRecognizer; +exports.MemoryStorage = storage.MemoryStorage; +exports.BotConnectorBot = connector.BotConnectorBot; +exports.BotConnectorSession = connector.BotConnectorSession; +exports.SkypeBot = skype.SkypeBot; +exports.SlackBot = slack.SlackBot; +exports.TextBot = text.TextBot; + diff --git a/Node/src/bots/BotConnectorBot.ts b/Node/src/bots/BotConnectorBot.ts new file mode 100644 index 000000000..d1cbd5177 --- /dev/null +++ b/Node/src/bots/BotConnectorBot.ts @@ -0,0 +1,281 @@ +import collection = require('../dialogs/DialogCollection'); +import session = require('../Session'); +import consts = require('../consts'); +import utils = require('../utils'); +import request = require('request'); + +export interface IBotConnectorOptions { + endpoint?: string; + appId?: string; + appSecret?: string; + subscriptionKey?: string; + defaultFrom?: IChannelAccount; + localizer?: ILocalizer; + defaultDialogId?: string; + defaultDialogArgs?: any; + groupWelcomeMessage?: string; + userWelcomeMessage?: string; + goodbyeMessage?: string; +} + +export interface IBotConnectorMessage extends IMessage { + botUserData?: any; + botConversationData?: any; + botPerUserInConversationData?: any; +} + +/** Express or Restify Request object. */ +interface IRequest { + body: any; + headers: { + [name: string]: string; + }; + on(event: string, ...args: any[]): void; +} + +/** Express or Restify Response object. */ +interface IResponse { + send(status: number, body?: any): void; + send(body: any): void; +} + +/** Express or Restify Middleware Function. */ +interface IMiddleware { + (req: IRequest, res: IResponse, next?: Function): void; +} + +export class BotConnectorBot extends collection.DialogCollection { + private options: IBotConnectorOptions = { + endpoint: process.env['endpoint'] || 'https://intercomppe.azure-api.net', + appId: process.env['appId'] || '', + appSecret: process.env['appSecret'] || '', + subscriptionKey: process.env['subscriptionKey'] || '', + defaultDialogId: '/' + } + + constructor(options?: IBotConnectorOptions) { + super(); + this.configure(options); + } + + public configure(options: IBotConnectorOptions) { + if (options) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + (this.options)[key] = (options)[key]; + } + } + } + } + + public verifyBotFramework(options?: IBotConnectorOptions): IMiddleware { + this.configure(options); + return (req: IRequest, res: IResponse, next: Function) => { + // Check authorization + var authorized: boolean; + if (this.options.appId && this.options.appSecret) { + if (req.headers && req.headers.hasOwnProperty('authorization')) { + var tmp = req.headers['authorization'].split(' '); + var buf = new Buffer(tmp[1], 'base64'); + var cred = buf.toString().split(':'); + if (cred[0] == this.options.appId && cred[1] == this.options.appSecret) { + authorized = true; + } else { + authorized = false; + } + } else { + authorized = false; + } + } else { + authorized = true; + } + if (authorized) { + next(); + } else { + res.send(403); + } + }; + } + + public listen(options?: IBotConnectorOptions): IMiddleware { + this.configure(options); + return (req: IRequest, res: IResponse) => { + if (req.body) { + this.processMessage(req.body, this.options.defaultDialogId, this.options.defaultDialogArgs, res); + } else { + var requestData = ''; + req.on('data', (chunk: string) => { + requestData += chunk + }); + req.on('end', () => { + try { + var msg = JSON.parse(requestData); + this.processMessage(msg, this.options.defaultDialogId, this.options.defaultDialogArgs, res); + } catch (e) { + this.emit('error', new Error('Invalid Bot Framework Message')); + res.send(400); + } + }); + } + }; + } + + public beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void { + // Fixup address fields + var msg: IBotConnectorMessage = address; + msg.type = 'Message'; + if (!msg.from) { + msg.from = this.options.defaultFrom; + } + + // Validate args + if (!msg.to || !msg.from) { + throw new Error('Invalid address passed to BotConnectorBot.beginDialog().'); + } + if (!this.hasDialog(dialogId)) { + throw new Error('Invalid dialog passed to BotConnectorBot.beginDialog().'); + } + + // Dispatch message + this.processMessage(msg, dialogId, dialogArgs); + } + + private processMessage(message: IBotConnectorMessage, dialogId: string, dialogArgs: any, res?: IResponse) { + try { + // Validate message + if (!message || !message.type) { + this.emit('error', new Error('Invalid Bot Framework Message')); + return res.send(400); + } + + // Dispatch messages + this.emit(message.type, message); + if (message.type == 'Message') { + // Initialize session + var ses = new BotConnectorSession({ + localizer: this.options.localizer, + dialogs: this, + dialogId: dialogId, + dialogArgs: dialogArgs + }); + ses.on('send', (message: IMessage) => { + // Compose reply + var reply: IBotConnectorMessage = message || {}; + reply.botUserData = utils.clone(ses.userData); + reply.botConversationData = utils.clone(ses.conversationData); + reply.botPerUserInConversationData = utils.clone(ses.perUserInConversationData); + reply.botPerUserInConversationData[consts.Data.SessionState] = ses.sessionState; + if (reply.text && !reply.language) { + reply.language = ses.message.language; + } + + // Send message + if (res) { + this.emit('reply', reply); + res.send(200, reply); + res = null; + } else if (ses.message.conversationId) { + // Post an additional reply + reply.from = ses.message.to; + reply.to = ses.message.replyTo ? ses.message.replyTo : ses.message.from; + reply.replyToMessageId = ses.message.id; + reply.conversationId = ses.message.conversationId; + reply.channelConversationId = ses.message.channelConversationId; + reply.channelMessageId = ses.message.channelMessageId; + reply.participants = ses.message.participants; + reply.totalParticipants = ses.message.totalParticipants; + this.emit('reply', reply); + this.post('/bot/v1.0/messages', reply, (err) => { + this.emit('error', err); + }); + } else { + // Start a new conversation + reply.from = ses.message.from; + reply.to = ses.message.to; + this.emit('send', reply); + this.post('/bot/v1.0/messages', reply, (err) => { + this.emit('error', err); + }); + } + }); + ses.on('error', (err: Error) => { + this.emit('error', err, ses.message); + if (res) { + res.send(500); + } + }); + ses.on('quit', () => { + this.emit('quit', ses.message); + }); + + // Unpack data fields + var sessionState: ISessionState; + if (message.botUserData) { + ses.userData = message.botUserData; + delete message.botUserData; + } else { + ses.userData = {}; + } + if (message.botConversationData) { + ses.conversationData = message.botConversationData; + delete message.botConversationData; + } else { + ses.conversationData = {}; + } + if (message.botPerUserInConversationData) { + if (message.botPerUserInConversationData.hasOwnProperty(consts.Data.SessionState)) { + sessionState = message.botPerUserInConversationData[consts.Data.SessionState]; + delete message.botPerUserInConversationData[consts.Data.SessionState]; + } + ses.perUserInConversationData = message.botPerUserInConversationData; + delete message.botPerUserInConversationData; + } else { + ses.perUserInConversationData = {}; + } + + // Dispatch message + ses.dispatch(sessionState, message); + } else if (res) { + var msg: string; + switch (message.type) { + case "botAddedToConversation": + msg = this.options.groupWelcomeMessage; + break; + case "userAddedToConversation": + msg = this.options.userWelcomeMessage; + break; + case "endOfConversation": + msg = this.options.goodbyeMessage; + break; + } + res.send(msg ? { type: message.type, text: msg } : {}); + } + } catch (e) { + this.emit('error', e instanceof Error ? e : new Error(e.toString())); + res.send(500); + } + } + + protected post(path: string, body: any, callback?: (error: any) => void): void { + var settings = this.options; + var options: request.Options = { + url: settings.endpoint + path, + body: body + }; + if (settings.appId && settings.appSecret) { + options.auth = { + username: 'Bot_' + settings.appId, + password: 'Bot_' + settings.appSecret + }; + options.headers = { + 'Ocp-Apim-Subscription-Key': settings.subscriptionKey || settings.appSecret + }; + } + request.post(options, callback); + } +} + +export class BotConnectorSession extends session.Session { + public conversationData: any; + public perUserInConversationData: any; +} diff --git a/Node/src/bots/SkypeBot.ts b/Node/src/bots/SkypeBot.ts new file mode 100644 index 000000000..ca7c87a95 --- /dev/null +++ b/Node/src/bots/SkypeBot.ts @@ -0,0 +1,226 @@ +import collection = require('../dialogs/DialogCollection'); +import session = require('../Session'); +import storage = require('../storage/Storage'); +import botkit = require('skype-botkit'); + +export interface ISkypeBotOptions { + userStore?: storage.IStorage; + sessionStore?: storage.IStorage; + maxSessionAge?: number; + localizer?: ILocalizer; + defaultDialogId?: string; + defaultDialogArgs?: any; + contactAddedmessage?: string; + botAddedMessage?: string; + botRemovedMessage?: string; + memberAddedMessage?: string; + memberRemovedMessage?: string; +} + +export class SkypeBot extends collection.DialogCollection { + private options: ISkypeBotOptions = { + maxSessionAge: 14400000, // <-- default max session age of 4 hours + defaultDialogId: '/' + }; + + constructor(protected botService: botkit.BotService, options?: ISkypeBotOptions) { + super(); + this.configure(options); + var events = 'message|personalMessage|groupMessage|attachment|threadBotAdded|threadAddMember|threadBotRemoved|threadRemoveMember|contactAdded|threadTopicUpdated|threadHistoryDisclosedUpdate'.split('|'); + events.forEach((value) => { + botService.on(value, (bot: botkit.Bot, data: botkit.IMessage) => { + this.emit(value, bot, data); + }); + }); + } + + public configure(options: ISkypeBotOptions) { + if (options) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + (this.options)[key] = (options)[key]; + } + } + } + } + + public beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void { + // Validate args + if (!address.to) { + throw new Error('Invalid address passed to SkypeBot.beginDialog().'); + } + if (!this.hasDialog(dialogId)) { + throw new Error('Invalid dialog passed to SkypeBot.beginDialog().'); + } + + // Dispatch message + this.dispatchMessage(null, this.toSkypeMessage(address), dialogId, dialogArgs); + } + + private handleEvent(event: string, bot: botkit.Bot, data: any) { + var onError = (err: Error) => { + this.emit('error', err, data); + }; + + switch (event) { + case 'personalMessage': + this.dispatchMessage(bot, data, this.options.defaultDialogId, this.options.defaultDialogArgs); + break; + case 'threadBotAdded': + if (this.options.botAddedMessage) { + bot.reply(this.options.botAddedMessage, onError); + } + break; + case 'threadAddMember': + if (this.options.memberAddedMessage) { + bot.reply(this.options.memberAddedMessage, onError); + } + break; + case 'threadBotRemoved': + if (this.options.botRemovedMessage) { + bot.reply(this.options.botRemovedMessage, onError); + } + break; + case 'threadRemoveMember': + if (this.options.memberRemovedMessage) { + bot.reply(this.options.memberRemovedMessage, onError); + } + break; + case 'contactAdded': + if (this.options.contactAddedmessage) { + bot.reply(this.options.contactAddedmessage, onError); + } + break; + } + } + + private dispatchMessage(bot: botkit.Bot, data: botkit.IMessage, dialogId: string, dialogArgs: any) { + var onError = (err: Error) => { + this.emit('error', err, data); + }; + + // Initialize session + var ses = new session.Session({ + localizer: this.options.localizer, + dialogs: this, + dialogId: dialogId, + dialogArgs: dialogArgs + }); + ses.on('send', (reply: IMessage) => { + this.saveData(msg.from.address, ses.userData, ses.sessionState, () => { + // If we have no message text then we're just saving state. + if (reply && reply.text) { + // Do we have a bot? + var skypeReply = this.toSkypeMessage(reply); + if (bot) { + // Check for a different TO address + if (skypeReply.to && skypeReply.to != data.from) { + this.emit('send', skypeReply); + bot.send(skypeReply.to, skypeReply.content, onError); + } else { + this.emit('reply', skypeReply); + bot.reply(skypeReply.content, onError); + } + } else { + skypeReply.to = ses.message.to.address; + this.emit('send', skypeReply); + this.botService.send(skypeReply.to, skypeReply.content, onError); + } + } + }); + }); + ses.on('error', (err: Error) => { + this.emit('error', err, data); + }); + ses.on('quit', () => { + this.emit('quit', data); + }); + + // Load data and dispatch message + var msg = this.fromSkypeMessage(data); + this.getData(msg.from.address, (userData, sessionState) => { + ses.userData = userData || {}; + ses.dispatch(sessionState, msg); + }); + } + + private getData(userId: string, callback: (userData: any, sessionState: ISessionState) => void) { + // Ensure stores specified + if (!this.options.userStore) { + this.options.userStore = new storage.MemoryStorage(); + } + if (!this.options.sessionStore) { + this.options.sessionStore = new storage.MemoryStorage(); + } + + // Load data + var ops = 2; + var userData: any, sessionState: ISessionState; + this.options.userStore.get(userId, (err, data) => { + if (!err) { + userData = data; + if (--ops == 0) { + callback(userData, sessionState); + } + } else { + this.emit('error', err); + } + }); + this.options.sessionStore.get(userId, (err: Error, data: ISessionState) => { + if (!err) { + if (data && (new Date().getTime() - data.lastAccess) < this.options.maxSessionAge) { + sessionState = data; + } + if (--ops == 0) { + callback(userData, sessionState); + } + } else { + this.emit('error', err); + } + }); + } + + private saveData(userId: string, userData: any, sessionState: ISessionState, callback: Function) { + var ops = 2; + function onComplete(err: Error) { + if (!err) { + if (--ops == 0) { + callback(null); + } + } else { + callback(err); + } + } + this.options.userStore.save(userId, userData, onComplete); + this.options.sessionStore.save(userId, sessionState, onComplete); + } + + private fromSkypeMessage(msg: botkit.IMessage): IMessage { + return { + type: msg.type, + id: msg.messageId.toString(), + from: { + channelId: 'skype', + address: msg.from + }, + to: { + channelId: 'skype', + address: msg.to + }, + text: msg.content, + channelData: msg + }; + } + + private toSkypeMessage(msg: IMessage): botkit.IMessage { + return { + type: msg.type, + from: msg.from ? msg.from.address : '', + to: msg.to ? msg.to.address : '', + content: msg.text, + messageId: msg.id ? Number(msg.id) : Number.NaN, + contentType: "RichText", + eventTime: msg.channelData ? msg.channelData.eventTime : new Date().getTime() + }; + } +} diff --git a/Node/src/bots/SlackBot.ts b/Node/src/bots/SlackBot.ts new file mode 100644 index 000000000..c93350dcb --- /dev/null +++ b/Node/src/bots/SlackBot.ts @@ -0,0 +1,201 @@ +import collection = require('../dialogs/DialogCollection'); +import session = require('../Session'); +import storage = require('../storage/Storage'); + +interface ISlackMessage { + type: string; + subtype?: string; + channel: string; + user: string; + text: string; + attachments?: ISlackAttachment[]; + ts: string; +} + +interface ISlackAttachment { +} + +declare class BotKitController { + on(event: string, listener: Function): void; +} + +declare class Bot { + reply(message: ISlackMessage, text: string): void; + say(message: ISlackMessage, cb: (err: Error) => void): void; +} + +export interface ISlackBotOptions { + userStore?: storage.IStorage; + sessionStore?: storage.IStorage; + maxSessionAge?: number; + localizer?: ILocalizer; + defaultDialogId?: string; + defaultDialogArgs?: any; +} + +export class SlackBot extends collection.DialogCollection { + protected options: ISlackBotOptions = { + maxSessionAge: 14400000, // <-- default max session age of 4 hours + defaultDialogId: '/' + }; + + constructor(protected controller: BotKitController, protected bot?: Bot, options?: ISlackBotOptions) { + super(); + this.configure(options); + controller.on('direct_message', (bot: Bot, msg: ISlackMessage) => { + this.emit('message', msg); + this.dispatchMessage(bot, msg, this.options.defaultDialogId, this.options.defaultDialogArgs); + }); + } + + public configure(options: ISlackBotOptions): this { + if (options) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + (this.options)[key] = (options)[key]; + } + } + } + return this; + } + + public beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void { + // Validate args + if (!this.bot) { + throw new Error('Spawned BotKit Bot not passed to constructor.'); + } + if (!address.to) { + throw new Error('Invalid address passed to SlackBot.beginDialog().'); + } + if (!this.hasDialog(dialogId)) { + throw new Error('Invalid dialog passed to SlackBot.beginDialog().'); + } + + // Dispatch message + this.dispatchMessage(null, this.toSlackMessage(address), dialogId, dialogArgs); + } + + private dispatchMessage(bot: Bot, data: ISlackMessage, dialogId: string, dialogArgs: any) { + var onError = (err: Error) => { + this.emit('error', err, data); + }; + + // Initialize session + var ses = new session.Session({ + localizer: this.options.localizer, + dialogs: this, + dialogId: this.options.defaultDialogId, + dialogArgs: this.options.defaultDialogArgs + }); + ses.on('send', (reply: IMessage) => { + this.saveData(message.from.address, ses.userData, ses.sessionState, () => { + // If we have no message text then we're just saving state. + if (reply && reply.text) { + var slackReply = this.toSlackMessage(reply); + if (bot) { + // Check for a different TO address + if (slackReply.user && slackReply.user != data.user) { + this.emit('send', slackReply); + bot.say(slackReply, onError); + } else { + this.emit('reply', slackReply); + bot.reply(data, slackReply.text); + } + } else { + slackReply.user = ses.message.to.address; + this.emit('send', slackReply); + this.bot.say(slackReply, onError); + } + } + }); + }); + ses.on('error', (err: Error) => { + this.emit('error', err, data); + }); + ses.on('quit', () => { + this.emit('quit', data); + }); + + // Dispatch message + var message = this.fromSlackMessage(data); + this.getData(message.from.address, (err, userData, sessionState) => { + ses.userData = userData || {}; + ses.dispatch(sessionState, message); + }); + } + + private getData(userId: string, callback: (err: Error, userData: any, sessionState: ISessionState) => void) { + // Ensure stores specified + if (!this.options.userStore) { + this.options.userStore = new storage.MemoryStorage(); + } + if (!this.options.sessionStore) { + this.options.sessionStore = new storage.MemoryStorage(); + } + + // Load data + var ops = 2; + var userData: any, sessionState: ISessionState; + this.options.userStore.get(userId, (err, data) => { + if (!err) { + userData = data; + if (--ops == 0) { + callback(null, userData, sessionState); + } + } else { + callback(err, null, null); + } + }); + this.options.sessionStore.get(userId, (err: Error, data: ISessionState) => { + if (!err) { + if (data && (new Date().getTime() - data.lastAccess) < this.options.maxSessionAge) { + sessionState = data; + } + if (--ops == 0) { + callback(null, userData, sessionState); + } + } else { + callback(err, null, null); + } + }); + } + + private saveData(userId: string, userData: any, sessionState: ISessionState, callback: (err: Error) => void) { + var ops = 2; + function onComplete(err: Error) { + if (!err) { + if (--ops == 0) { + callback(null); + } + } else { + callback(err); + } + } + this.options.userStore.save(userId, userData, onComplete); + this.options.sessionStore.save(userId, sessionState, onComplete); + } + + private fromSlackMessage(msg: ISlackMessage): IMessage { + return { + type: msg.type, + id: msg.ts, + text: msg.text, + from: { + channelId: 'slack', + address: msg.user + }, + channelConversationId: msg.channel, + channelData: msg + }; + } + + private toSlackMessage(msg: IMessage): ISlackMessage { + return { + type: msg.type, + ts: msg.id, + text: msg.text, + user: msg.to ? msg.to.address : msg.from.address, + channel: msg.channelConversationId + }; + } +} \ No newline at end of file diff --git a/Node/src/bots/TextBot.ts b/Node/src/bots/TextBot.ts new file mode 100644 index 000000000..824cf41af --- /dev/null +++ b/Node/src/bots/TextBot.ts @@ -0,0 +1,170 @@ +import collection = require('../dialogs/DialogCollection'); +import session = require('../Session'); +import storage = require('../storage/Storage'); +import uuid = require('node-uuid'); +import readline = require('readline'); + +export interface ITextBotOptions { + userStore?: storage.IStorage; + sessionStore?: storage.IStorage; + maxSessionAge?: number; + localizer?: ILocalizer; + defaultDialogId?: string; + defaultDialogArgs?: any; +} + +export class TextBot extends collection.DialogCollection { + private options: ITextBotOptions = { + maxSessionAge: 14400000, // <-- default max session age of 4 hours + defaultDialogId: '/' + }; + + constructor(options?: ITextBotOptions) { + super(); + this.configure(options); + } + + public configure(options: ITextBotOptions) { + if (options) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + (this.options)[key] = (options)[key]; + } + } + } + } + + public beginDialog(address: IBeginDialogAddress, dialogId: string, dialogArgs?: any): void { + // Validate args + if (!this.hasDialog(dialogId)) { + throw new Error('Invalid dialog passed to SkypeBot.beginDialog().'); + } + + // Dispatch message + this.dispatchMessage(address || {}, null, dialogId, dialogArgs); + } + + public processMessage(message: IMessage, callback?: (err: Error, reply: IMessage) => void): void { + this.emit('message', message); + if (!message.id) { + message.id = uuid.v1(); + } + if (!message.from) { + message.from = { channelId: 'text', address: 'user' }; + } + this.dispatchMessage(message, callback, this.options.defaultDialogId, this.options.defaultDialogArgs); + } + + public listenStdin(): void { + function onMessage(message: IMessage) { + console.log(message.text); + } + this.on('reply', onMessage); + this.on('send', onMessage); + this.on('quit', () => { + rl.close(); + process.exit(); + }); + var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); + rl.on('line', (line: string) => { + this.processMessage({ text: line || '' }); + }); + } + + private dispatchMessage(message: IMessage, callback: (err: Error, reply: IMessage) => void, dialogId: string, dialogArgs: any): void { + // Initialize session + var ses = new session.Session({ + localizer: this.options.localizer, + dialogs: this, + dialogId: dialogId, + dialogArgs: dialogArgs + }); + ses.on('send', (reply: IMessage) => { + this.saveData(message.from.address, ses.userData, ses.sessionState, () => { + // If we have no message text then we're just saving state. + if (reply && reply.text) { + if (callback) { + callback(null, reply); + callback = null; + } else if (message.id || message.conversationId) { + reply.from = message.to; + reply.to = reply.replyTo || reply.to; + reply.conversationId = message.conversationId; + reply.language = message.language; + this.emit('reply', reply); + } else { + this.emit('send', reply); + } + } + }); + }); + ses.on('error', (err: Error) => { + if (callback) { + callback(err, null); + callback = null; + } else { + this.emit('error', err, message); + } + }); + ses.on('quit', () => { + this.emit('quit', message); + }); + + // Dispatch message + this.getData(message.from.address, (err, userData, sessionState) => { + ses.userData = userData || {}; + ses.dispatch(sessionState, message); + }); + } + + private getData(userId: string, callback: (err: Error, userData: any, sessionState: ISessionState) => void) { + // Ensure stores specified + if (!this.options.userStore) { + this.options.userStore = new storage.MemoryStorage(); + } + if (!this.options.sessionStore) { + this.options.sessionStore = new storage.MemoryStorage(); + } + + // Load data + var ops = 2; + var userData: any, sessionState: ISessionState; + this.options.userStore.get(userId, (err, data) => { + if (!err) { + userData = data; + if (--ops == 0) { + callback(null, userData, sessionState); + } + } else { + callback(err, null, null); + } + }); + this.options.sessionStore.get(userId, (err: Error, data: ISessionState) => { + if (!err) { + if (data && (new Date().getTime() - data.lastAccess) < this.options.maxSessionAge) { + sessionState = data; + } + if (--ops == 0) { + callback(null, userData, sessionState); + } + } else { + callback(err, null, null); + } + }); + } + + private saveData(userId: string, userData: any, sessionState: ISessionState, callback: (err: Error) => void) { + var ops = 2; + function onComplete(err: Error) { + if (!err) { + if (--ops == 0) { + callback(null); + } + } else { + callback(err); + } + } + this.options.userStore.save(userId, userData, onComplete); + this.options.sessionStore.save(userId, sessionState, onComplete); + } +} \ No newline at end of file diff --git a/Node/src/consts.ts b/Node/src/consts.ts new file mode 100644 index 000000000..25fc24644 --- /dev/null +++ b/Node/src/consts.ts @@ -0,0 +1,19 @@ +export var Data = { + SessionState: 'BotBuilder.Data.SessionState', + Handler: 'BotBuilder.Data.Handler', + Group: 'BotBuilder.Data.Group', + Intent: 'BotBuilder.Data.Intent', + WaterfallStep: 'BotBuilder.Data.WaterfallStep' +}; + +export var DialogId = { + Prompts: 'BotBuilder.Dialogs.Prompts' +}; + +export var Id = { + DefaultGroup: 'BotBuilder.Id.DefaultGroup' +}; + +export var Intents = { + Default: 'BotBuilder.Intents.Default' +}; \ No newline at end of file diff --git a/Node/src/dialogs/CommandDialog.ts b/Node/src/dialogs/CommandDialog.ts new file mode 100644 index 000000000..fd281e414 --- /dev/null +++ b/Node/src/dialogs/CommandDialog.ts @@ -0,0 +1,138 @@ +import session = require('../Session'); +import dialog = require('./Dialog'); +import actions = require('./DialogAction'); +import consts = require('../consts'); +import util = require('util'); + +export interface ICommandArgs { + expression: RegExp; + matches: RegExpExecArray; +} + +interface ICommandDialogEntry { + expressions?: RegExp[]; + fn: IDialogHandler; +} + +export class CommandDialog extends dialog.Dialog { + private beginDialog: (session: ISession, args: any, next: (handled: boolean) => void) => void; + private commands: ICommandDialogEntry[] = []; + private default: ICommandDialogEntry; + + public begin(session: ISession, args: T): void { + if (this.beginDialog) { + this.beginDialog(session, args, (handled) => { + if (!handled) { + super.begin(session, args); + } + }); + } else { + super.begin(session, args); + } + } + + public replyReceived(session: ISession): void { + var score = 0.0; + var expression: RegExp; + var matches: RegExpExecArray; + var text = session.message.text; + var matched: ICommandDialogEntry; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + for (var j = 0; j < cmd.expressions.length; j++) { + expression = cmd.expressions[j]; + if (expression.test(text)) { + matched = cmd; + session.dialogData[consts.Data.Handler] = i; + matches = expression.exec(text); + if (matches) { + var length = 0; + matches.forEach((value) => { + length += value.length; + }); + score = length / text.length; + } + break; + } + } + if (matched) break; + } + if (!matched && this.default) { + expression = null; + matched = this.default; + session.dialogData[consts.Data.Handler] = -1; + } + if (matched) { + session.compareConfidence(session.message.language, text, score, (handled) => { + if (!handled) { + matched.fn(session, { expression: expression, matches: matches }); + } + }); + } else { + session.send(); + } + } + + public dialogResumed(session: ISession, result: dialog.IDialogResult): void { + var cur: ICommandDialogEntry; + var handler = session.dialogData[consts.Data.Handler]; + if (handler >= 0 && handler < this.commands.length) { + cur = this.commands[handler]; + } else if (this.default) { + cur = this.default; + } + if (cur) { + cur.fn(session, result); + } else { + super.dialogResumed(session, result); + } + } + + public onBegin(handler: IBeginDialogHandler): this { + this.beginDialog = handler; + return this; + } + + public matches(pattern: string, fn: IDialogHandler): this; + public matches(patterns: string[], fn: IDialogHandler): this; + public matches(pattern: string, waterfall: actions.IDialogWaterfallStep[]): this; + public matches(patterns: string[], waterfall: actions.IDialogWaterfallStep[]): this; + public matches(pattern: string, dialogId: string, dialogArgs?: any): this; + public matches(patterns: string[], dialogId: string, dialogArgs?: any): this; + public matches(patterns: any, dialogId: any, dialogArgs?: any): this { + // Fix args + var fn: IDialogHandler; + var patterns = !util.isArray(patterns) ? [patterns] : patterns; + if (Array.isArray(dialogId)) { + fn = actions.DialogAction.waterfall(dialogId); + } else if (typeof dialogId == 'string') { + fn = actions.DialogAction.beginDialog(dialogId, dialogArgs); + } else { + fn = dialogId; + } + + // Save compiled expressions + var expressions: RegExp[] = []; + for (var i = 0; i < (patterns).length; i++) { + expressions.push(new RegExp((patterns)[i], 'i')); + } + this.commands.push({ expressions: expressions, fn: fn }); + return this; + } + + public onDefault(fn: IDialogHandler): this; + public onDefault(waterfall: actions.IDialogWaterfallStep[]): this; + public onDefault(dialogId: string, dialogArgs?: any): this; + public onDefault(dialogId: any, dialogArgs?: any): this { + var fn: IDialogHandler; + if (Array.isArray(dialogId)) { + fn = actions.DialogAction.waterfall(dialogId); + } else if (typeof dialogId == 'string') { + fn = actions.DialogAction.beginDialog(dialogId, dialogArgs); + } else { + fn = dialogId; + } + this.default = { fn: fn }; + return this; + } +} \ No newline at end of file diff --git a/Node/src/dialogs/Dialog.ts b/Node/src/dialogs/Dialog.ts new file mode 100644 index 000000000..fd11c9948 --- /dev/null +++ b/Node/src/dialogs/Dialog.ts @@ -0,0 +1,38 @@ + +export interface IDialog { + begin(session: ISession, args?: T): void; + replyReceived(session: ISession): void; + dialogResumed(session: ISession, result: any): void; + compareConfidence(action: ISessionAction, language: string, utterance: string, score: number): void; +} + +export enum ResumeReason { completed, notCompleted, canceled, back, forward, captureCompleted, childEnded } + +export interface IDialogResult { + resumed: ResumeReason; + childId?: string; + error?: Error; + response?: T; +} + +export abstract class Dialog implements IDialog { + public begin(session: ISession, args?: T): void { + this.replyReceived(session); + } + + abstract replyReceived(session: ISession): void; + + public dialogResumed(session: ISession, result: IDialogResult): void { + if (!session.messageSent()) { + if (result.error) { + session.error(result.error); + } else { + session.send(); + } + } + } + + public compareConfidence(action: ISessionAction, language: string, utterance: string, score: number): void { + action.next(); + } +} diff --git a/Node/src/dialogs/DialogAction.ts b/Node/src/dialogs/DialogAction.ts new file mode 100644 index 000000000..813b82fb0 --- /dev/null +++ b/Node/src/dialogs/DialogAction.ts @@ -0,0 +1,95 @@ +import session = require('../Session'); +import dialog = require('./Dialog'); +import consts = require('../consts'); + +export interface IDialogWaterfallStep { + (session: ISession, result?: any, skip?: IDialogWaterfallCursor): void; +} + +export interface IDialogWaterfallCursor { + (count?: number, results?: dialog.IDialogResult): void; +} + +export class DialogAction { + static send(msg: string, ...args: any[]): IDialogHandler { + args.splice(0, 0, msg); + return function sendAction(s: ISession) { + // Send a message to the user. + session.Session.prototype.send.apply(s, args); + }; + } + + static beginDialog(id: string, args?: T): IDialogHandler { + return function beginDialogAction(s: ISession, a: any) { + // Ignore calls where we're being resumed. + if (!a || !a.hasOwnProperty('resumed')) { + // Merge args + if (args) { + a = a || {}; + for (var key in args) { + if (args.hasOwnProperty(key)) { + a[key] = (args)[key]; + } + } + } + + // Begin a new dialog + s.beginDialog(id, a); + } + }; + } + + static endDialog(result?: any): IDialogHandler { + return function endDialogAction(s: ISession) { + // End dialog + s.endDialog(result); + }; + } + + static waterfall(steps: IDialogWaterfallStep[]): IDialogHandler { + return function waterfallAction(s: ISession, r: dialog.IDialogResult) { + var skip = (count = 1, result?: dialog.IDialogResult) => { + result = result || { resumed: dialog.ResumeReason.forward }; + s.dialogData[consts.Data.WaterfallStep] += count; + waterfallAction(s, result); + }; + + try { + // Check for continuation of waterfall + if (r && r.hasOwnProperty('resumed')) { + // Adjust step based on users utterance + var step = s.dialogData[consts.Data.WaterfallStep]; + switch (r.resumed) { + case dialog.ResumeReason.back: + step -= 1; + break; + case dialog.ResumeReason.forward: + step += 2; + break; + default: + step++; + } + + // Handle result + if (step >= 0 && step < steps.length) { + s.dialogData[consts.Data.WaterfallStep] = step; + steps[step](s, r, skip); + } else { + delete s.dialogData[consts.Data.WaterfallStep]; + s.send(); + } + } else if (steps && steps.length > 0) { + // Start waterfall + s.dialogData[consts.Data.WaterfallStep] = 0; + steps[0](s, r, skip); + } else { + delete s.dialogData[consts.Data.WaterfallStep]; + s.send(); + } + } catch (e) { + delete s.dialogData[consts.Data.WaterfallStep]; + s.endDialog({ resumed: dialog.ResumeReason.notCompleted, error: e instanceof Error ? e : new Error(e.toString()) }); + } + }; + } +} \ No newline at end of file diff --git a/Node/src/dialogs/DialogCollection.ts b/Node/src/dialogs/DialogCollection.ts new file mode 100644 index 000000000..8304fbf79 --- /dev/null +++ b/Node/src/dialogs/DialogCollection.ts @@ -0,0 +1,63 @@ +import dialog = require('./Dialog'); +import actions = require('./DialogAction'); +import simpleDialog = require('./SimpleDialog'); +import events = require('events'); + +interface IDialogMap { + [id: string]: dialog.IDialog; +} + +export class DialogCollection extends events.EventEmitter { + private middleware: { (session: ISession, next: Function): void; }[] = []; + private dialogs: IDialogMap = {}; + + constructor() { + super(); + } + + public add(dialogs: { [id: string]: dialog.IDialog; }): DialogCollection; + public add(id: string, fn: IDialogHandler): DialogCollection; + public add(id: string, waterfall: actions.IDialogWaterfallStep[]): DialogCollection; + public add(id: string, dialog: dialog.IDialog): DialogCollection; + public add(id: any, dialog?: any): DialogCollection { + // Fixup params + var dialogs: { [id: string]: dialog.IDialog; }; + if (typeof id == 'string') { + if (Array.isArray(dialog)) { + dialog = new simpleDialog.SimpleDialog(actions.DialogAction.waterfall(dialog)); + } else if (typeof dialog == 'function') { + dialog = new simpleDialog.SimpleDialog(dialog); + } + dialogs = { [id]: dialog }; + } else { + dialogs = id; + } + + // Add dialogs + for (var key in dialogs) { + if (!this.dialogs.hasOwnProperty(key)) { + this.dialogs[key] = dialogs[key]; + } else { + throw new Error('Dialog[' + key + '] already exists.'); + } + } + return this; + } + + public getDialog(id: string): dialog.IDialog { + return this.dialogs[id]; + } + + public getMiddleware(): { (session: ISession, next: Function): void; }[] { + return this.middleware; + } + + public hasDialog(id: string): boolean { + return this.dialogs.hasOwnProperty(id); + } + + public use(fn: (session: ISession, next: Function) => void): DialogCollection { + this.middleware.push(fn); + return this; + } +} \ No newline at end of file diff --git a/Node/src/dialogs/EntityRecognizer.ts b/Node/src/dialogs/EntityRecognizer.ts new file mode 100644 index 000000000..66d162b9a --- /dev/null +++ b/Node/src/dialogs/EntityRecognizer.ts @@ -0,0 +1,207 @@ +import utils = require('../utils'); +import sprintf = require('sprintf-js'); +import chrono = require('chrono-node'); + +interface ILuisDateTimeEntity extends IEntity { + resolution: { + resolution_type: string; + date?: string; + time?: string; + comment?: string; + duration?: string; + }; +} + +interface IChronoDuration extends IEntity { + resolution: { + resolution_type: string; + start: Date; + end?: Date; + ref?: Date; + }; +} + +export interface IFindMatchResult { + index: number; + entity: string; + score: number; +} + +export class EntityRecognizer { + static yesExp = /^(1|y|yes|yep|sure|ok|true)\z/i; + static noExp = /^(0|n|no|nope|not|false)\z/i; + static numberExp = /[+-]?(?:\d+\.?\d*|\d*\.?\d+)/; + + static findEntity(entities: IEntity[], type: string): IEntity { + for (var i = 0; i < entities.length; i++) { + if (entities[i].type == type) { + return entities[i]; + } + } + return null; + } + + static findAllEntities(entities: IEntity[], type: string): IEntity[] { + var found: IEntity[] = []; + for (var i = 0; i < entities.length; i++) { + if (entities[i].type == type) { + found.push(entities[i]); + } + } + return found; + } + + static parseTime(utterance: string): Date; + static parseTime(entities: IEntity[]): Date; + static parseTime(entities: any): Date { + if (typeof entities == 'string') { + entities = EntityRecognizer.recognizeTime(entities); + } + return EntityRecognizer.resolveTime(entities); + } + + static resolveTime(entities: IEntity[], timezoneOffset?: number): Date { + var now = new Date(); + var date: string; + var time: string; + entities.forEach((entity: ILuisDateTimeEntity) => { + if (entity.resolution) { + switch (entity.resolution.resolution_type) { + case 'builtin.datetime.date': + if (!date) { + date = entity.resolution.date; + } + break; + case 'builtin.datetime.time': + if (!time) { + time = entity.resolution.time; + if (time.length == 3) { + time = time + ':00:00'; + } else if (time.length == 6) { + time = time + ':00'; + } + // TODO: resolve "ampm" comments + } + break; + case 'chrono.duration': + // Date is already calculated + var duration = entity; + return duration.resolution.start; + } + } + }); + if (date || time) { + // The user can just say "at 9am" so we'll use today if no date. + if (!date) { + date = utils.toDate8601(now); + } + if (time) { + // Append time but adjust timezone. Default is to use bots timezone. + if (typeof timezoneOffset !== 'number') { + timezoneOffset = now.getTimezoneOffset() / 60; + } + date = sprintf.sprintf('%s%s%s%02d:00', date, time, (timezoneOffset > 0 ? '-' : '+'), timezoneOffset); + } + return new Date(date); + } + return null; + } + + static recognizeTime(utterance: string, refDate?: Date): IChronoDuration { + var response: IChronoDuration; + try { + var results = chrono.parse(utterance, refDate); + if (results && results.length > 0) { + var duration = results[0]; + response = { + type: 'chrono.duration', + entity: duration.text, + startIndex: duration.index, + endIndex: duration.index + duration.text.length, + resolution: { + resolution_type: 'chrono.duration', + start: duration.start.date() + } + }; + if (duration.end) { + response.resolution.end = duration.end.date(); + } + if (duration.ref) { + response.resolution.ref = duration.ref; + } + // Calculate a confidence score based on text coverage and call compareConfidence. + response.score = duration.text.length / utterance.length; + } + } catch (err) { + console.error('Error recognizing time: ' + err.toString()); + response = null; + } + return response; + } + + static parseNumber(utterance: string): number; + static parseNumber(entities: IEntity[]): number; + static parseNumber(entities: any): number { + var entity: IEntity; + if (typeof entities == 'string') { + entity = { type: 'text', entity: entities.trim() }; + } else { + entity = EntityRecognizer.findEntity(entities, 'builtin.number'); + } + if (entity) { + var match = this.numberExp.exec(entity.entity); + if (match) { + return Number(match[0]); + } + } + return undefined; + } + + static parseBoolean(utterance: string): boolean { + utterance = utterance.trim(); + if (EntityRecognizer.yesExp.test(utterance)) { + return true; + } else if (EntityRecognizer.noExp.test(utterance)) { + return false; + } + return undefined; + } + + static findBestMatch(choices: string[], utterance: string, threshold = 0.6): IFindMatchResult { + var best: IFindMatchResult; + var matches = EntityRecognizer.findAllMatches(choices, utterance, threshold); + matches.forEach((value) => { + if (!best || value.score > best.score) { + best = value; + } + }); + return best; + } + + static findAllMatches(choices: string[], utterance: string, threshold = 0.6): IFindMatchResult[] { + var matches: IFindMatchResult[] = []; + utterance = utterance.trim().toLowerCase(); + var tokens = utterance.split(' '); + choices.forEach((choice, index) => { + var score = 0.0; + var value = choice.trim().toLowerCase(); + if (value.indexOf(utterance) >= 0) { + score = utterance.length / value.length; + } else if (utterance.indexOf(value) >= 0) { + score = value.length / utterance.length; + } else { + var matched = ''; + tokens.forEach((token) => { + if (value.indexOf(token) >= 0) { + matched += token; + } + }); + score = matched.length / value.length; + } + if (score > threshold) { + matches.push({ index: index, entity: choice, score: score }); + } + }); + return matches; + } +} \ No newline at end of file diff --git a/Node/src/dialogs/IntentDialog.ts b/Node/src/dialogs/IntentDialog.ts new file mode 100644 index 000000000..c905f6272 --- /dev/null +++ b/Node/src/dialogs/IntentDialog.ts @@ -0,0 +1,263 @@ +import session = require('../Session'); +import dialog = require('./Dialog'); +import actions = require('./DialogAction'); +import consts = require('../consts'); + +export interface IIntentHandler { + (session: ISession, entities?: IEntity[], intents?: IIntent[]): void; +} + +export interface ICaptureIntentHandler { + (action: ISessionAction, intent: IIntent, entities?: IEntity[]): void; +} + +interface ICaptureResult extends dialog.IDialogResult { + captured: { + intents: IIntent[]; + entities: IEntity[]; + }; +} + +export interface IIntentArgs { + intents: IIntent[]; + entities: IEntity[]; +} + +interface IHandlerMatch { + groupId: string; + handler: IDialogHandler; +} + +export abstract class IntentDialog extends dialog.Dialog { + private static CAPTURE_THRESHOLD = 0.6; + + private groups: { [id: string]: IntentGroup; } = {}; + private beginDialog: IBeginDialogHandler; + private captureIntent: ICaptureIntentHandler; + private intentThreshold = 0.3; + + public begin(session: ISession, args: IntentGroup): void { + if (this.beginDialog) { + this.beginDialog(session, args, (handled) => { + if (!handled) { + super.begin(session, args); + } + }); + } else { + super.begin(session, args); + } + } + + public replyReceived(session: ISession): void { + var msg = session.message; + this.recognizeIntents(msg.language, msg.text, (err, intents, entities) => { + if (!err) { + var topIntent = this.findTopIntent(intents); + var score = topIntent ? topIntent.score : 0; + session.compareConfidence(msg.language, msg.text, score, (handled) => { + if (!handled) { + this.invokeIntent(session, intents, entities); + } + }); + } else { + session.endDialog({ error: new Error('Intent recognition error: ' + err.message) }); + } + }); + } + + public dialogResumed(session: ISession, result: ICaptureResult): void { + if (result.captured) { + this.invokeIntent(session, result.captured.intents, result.captured.entities); + } else { + var activeGroup: string = session.dialogData[consts.Data.Group]; + var activeIntent: string = session.dialogData[consts.Data.Intent]; + var group = activeGroup ? this.groups[activeGroup] : null; + var handler = group && activeIntent ? group._intentHandler(activeIntent) : null; + if (handler) { + handler(session, result); + } else { + super.dialogResumed(session, result); + } + } + } + + public compareConfidence(action: ISessionAction, language: string, utterance: string, score: number): void { + // First check to see if the childs confidence is low and that we have a capture handler. + if (score < IntentDialog.CAPTURE_THRESHOLD && this.captureIntent) { + this.recognizeIntents(language, utterance, (err, intents, entities) => { + var handled = false; + if (!err) { + // Ensure capture handler is worth invoking. Requirements are the top intents + // score should be greater then the childs score and there should be a handler + // registered for that intent. The last requirement addresses the fact that the + // 'None' intent from LUIS is likely to have a score that's greater then the + // intent threshold. + var matches: IHandlerMatch; + var topIntent = this.findTopIntent(intents); + if (topIntent && topIntent.score > this.intentThreshold && topIntent.score > score) { + matches = this.findHandler(topIntent); + } + if (matches) { + this.captureIntent({ + next: action.next, + userData: action.userData, + dialogData: action.dialogData, + endDialog: () => { + action.endDialog({ + resumed: dialog.ResumeReason.completed, + captured: { + intents: intents, + entities: entities + } + }); + }, + send: action.send + }, topIntent, entities); + } else { + action.next(); + } + } else { + console.error('Intent recognition error: ' + err.message); + action.next(); + } + }); + } else { + action.next(); + } + } + + public addGroup(group: IntentGroup): this { + var id = group.getId(); + if (!this.groups.hasOwnProperty(id)) { + this.groups[id] = group; + } else { + throw "Group of " + id + " already exists within the dialog."; + } + return this; + } + + public onBegin(handler: IBeginDialogHandler): this { + this.beginDialog = handler; + return this; + } + + public on(intent: string, fn: IDialogHandler): this; + public on(intent: string, waterfall: actions.IDialogWaterfallStep[]): this; + public on(intent: string, dialogId: string, dialogArgs?: any): this; + public on(intent: string, dialogId: any, dialogArgs?: any): this { + this.getDefaultGroup().on(intent, dialogId, dialogArgs); + return this; + } + + public onDefault(fn: IDialogHandler): this; + public onDefault(waterfall: actions.IDialogWaterfallStep[]): this; + public onDefault(dialogId: string, dialogArgs?: any): this; + public onDefault(dialogId: any, dialogArgs?: any): this { + this.getDefaultGroup().on(consts.Intents.Default, dialogId, dialogArgs); + return this; + } + + public getThreshold(): number { + return this.intentThreshold; + } + + public setThreshold(score: number): this { + this.intentThreshold = score; + return this; + } + + private invokeIntent(session: ISession, intents: IIntent[], entities: IEntity[]): void { + try { + // Find top intent, group, and handler; + var match: IHandlerMatch; + var topIntent = this.findTopIntent(intents); + if (topIntent && topIntent.score > this.intentThreshold) { + match = this.findHandler(topIntent); + } + if (!match) { + match = { + groupId: consts.Id.DefaultGroup, + handler: this.getDefaultGroup()._intentHandler(consts.Intents.Default) + }; + } + + // Invoke handler + if (match) { + session.dialogData[consts.Data.Group] = match.groupId; + session.dialogData[consts.Data.Intent] = topIntent.intent; + match.handler(session, { intents: intents, entities: entities }); + } else { + session.send(); + } + } catch (e) { + session.endDialog({ error: new Error('Exception handling intent: ' + e.message) }); + } + } + + private findTopIntent(intents: IIntent[]): IIntent { + var topIntent: IIntent; + for (var i = 0; i < intents.length; i++) { + var intent = intents[i]; + if (!topIntent) { + topIntent = intent; + } else if (intent.score > topIntent.score) { + topIntent = intent; + } + } + return topIntent; + } + + private findHandler(intent: IIntent): IHandlerMatch { + for (var groupId in this.groups) { + var handler = this.groups[groupId]._intentHandler(intent.intent); + if (handler) { + return { groupId: groupId, handler: handler }; + } + } + return null; + } + + private getDefaultGroup(): IntentGroup { + var group = this.groups[consts.Id.DefaultGroup]; + if (!group) { + this.groups[consts.Id.DefaultGroup] = group = new IntentGroup(consts.Id.DefaultGroup); + } + return group; + } + + protected abstract recognizeIntents(language: string, utterance: string, callback: (err: Error, intents?: IIntent[], entities?: IEntity[]) => void): void; +} + +export class IntentGroup { + private handlers: { [id: string]: IDialogHandler; } = {}; + + constructor(private id: string) { + } + + public getId(): string { + return this.id; + } + + /** Returns the handler registered for an intent if it exists. */ + public _intentHandler(intent: string): IDialogHandler { + return this.handlers[intent]; + } + + public on(intent: string, fn: IDialogHandler): this; + public on(intent: string, waterfall: actions.IDialogWaterfallStep[]): this; + public on(intent: string, dialogId: string, dialogArgs?: any): this; + public on(intent: string, dialogId: any, dialogArgs?: any): this { + if (!this.handlers.hasOwnProperty(intent)) { + if (Array.isArray(dialogId)) { + this.handlers[intent] = actions.DialogAction.waterfall(dialogId); + } else if (typeof dialogId == 'string') { + this.handlers[intent] = actions.DialogAction.beginDialog(dialogId, dialogArgs); + } else { + this.handlers[intent] = dialogId; + } + } else { + throw new Error('Intent[' + intent + '] already exists.'); + } + return this; + } +} diff --git a/Node/src/dialogs/LuisDialog.ts b/Node/src/dialogs/LuisDialog.ts new file mode 100644 index 000000000..2f4f4294e --- /dev/null +++ b/Node/src/dialogs/LuisDialog.ts @@ -0,0 +1,124 @@ +import intent = require('./IntentDialog'); +import dialog = require('./Dialog'); +import utils = require('../utils'); +import request = require('request'); +import sprintf = require('sprintf-js'); + +interface ILuisResults { + query: string; + intents: IIntent[]; + entities: IEntity[]; +} + +export class LuisDialog extends intent.IntentDialog { + constructor(private serviceUri: string) { + super(); + } + + protected recognizeIntents(language: string, utterance: string, callback: (err: Error, intents?: IIntent[], entities?: IEntity[]) => void): void { + LuisDialog.recognize(utterance, this.serviceUri, callback); + } + + static recognize(utterance: string, serviceUri: string, callback: (err: Error, intents?: IIntent[], entities?: IEntity[]) => void): void { + var uri = serviceUri.trim(); + if (uri.lastIndexOf('&q=') != uri.length - 3) { + uri += '&q='; + } + uri += encodeURIComponent(utterance || ''); + request.get(uri, (err: Error, res: any, body: string) => { + try { + if (!err) { + var result: ILuisResults = JSON.parse(body); + if (result.intents.length == 1 && !result.intents[0].hasOwnProperty('score')) { + // Intents for the builtin Cortana app don't return a score. + result.intents[0].score = 1.0; + } + callback(null, result.intents, result.entities); + } else { + callback(err); + } + } catch (e) { + callback(e); + } + }); + } +} + +interface ILuisDateTimeEntity extends IEntity { + resolution: ILuisDateTimeResolution; +} + +interface ILuisDateTimeResolution { + resolution_type: string; + date?: string; + time?: string; + comment?: string; + duration?: string; +} + +export class LuisEntityResolver { + + static findEntity(entities: IEntity[], type: string): IEntity { + for (var i = 0; i < entities.length; i++) { + if (entities[i].type == type) { + return entities[i]; + } + } + return null; + } + + static findAllEntities(entities: IEntity[], type: string): IEntity[] { + var found: IEntity[] = []; + for (var i = 0; i < entities.length; i++) { + if (entities[i].type == type) { + found.push(entities[i]); + } + } + return found; + } + + static resolveDate(entities: IEntity[], timezoneOffset?: number): Date { + var now = new Date(); + var date: string; + var time: string; + for (var i = 0; i < entities.length; i++) { + var entity = entities[i]; + if (entity.resolution) { + switch (entity.resolution.resolution_type) { + case 'builtin.datetime.date': + if (!date) { + date = entity.resolution.date; + } + break; + case 'builtin.datetime.time': + if (!time) { + time = entity.resolution.time; + if (time.length == 3) { + time = time + ':00:00'; + } else if (time.length == 6) { + time = time + ':00'; + } + // TODO: resolve "ampm" comments + } + break; + } + } + } + if (date || time) { + // The user can just say "at 9am" so we'll use today if no date. + if (!date) { + date = utils.toDate8601(now); + } + if (time) { + // Append time but adjust timezone. Default is to use bots timezone. + if (typeof timezoneOffset !== 'number') { + timezoneOffset = now.getTimezoneOffset() / 60; + } + date = sprintf.sprintf('%s%s%s%02d:00', date, time, (timezoneOffset > 0 ? '-' : '+'), timezoneOffset); + } + return new Date(date); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/Node/src/dialogs/Prompts.ts b/Node/src/dialogs/Prompts.ts new file mode 100644 index 000000000..0881054d6 --- /dev/null +++ b/Node/src/dialogs/Prompts.ts @@ -0,0 +1,317 @@ +import dialog = require('./Dialog'); +import session = require('../Session'); +import consts = require('../consts'); +import entities = require('./EntityRecognizer'); + +export enum PromptType { text, number, confirm, choice, time } + +export enum ListStyle { none, inline, list } + +export interface IPromptOptions { + retryPrompt?: string; + maxRetries?: number; + refDate?: number; + listStyle?: ListStyle; +} + +export interface IPromptArgs extends IPromptOptions { + promptType: PromptType; + prompt: string; + enumValues?: string[]; +} + +export interface IPromptResult extends dialog.IDialogResult { + promptType?: PromptType; +} + +export interface IPromptRecognizerResult extends IPromptResult { + handled?: boolean; +} + +export interface IPromptRecognizer { + recognize(args: IPromptRecognizerArgs, callback: (result: IPromptRecognizerResult) => void, session?: ISession): void; +} + +export interface IPromptRecognizerArgs { + promptType: PromptType; + language: string; + utterance: string; + enumValues?: string[]; + refDate?: number; + compareConfidence(language: string, utterance: string, score: number, callback: (handled: boolean) => void): void; +} + +export interface IPromptsOptions { + recognizer?: IPromptRecognizer +} + +export interface IChronoDuration extends IEntity { + resolution: { + start: Date; + end?: Date; + ref?: Date; + }; +} + +export class SimplePromptRecognizer implements IPromptRecognizer { + private cancelExp = /^(cancel|nevermind|never mind|back|stop|forget it)/i; + + public recognize(args: IPromptRecognizerArgs, callback: (result: IPromptRecognizerResult) => void, session?: ISession): void { + this.checkCanceled(args, () => { + try { + // Recognize value + var score = 0.0; + var response: any; + var text = args.utterance.trim(); + switch (args.promptType) { + case PromptType.text: + // This is an open ended question so it's a little tricky to know what to pass as a confidence + // score. Currently we're saying that we have 0.1 confidence that we understand the users intent + // which will give all of the prompts parents a chance to capture the utterance. If no one + // captures the utterance we'll return the full text of the utterance as the result. + score = 0.1; + response = text; + break; + case PromptType.number: + var n = entities.EntityRecognizer.parseNumber(text); + if (!isNaN(n)) { + var score = n.toString().length / text.length; + response = n; + } + break; + case PromptType.confirm: + var b = entities.EntityRecognizer.parseBoolean(text); + if (typeof b == 'boolean') { + score = 1.0; + response = b; + } + break; + case PromptType.time: + var entity = entities.EntityRecognizer.recognizeTime(text, args.refDate ? new Date(args.refDate) : null); + if (entity) { + score = entity.entity.length / text.length; + response = entity; + } + break; + case PromptType.choice: + var best = entities.EntityRecognizer.findBestMatch(args.enumValues, text); + if (!best) { + var n = entities.EntityRecognizer.parseNumber(text); + if (!isNaN(n) && n > 0 && n <= args.enumValues.length) { + best = { index: n, entity: args.enumValues[n - 1], score: 1.0 }; + } + } + if (best) { + score = best.score; + response = best; + } + break; + default: + } + + // Return results + args.compareConfidence(args.language, text, score, (handled) => { + if (!handled && score > 0) { + callback({ resumed: dialog.ResumeReason.completed, promptType: args.promptType, response: response }); + } else { + callback({ resumed: dialog.ResumeReason.notCompleted, promptType: args.promptType, handled: handled }); + } + }); + } catch (err) { + callback({ resumed: dialog.ResumeReason.notCompleted, promptType: args.promptType, error: err instanceof Error ? err : new Error(err.toString()) }); + } + }, callback); + } + + protected checkCanceled(args: IPromptRecognizerArgs, onContinue: Function, callback: (result: IPromptRecognizerResult) => void) { + if (!this.cancelExp.test(args.utterance.trim())) { + onContinue(); + } else { + callback({ resumed: dialog.ResumeReason.canceled, promptType: args.promptType }); + } + } +} + +export class Prompts extends dialog.Dialog { + private static options: IPromptsOptions = { + recognizer: new SimplePromptRecognizer() + }; + + public begin(session: ISession, args: IPromptArgs): void { + args = args || {}; + args.maxRetries = args.maxRetries || 1; + for (var key in args) { + if (args.hasOwnProperty(key)) { + session.dialogData[key] = (args)[key]; + } + } + session.send(args.prompt); + } + + public replyReceived(session: ISession): void { + var args: IPromptArgs = session.dialogData; + Prompts.options.recognizer.recognize( + { + promptType: args.promptType, + utterance: session.message.text, + language: session.message.language, + enumValues: args.enumValues, + refDate: args.refDate, + compareConfidence: function (language, utterance, score, callback) { + session.compareConfidence(language, utterance, score, callback); + } + }, (result) => { + if (!result.handled) { + if (result.error || result.resumed == dialog.ResumeReason.completed || + result.resumed == dialog.ResumeReason.canceled || args.maxRetries == 0) { + result.promptType = args.promptType; + session.endDialog(result); + } else { + args.maxRetries--; + session.send(args.retryPrompt || "I didn't understand. " + args.prompt); + } + } + }); + } + + static configure(options: IPromptsOptions): void { + if (options) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + (Prompts.options)[key] = (options)[key]; + } + } + } + } + + static text(ses: session.Session, prompt: string): void { + beginPrompt(ses, { + promptType: PromptType.text, + prompt: prompt + }); + } + + static recognizeText(language: string, text: string, callback: (result: IPromptResult) => void): void { + Prompts.options.recognizer.recognize( + { + promptType: PromptType.text, + language: language, + utterance: text, + compareConfidence: (language, utterance, score, callback) => { + callback(false); + } + }, callback); + } + + static number(ses: session.Session, prompt: string, options?: IPromptOptions): void { + var args: IPromptArgs = options || {}; + args.promptType = PromptType.number; + args.prompt = prompt; + beginPrompt(ses, args); + } + + static recognizeNumber(language: string, text: string, callback: (result: IPromptResult) => void): void { + Prompts.options.recognizer.recognize( + { + promptType: PromptType.number, + language: language, + utterance: text, + compareConfidence: (language, utterance, score, callback) => { + callback(false); + } + }, callback); + } + + static confirm(ses: session.Session, prompt: string, options?: IPromptOptions): void { + var args: IPromptArgs = options || {}; + args.promptType = PromptType.confirm; + args.prompt = prompt; + beginPrompt(ses, args); + } + + static recognizeConfirm(language: string, text: string, callback: (result: IPromptResult) => void): void { + Prompts.options.recognizer.recognize( + { + promptType: PromptType.confirm, + language: language, + utterance: text, + compareConfidence: (language, utterance, score, callback) => { + callback(false); + } + }, callback); + } + + static choice(ses: session.Session, prompt: string, enumValues: string[], options?: IPromptOptions): void { + var args: IPromptArgs = options || {}; + args.promptType = PromptType.choice; + args.prompt = prompt; + args.enumValues = enumValues; + args.listStyle = args.listStyle || ListStyle.list; + + // Format list + var connector = '', list: string; + switch (args.listStyle) { + case ListStyle.list: + list = '\n '; + enumValues.forEach((value, index) => { + list += connector + (index + 1) + '. ' + value; + connector = '\n '; + }); + args.prompt += list; + break; + case ListStyle.inline: + list = ' '; + enumValues.forEach((value, index) => { + list += connector + (index + 1) + '. ' + value; + if (index == enumValues.length - 2) { + connector = index == 0 ? ' or ' : ', or '; + } else { + connector = ', '; + } + }); + args.prompt += list; + break; + } + beginPrompt(ses, args); + } + + static recognizeChoice(language: string, text: string, enumValues: string[], callback: (result: IPromptResult) => void): void { + Prompts.options.recognizer.recognize( + { + promptType: PromptType.choice, + language: language, + utterance: text, + enumValues: enumValues, + compareConfidence: (language, utterance, score, callback) => { + callback(false); + } + }, callback); + } + + static time(ses: session.Session, prompt: string, options?: IPromptOptions): void { + var args: IPromptArgs = options || {}; + args.promptType = PromptType.time; + args.prompt = prompt; + beginPrompt(ses, args); + } + + static recognizeTime(language: string, text: string, refDate: Date, callback: (result: IPromptResult) => void): void { + Prompts.options.recognizer.recognize( + { + promptType: PromptType.time, + language: language, + utterance: text, + refDate: (refDate || new Date()).getTime(), + compareConfidence: (language, utterance, score, callback) => { + callback(false); + } + }, callback); + } +} + +function beginPrompt(ses: session.Session, args: IPromptArgs) { + if (!ses.dialogs.hasDialog(consts.DialogId.Prompts)) { + ses.dialogs.add(consts.DialogId.Prompts, new Prompts()); + } + ses.beginDialog(consts.DialogId.Prompts, args); +} diff --git a/Node/src/dialogs/SimpleDialog.ts b/Node/src/dialogs/SimpleDialog.ts new file mode 100644 index 000000000..3a7b7d81f --- /dev/null +++ b/Node/src/dialogs/SimpleDialog.ts @@ -0,0 +1,24 @@ +import dialog = require('./Dialog'); + +export class SimpleDialog extends dialog.Dialog { + constructor(private fn: (session: ISession, arg?: any) => void) { + super(); + } + + public begin(session: ISession, args?: T): void { + this.fn(session, args); + } + + public replyReceived(session: ISession): void { + session.compareConfidence(session.message.language, session.message.text, 0.0, (handled) => { + if (!handled) { + this.fn(session); + } + }); + } + + public dialogResumed(session: ISession, result: any): void { + this.fn(session, result); + } +} + diff --git a/Node/src/interfaces.d.ts b/Node/src/interfaces.d.ts new file mode 100644 index 000000000..997c966ac --- /dev/null +++ b/Node/src/interfaces.d.ts @@ -0,0 +1,127 @@ + +interface IMessage { + type?: string; + id?: string; + conversationId?: string; + created?: string; + sourceText?: string; + sourceLanguage?: string; + language?: string; + text?: string; + attachments?: IAttachment[]; + from?: IChannelAccount; + to?: IChannelAccount; + replyTo?: IChannelAccount; + replyToMessageId?: string; + participants?: IChannelAccount[]; + totalParticipants?: number; + mentions?: IMention[]; + place?: string; + channelMessageId?: string; + channelConversationId?: string; + channelData?: any; + location?: ILocation; + hashtags?: string[]; + eTag?: string; +} + +interface IAttachment { + contentType: string; + contentUrl?: string; + content?: any; + fallbackText?: string; + title?: string; + titleLink?: string; + text?: string; + thumbnailUrl?: string; +} + +interface IChannelAccount { + name?: string; + channelId: string; + address: string; + id?: string; + isBot?: boolean; +} + +interface IMention { + mentioned?: IChannelAccount; + text?: string; +} + +interface ILocation { + altitude?: number; + latitude: number; + longitude: number; +} + +interface IBeginDialogAddress { + to: IChannelAccount; + from?: IChannelAccount; + language?: string; + text?: string; +} + +interface ILocalizer { + gettext(language: string, msgid: string): string; + ngettext(language: string, msgid: string, msgid_plural: string, count: number): string; +} + +interface ISession { + sessionState: ISessionState; + message: IMessage; + userData: any; + dialogData: any; + error(err: Error): ISession; + gettext(msgid: string, ...args: any[]): string; + ngettext(msgid: string, msgid_plural: string, count: number): string; + send(): ISession; + send(msg: string, ...args: any[]): ISession; + send(msg: IMessage): ISession; + messageSent(): boolean; + beginDialog(id: string, args?: T): ISession; + endDialog(result?: any): ISession; + compareConfidence(language: string, utterance: string, score: number, callback: (handled: boolean) => void): void; + reset(id: string): ISession; + isReset(): boolean; +} + +interface ISessionAction { + userData: any; + dialogData: any; + next(): void; + endDialog(result?: any): void; + send(msg: string, ...args: any[]): void; + send(msg: IMessage): void; +} + +interface ISessionState { + callstack: IDialogState[]; + lastAccess: number; +} + +interface IDialogState { + id: string; + state: any; +} + +interface IBeginDialogHandler { + (session: ISession, args: any, next: (handled: boolean) => void): void; +} + +interface IDialogHandler { + (session: ISession, args?: T): void; +} + +interface IIntent { + intent: string; + score: number; +} + +interface IEntity { + entity: string; + type: string; + startIndex?: number; + endIndex?: number; + score?: number; +} diff --git a/Node/src/storage/Storage.ts b/Node/src/storage/Storage.ts new file mode 100644 index 000000000..edbf41588 --- /dev/null +++ b/Node/src/storage/Storage.ts @@ -0,0 +1,31 @@ +import utils = require('../utils'); + +export interface IStorage { + get(id: string, callback: (err: Error, data: any) => void): void; + save(id: string, data: any, callback?: (err: Error) => void): void; +} + +export class MemoryStorage implements IStorage { + private store: { [id: string]: any; } = {}; + + public get(id: string, callback: (err: Error, data: any) => void): void { + if (this.store.hasOwnProperty(id)) { + callback(null, utils.clone(this.store[id])); + } else { + callback(null, null); + } + } + + public save(id: string, data: any, callback?: (err: Error) => void): void { + this.store[id] = utils.clone(data || {}); + if (callback) { + callback(null); + } + } + + public delete(id: string) { + if (this.store.hasOwnProperty(id)) { + delete this.store[id]; + } + } +} \ No newline at end of file diff --git a/Node/src/tsconfig.json b/Node/src/tsconfig.json new file mode 100644 index 000000000..342c86071 --- /dev/null +++ b/Node/src/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "sourceMap": false, + "noImplicitAny": true, + "outDir": "..\build", + "noEmitOnError": true + }, + "exclude": [ + "botbuilder.d.ts" + ] +} \ No newline at end of file diff --git a/Node/src/utils.ts b/Node/src/utils.ts new file mode 100644 index 000000000..c4addc772 --- /dev/null +++ b/Node/src/utils.ts @@ -0,0 +1,17 @@ +import sprintf = require('sprintf-js'); + +export function clone(obj: any): any { + var cpy: any = {}; + if (obj) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + cpy[key] = obj[key]; + } + } + } + return cpy; +} + +export function toDate8601(date: Date): string { + return sprintf.sprintf('%04d-%02d-%02d', date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()); +} \ No newline at end of file