diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..11d0faa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: node_js +node_js: + - "node" + - "7" + - "6" + - "5" + - "4" + - "lts/*" +script: npm test +env: + CODECLIMATE_REPO_TOKEN: a174b03227cdc953c831f45bebcf0af357ec1b8a4b8477f36444c16f084a409f diff --git a/README.md b/README.md index ded8f9f..d553004 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # Dota 2 Random - Alexa Skill -Skill is now available from the [Alexa Skill Store](https://www.amazon.com/binhonglee-Dota2-Random/dp/B073W4GDLS)! +[![Build Status](https://travis-ci.org/binhonglee/dota2-random.svg?branch=master)](https://travis-ci.org/binhonglee/dota2-random) [![Test Coverage](https://codeclimate.com/github/binhonglee/dota2-random/badges/coverage.svg)](https://codeclimate.com/github/binhonglee/dota2-random/coverage) [![Dependency Status](https://gemnasium.com/badges/github.com/binhonglee/dota2-random.svg)](https://gemnasium.com/github.com/binhonglee/dota2-random) -#### Dependency -- alexa-sdk +## Skill is now available from the [Alexa Skill Store](https://www.amazon.com/binhonglee-Dota2-Random/dp/B073W4GDLS)! + +### Dependency - node.js +- alexa-sdk -## Documentations +### Documentations -### Intents +#### Intents | Intents | Description | |:---------|:------------| @@ -20,7 +22,7 @@ Skill is now available from the [Alexa Skill Store](https://www.amazon.com/binho | `AMAZON.HelpIntent` | Provide examples on how to use the skill | | `Stop` | Stop the skill | -### ROLE_TYPES (Custom slot) +#### ROLE_TYPES (Custom slot) - melee - ranged - carry diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..345b0a8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,146 @@ +{ + "name": "dota2-random", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "alexa-sdk": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/alexa-sdk/-/alexa-sdk-1.0.11.tgz", + "integrity": "sha1-u8XZXJh4vHbEtI95Ca1iDIEESZU=", + "requires": { + "aws-sdk": "2.95.0", + "i18next": "3.5.2", + "i18next-sprintf-postprocessor": "0.2.2" + }, + "dependencies": { + "aws-sdk": { + "version": "2.95.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.95.0.tgz", + "integrity": "sha1-JuIdsUlEOx8GOUnch5hPDRdwDmo=", + "requires": { + "buffer": "4.9.1", + "crypto-browserify": "1.0.9", + "events": "1.1.1", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.0.1", + "xml2js": "0.4.17", + "xmlbuilder": "4.2.1" + }, + "dependencies": { + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "1.2.1", + "ieee754": "1.1.8", + "isarray": "1.0.0" + }, + "dependencies": { + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha1-qRlH2h9KUW6jjltOwOw3c2deCIY=" + }, + "ieee754": { + "version": "1.1.8", + "resolved": "http://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } + } + }, + "crypto-browserify": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", + "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" + }, + "events": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "http://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "uuid": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" + }, + "xml2js": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", + "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "requires": { + "sax": "1.2.1", + "xmlbuilder": "4.2.1" + } + }, + "xmlbuilder": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "requires": { + "lodash": "4.17.4" + }, + "dependencies": { + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + } + } + } + } + }, + "i18next": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-3.5.2.tgz", + "integrity": "sha1-kwOQ1cMYzqpIWLUt0OQOayA/n0E=" + }, + "i18next-sprintf-postprocessor": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/i18next-sprintf-postprocessor/-/i18next-sprintf-postprocessor-0.2.2.tgz", + "integrity": "sha1-LkCfEENXk4Jpi2otpwzapVHWfqQ=" + } + } + } + } +} diff --git a/package.json b/package.json index 46d5c4e..9e4a503 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,18 @@ "dependencies": { "alexa-sdk": "^1.0.7" }, + "scripts": { + "test": "npm install -g standard && standard && node test/testflow.js && npm uninstall -g standard" + }, "author": "binhonglee", "license": "MIT", "repository": { "type": "git", - "url": "git+ssh://git@github.com/binhonglee/dota2-alexa-skill.git" + "url": "git+ssh://git@github.com/binhonglee/dota2-random.git" }, "bugs": { - "url": "https://github.com/binhonglee/dota2-alexa-skill/issues" + "url": "https://github.com/binhonglee/dota2-random/issues" }, - "homepage": "https://github.com/binhonglee/dota2-alexa-skill#readme" + "homepage": "https://github.com/binhonglee/dota2-random#readme", + "devDependencies": {} } diff --git a/src/index.js b/src/index.js index ee979bb..e9b47ad 100644 --- a/src/index.js +++ b/src/index.js @@ -81,7 +81,8 @@ function randomHero (categories, callback) { } if (!possible) { - callback('Random failed') + var errorMessage = 'Random failed' + callback(errorMessage) return } diff --git a/test/dialogs/default.txt b/test/dialogs/default.txt new file mode 100644 index 0000000..87f3ad5 --- /dev/null +++ b/test/dialogs/default.txt @@ -0,0 +1,7 @@ +LaunchRequest +AMAZON.HelpIntent +AnyRequest +RequestIntent Role=agility +RequestTwoIntent RoleOne=melee RoleTwo=ranged +RequestThreeIntent RoleOne=melee RoleTwo=intelligence RoleThree=support +Stop diff --git a/test/testflow.js b/test/testflow.js new file mode 100644 index 0000000..ec5d7dd --- /dev/null +++ b/test/testflow.js @@ -0,0 +1,290 @@ +// Test Flow - a multiple intent test script for Alexa Lambda code +// Launch from a Terminal Prompt. Examples: +// node testflow +// node testflow staterequest.txt + +// Originally taken from https://github.com/alexa/alexa-cookbook/blob/master/testing/TestFlow/testflow.js + +// Toggle on or off various debugging outputs +const options = { + speechOutput: true, + slots: true, + stdout: false, // standard output / console.log() in your code + requestEvent: false, // show the full request JSON sent to your code + reprompt: false, + delay: 1.0 // seconds between requests +} + +var locale = 'en-US' + +var fs = require('fs') +var MyLambdaFunction = require('../src/index.js') // Your Lambda source with exports.handler + +var MyDialog = './test/dialogs/default.txt' +var appId = '' // 'amzn1.ask.skill.d60ceb98-befe-4f49-a627-1f20f6e98d94' +var slotArray = [] + +if (process.argv[2]) { + MyDialog = './dialogs/' + process.argv[2] +} + +console.log() +console.log('================================================================================') +console.log('Running test sequence from dialog file : ', MyDialog) +console.log() + +const OriginalConsoleLog = console.log + +var slotname = '' +var slotvalue = '' +var sa = {} +var currentLine = 1 +var lineArray = [] +var Intent = '' +var prompt = false + +var context = { + 'succeed': function (data) { + if (data.response.shouldEndSession) { + sa = {} + } else { + sa = data.sessionAttributes + } + + console.log = OriginalConsoleLog + var textToSay = data.response.outputSpeech.ssml + + if (options.speechOutput) { + console.log('\n\x1b[36m%s\x1b[0m', textToSay) + } + + if (data.response.reprompt && data.response.reprompt.outputSpeech && data.response.reprompt.outputSpeech.ssml) { + var textReprompt = data.response.reprompt.outputSpeech.ssml + if (options.reprompt) { + // console.log('%s \x1b[33m\x1b[1m%s\x1b[0m \x1b[2m%s\x1b[0m', currentLine+1, Intent, sdkState) + console.log('\x1b[36m \x1b[2m%s\x1b[0m ', textReprompt) + } + } + + if (data.response.shouldEndSession) { + console.log('\n================= Session Ended =================') + } + + if (currentLine < lineArray.length) { + console.log() + runSingleTest(lineArray, currentLine++, sa) + } else { + console.log('') + process.exit() + } + }, + 'fail': function (err) { + console.log('context.fail occurred') + console.log(JSON.stringify(err, null, '\t')) + } +} + +fs.readFile(MyDialog, function (err, data) { // open dialog sequence file and read Intents + // var newSession = true + if (err) {} + lineArray = cleanArray(data.toString().split('\n')) // remove empty or comment lines (# or //) + runSingleTest(lineArray, 0, {}) +}) + +function runSingleTest (myLineArray, currentLine, sa) { + // console.log('--------------------------------------------------------------------------------') + // console.log('testing line ', currentLine) + // console.log('testing line values ', myLineArray[currentLine]) + prompt = false + var newSession = true + if (currentLine > 0) { + newSession = false + } + + var tokenArray = myLineArray[currentLine].split(' ') + + if (tokenArray[0].replace('\r', '') === '?') { // pause and prompt the user to confirm + prompt = true + // console.log(' ----------------- > prompt') + tokenArray.shift() // removes first item + } + + var requestType = tokenArray[0].replace('\r', '') + tokenArray.shift() + + if (requestType === 'stop') { + console.log('') + process.exit() + } + + Intent = requestType + // slotArray = [] + + var sdkState = '' + if (sa['STATE']) { + sdkState = sa['STATE'] + } + + console.log('%s \x1b[33m\x1b[1m%s\x1b[0m \x1b[2m%s\x1b[0m', currentLine + 1, Intent, sdkState) + processArray(tokenArray, function (request) { + prepareTestRequest(sa, newSession, request) + }) +} + +function processArray (arr, cb) { + if (arr.length > 0) { + var equalsPosition = arr[0].indexOf('=') + slotname = arr[0].substr(0, equalsPosition) + slotvalue = decodeURI(arr[0].substr(equalsPosition + 1, 300)).replace('\r', '') + + promptForSlot(prompt, slotname, slotvalue, (newValue) => { + // console.log('slotname, slotvalue, newValue') + // console.log(slotname, slotvalue, newValue) + + var answer = newValue.toString().trim() + + // console.log('answer = ' + answer) + + if (answer === '') { + answer = slotvalue + } + + if (answer !== '') { + slotArray.push('"' + slotname + '": {"name":"' + slotname + '","value":"' + answer + '"}') + } + + arr.shift() + processArray(arr, cb) // RECURSION + }) + } else { // nothing left in slot array + var slotArrayString = '{' + slotArray.toString() + '}' + + var slotObj = JSON.parse(slotArrayString) + + var req = { + 'type': 'IntentRequest', + 'intent': { + 'name': Intent, + 'slots': slotObj + }, + 'locale': locale + } + cb(req) + // process.exit() + } +} + +function prepareTestRequest (sa, newSession, request) { + var eventJSON = { + 'session': { + 'sessionId': 'SessionId.f9e6dcbb-b7da-4b47-905c.etc.etc', + 'application': { + 'applicationId': appId + }, + 'attributes': sa, + 'user': { + 'userId': 'amzn1.ask.account.VO3PVTGF563MOPBY.etc.etc' + }, + 'new': newSession + }, + request, + 'version': '1.0' + } + + if (options.requestEvent) { + console.log(JSON.stringify(request, null, 2)) + } + + // blocking pause + var waitTill = new Date(new Date().getTime() + options.delay * 1000) + while (waitTill > new Date()) {} + + // call the function + if (options.stdout) { + MyLambdaFunction['handler'](eventJSON, context, callback) + } else { + // console.log('setting log to {}') + console.log = function () {} + // console.log('set log to {}') + + MyLambdaFunction['handler'](eventJSON, context, callback) + console.log = OriginalConsoleLog + } +} + +function promptForSlot (prompt, slotname, slotvalue, callback) { + if (prompt) { + process.stdout.write('\x1b[34m' + slotname + ' \x1b[0m\x1b[32m [' + slotvalue + ']\x1b[0m: ') + + // console.log('\x1b[34m%s :\x1b[0m\x1b[32m %s\x1b[0m ', slotname, slotvalue ) + + process.stdin.once('data', function (data) { + var answer = data.toString().trim() + // console.log(answer) + if (answer === '') { + if (slotvalue === '') { + // no default, user must type something + console.error('Error: No default slot value defined, user must type a slot value.') + process.exit() + } else { + answer = slotvalue + } + } + + callback(answer) + }) + } else { + if (options.slots) { + console.log('\x1b[34m%s :\x1b[0m\x1b[32m %s\x1b[0m ', slotname, slotvalue) + } + + callback(slotvalue) + } +} + +function callback (error, data) { + if (error) { + console.log('error: ' + error) + } else { + console.log(data) + } +} + +function cleanArray (myArray) { + var cleanedArray = [] + + for (var i = 0; i < myArray.length; i++) { + if (myArray[i] !== '' && myArray[i].substring(0, 1) !== '#' && myArray[i].substring(0, 2) !== '//') { + cleanedArray.push(myArray[i]) + } + } + return cleanedArray +} +// +// const fontcolor = { +// Reset = '\x1b[0m', +// Bright = '\x1b[1m', +// Dim = '\x1b[2m', +// Underscore = '\x1b[4m', +// Blink = '\x1b[5m', +// Reverse = '\x1b[7m', +// Hidden = '\x1b[8m', +// +// FgBlack = '\x1b[30m', +// FgRed = '\x1b[31m', +// FgGreen = '\x1b[32m', +// FgYellow = '\x1b[33m', +// FgBlue = '\x1b[34m', +// FgMagenta = '\x1b[35m', +// FgCyan = '\x1b[36m', +// FgWhite = '\x1b[37m', +// +// BgBlack = '\x1b[40m', +// BgRed = '\x1b[41m', +// BgGreen = '\x1b[42m', +// BgYellow = '\x1b[43m', +// BgBlue = '\x1b[44m', +// BgMagenta = '\x1b[45m', +// BgCyan = '\x1b[46m', +// BgWhite = '\x1b[47m' +// }