├── slots ├── ARTICLE.csv ├── WHAT_IS.csv ├── LEAGUE.csv ├── UNIQUE_MAP.csv ├── UNIQUE_FLASK.csv ├── FRAGMENT.csv ├── QUEST.csv ├── UNIQUE_JEWEL.csv ├── UNIQUE_ACCESSORY.csv ├── ESSENCE.csv ├── CURRENCY.csv ├── DIVINATION.csv ├── UNIQUE_WEAPON.csv ├── PROPHECY.csv ├── UNIQUE_ARMOUR.csv └── GEM.csv ├── lambda ├── custom │ ├── intents │ │ ├── currencyitempricecheck │ │ │ ├── index.ts │ │ │ └── Completed.ts │ │ ├── linkeditempricecheck │ │ │ ├── index.ts │ │ │ └── Completed.ts │ │ ├── gempricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── mappricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── questrewards │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── currencypricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── divinationpricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── essencepricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── fragmentpricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── normalitempricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── prophecypricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── uniquemappricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── uniqueaccessorypricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── uniquearmourpricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── uniqueflaskpricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── uniquejewelpricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── uniqueweaponpricecheck │ │ │ ├── index.ts │ │ │ ├── InProgress.ts │ │ │ └── Completed.ts │ │ ├── index.ts │ │ ├── Debug.ts │ │ ├── SessionEnded.ts │ │ ├── Stop.ts │ │ ├── Help.ts │ │ ├── SystemExceptionEncountered.ts │ │ ├── Launch.ts │ │ └── Fallback.ts │ ├── api │ │ ├── index.ts │ │ ├── POENinjaClient.ts │ │ ├── interfaces.ts │ │ └── __mocks__ │ │ │ └── POENinjaClient.ts │ ├── interceptors │ │ ├── index.ts │ │ ├── Slots.ts │ │ └── Localization.ts │ ├── errors │ │ ├── index.ts │ │ ├── API.ts │ │ ├── Unexpected.ts │ │ └── Unknown.ts │ ├── package.json │ ├── package-lock.json │ ├── interfaces.ts │ ├── lib │ │ ├── constants.ts │ │ └── strings.ts │ └── index.ts ├── local │ └── index.ts └── tools │ └── generateItemNames.ts ├── assets ├── Icon_108.png └── Icon_512.png ├── nodemon.json ├── .travis.yml ├── .gitignore ├── __tests__ ├── enUS │ ├── Map.spec.ts │ ├── Essence.spec.ts │ ├── Prophecy.spec.ts │ ├── UniqueMap.spec.ts │ ├── Currency.spec.ts │ ├── Divination.spec.ts │ ├── Fragment.spec.ts │ ├── UniqueFlask.spec.ts │ ├── UniqueJewel.spec.ts │ ├── UniqueAccessory.spec.ts │ ├── Launch.spec.ts │ ├── UniqueArmour.spec.ts │ ├── UniqueWeapon.spec.ts │ ├── Errors.spec.ts │ ├── POENinjaClient.spec.ts │ ├── BuiltIn.spec.ts │ ├── QuestReward.spec.ts │ └── Gem.spec.ts ├── helpers.ts └── generic.ts ├── gulpfile.js ├── tsconfig.json ├── .ask └── config ├── LICENSE.md ├── package.json ├── README.md ├── Intents.md └── skill.json /slots/ARTICLE.csv: -------------------------------------------------------------------------------- 1 | a, 2 | an, 3 | the, -------------------------------------------------------------------------------- /lambda/custom/intents/currencyitempricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Completed"; 2 | -------------------------------------------------------------------------------- /lambda/custom/intents/linkeditempricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Completed"; 2 | -------------------------------------------------------------------------------- /assets/Icon_108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xzya/alexa-poe-helper/master/assets/Icon_108.png -------------------------------------------------------------------------------- /assets/Icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xzya/alexa-poe-helper/master/assets/Icon_512.png -------------------------------------------------------------------------------- /lambda/custom/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./interfaces"; 2 | export * from "./POENinjaClient"; 3 | -------------------------------------------------------------------------------- /lambda/custom/interceptors/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Localization"; 2 | export * from "./Slots"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/gempricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/mappricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/questrewards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/currencypricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/divinationpricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/essencepricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/fragmentpricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/normalitempricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/prophecypricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquemappricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Unknown"; 2 | export * from "./API"; 3 | export * from "./Unexpected"; 4 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueaccessorypricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquearmourpricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueflaskpricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquejewelpricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueweaponpricecheck/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./InProgress"; 2 | export * from "./Completed"; 3 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "lambda" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "lambda/**/*.spec.ts" 8 | ], 9 | "exec": "ts-node ./lambda/local/index.ts" 10 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.10" 4 | before_install: 5 | - npm install codecov -g 6 | install: 7 | - npm install 8 | script: 9 | - npm run build 10 | - codecov -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | dist 3 | .coverage 4 | 5 | # Dependency directories 6 | /**/node_modules 7 | 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* -------------------------------------------------------------------------------- /slots/WHAT_IS.csv: -------------------------------------------------------------------------------- 1 | what is, 2 | what's, 3 | what are, 4 | which is, 5 | which are, 6 | tell me, 7 | give, 8 | give me, 9 | get, 10 | get me, 11 | find, 12 | find me, 13 | how much, 14 | how much is, 15 | how much are, -------------------------------------------------------------------------------- /lambda/custom/intents/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Launch"; 2 | export * from "./Help"; 3 | export * from "./Stop"; 4 | export * from "./SessionEnded"; 5 | export * from "./Fallback"; 6 | export * from "./SystemExceptionEncountered"; 7 | 8 | export * from "./Debug"; 9 | -------------------------------------------------------------------------------- /__tests__/enUS/Map.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Maps", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.MapPriceCheck, 7 | slotName: SlotTypes.Map, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/Essence.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Essences", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.EssencePriceCheck, 7 | slotName: SlotTypes.Essence, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/Prophecy.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Prophecies", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.ProphecyPriceCheck, 7 | slotName: SlotTypes.Prophecy, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/UniqueMap.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Unique Maps", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.UniqueMapPriceCheck, 7 | slotName: SlotTypes.UniqueMap, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/Currency.spec.ts: -------------------------------------------------------------------------------- 1 | import { CurrencyItemTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Currencies", () => { 5 | CurrencyItemTest({ 6 | intentName: IntentTypes.CurrencyPriceCheck, 7 | slotName: SlotTypes.Currency, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/Divination.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Divinations", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.DivinationPriceCheck, 7 | slotName: SlotTypes.Divination, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/Fragment.spec.ts: -------------------------------------------------------------------------------- 1 | import { CurrencyItemTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Fragments", () => { 5 | CurrencyItemTest({ 6 | intentName: IntentTypes.FragmentPriceCheck, 7 | slotName: SlotTypes.Fragment, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/UniqueFlask.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Unique Flasks", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.UniqueFlaskPriceCheck, 7 | slotName: SlotTypes.UniqueFlask, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/UniqueJewel.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Unique Jewels", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.UniqueJewelPriceCheck, 7 | slotName: SlotTypes.UniqueJewel, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /__tests__/enUS/UniqueAccessory.spec.ts: -------------------------------------------------------------------------------- 1 | import { GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Unique Accessories", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.UniqueAccessoryPriceCheck, 7 | slotName: SlotTypes.UniqueAccessory, 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /lambda/custom/intents/Debug.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | 3 | export const Debug: RequestHandler = { 4 | canHandle(handlerInput) { 5 | console.log(JSON.stringify(handlerInput, null, 2)); 6 | 7 | return false; 8 | }, 9 | handle(handlerInput) { 10 | return handlerInput.responseBuilder 11 | .getResponse(); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lambda/custom/intents/mappricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.MapPriceCheck, 7 | slotName: SlotTypes.Map, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/questrewards/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.QuestReward, 7 | slotName: SlotTypes.Quest, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/currencypricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.CurrencyPriceCheck, 7 | slotName: SlotTypes.Currency, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/essencepricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.EssencePriceCheck, 7 | slotName: SlotTypes.Essence, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/fragmentpricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.FragmentPriceCheck, 7 | slotName: SlotTypes.Fragment, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/prophecypricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.ProphecyPriceCheck, 7 | slotName: SlotTypes.Prophecy, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/divinationpricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.DivinationPriceCheck, 7 | slotName: SlotTypes.Divination, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquemappricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.UniqueMapPriceCheck, 7 | slotName: SlotTypes.UniqueMap, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueflaskpricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.UniqueFlaskPriceCheck, 7 | slotName: SlotTypes.UniqueFlask, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquejewelpricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.UniqueJewelPriceCheck, 7 | slotName: SlotTypes.UniqueJewel, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquearmourpricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.UniqueArmourPriceCheck, 7 | slotName: SlotTypes.UniqueArmour, 8 | }); 9 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueweaponpricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.UniqueWeaponPriceCheck, 7 | slotName: SlotTypes.UniqueWeapon, 8 | }); 9 | -------------------------------------------------------------------------------- /slots/LEAGUE.csv: -------------------------------------------------------------------------------- 1 | Hardcore,Hardcore 2 | Standard,Standard 3 | Hardcore Incursion,tmphardcore,Incursion Hardcore,Challenge Hardcore,Hardcore Challenge,Current Hardcore 4 | Incursion,tmpstandard,Current,Challenge 5 | Flashback Event,flashbackevent,Incursion Flashback Event,Incursion Event,Flashback 6 | Hardcore Flashback Event,flashbackeventhd,Hardcore Incursion Flashback,Hardcore Incursion Event,Hardcore Incursion Flashback Event,Hardcore Flashback -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueaccessorypricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateNormalItemInProgressHandler } from "../normalitempricecheck"; 4 | 5 | export const InProgress: RequestHandler = CreateNormalItemInProgressHandler({ 6 | intentName: IntentTypes.UniqueAccessoryPriceCheck, 7 | slotName: SlotTypes.UniqueAccessory, 8 | }); 9 | -------------------------------------------------------------------------------- /slots/UNIQUE_MAP.csv: -------------------------------------------------------------------------------- 1 | Acton's Nightmare, 2 | Caer Blaidd Wolfpack's Den, 3 | Death and Taxes, 4 | Hall of Grandmasters, 5 | Hallowed Ground, 6 | Maelström of Chaos, 7 | Mao Kun, 8 | Oba's Cursed Trove, 9 | Olmec's Sanctum, 10 | Pillars of Arun, 11 | Poorjoy's Asylum, 12 | The Beachhead, 13 | The Coward's Trial, 14 | The Perandus Manor, 15 | The Putrid Cloister, 16 | The Twilight Temple, 17 | The Vinktar Square, 18 | Vaults of Atziri, 19 | Whakawairua Tuahu, -------------------------------------------------------------------------------- /lambda/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ask-sdk-core": "^2.0.7", 13 | "ask-sdk-model": "^1.3.1", 14 | "i18next": "^11.3.5", 15 | "i18next-sprintf-postprocessor": "^0.2.2", 16 | "request": "^2.87.0" 17 | } 18 | } -------------------------------------------------------------------------------- /lambda/custom/intents/SessionEnded.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { IsType } from "../lib/helpers"; 3 | import { RequestTypes } from "../lib/constants"; 4 | 5 | export const SessionEnded: RequestHandler = { 6 | canHandle(handlerInput) { 7 | return IsType(handlerInput, RequestTypes.SessionEnded); 8 | }, 9 | handle(handlerInput) { 10 | //any cleanup logic goes here 11 | return handlerInput.responseBuilder.getResponse(); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lambda/custom/intents/mappricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.MapPriceCheck, 8 | slotName: SlotTypes.Map, 9 | requestType: ItemRequestTypes.Map, 10 | }); 11 | -------------------------------------------------------------------------------- /__tests__/enUS/Launch.spec.ts: -------------------------------------------------------------------------------- 1 | import { skill, ssml, RequestWithType } from "../helpers"; 2 | import { RequestTypes, LocaleTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Launch", () => { 5 | it("should work", async () => { 6 | const response = await skill(RequestWithType({ 7 | type: RequestTypes.Launch, 8 | locale: LocaleTypes.enUS, 9 | })); 10 | expect(response).toMatchObject(ssml(/Welcome to P.O.E. Helper/gi)); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /__tests__/enUS/UniqueArmour.spec.ts: -------------------------------------------------------------------------------- 1 | import { LinkedItemTest, GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Unique Armours", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.UniqueArmourPriceCheck, 7 | slotName: SlotTypes.UniqueArmour, 8 | }); 9 | 10 | LinkedItemTest({ 11 | intentName: IntentTypes.UniqueArmourPriceCheck, 12 | slotName: SlotTypes.UniqueArmour, 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /__tests__/enUS/UniqueWeapon.spec.ts: -------------------------------------------------------------------------------- 1 | import { LinkedItemTest, GenericTest } from "../generic"; 2 | import { IntentTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Unique Weapons", () => { 5 | GenericTest({ 6 | intentName: IntentTypes.UniqueWeaponPriceCheck, 7 | slotName: SlotTypes.UniqueWeapon, 8 | }); 9 | 10 | LinkedItemTest({ 11 | intentName: IntentTypes.UniqueWeaponPriceCheck, 12 | slotName: SlotTypes.UniqueWeapon, 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /lambda/custom/intents/essencepricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.EssencePriceCheck, 8 | slotName: SlotTypes.Essence, 9 | requestType: ItemRequestTypes.Essence, 10 | }); 11 | -------------------------------------------------------------------------------- /__tests__/enUS/Errors.spec.ts: -------------------------------------------------------------------------------- 1 | import { skill, ssml, RequestWithIntent } from "../helpers"; 2 | import { IntentTypes, LocaleTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("Errors", () => { 5 | it("Unknown", async () => { 6 | const response = await skill(RequestWithIntent({ 7 | name: "Intent" as IntentTypes, 8 | locale: LocaleTypes.enUS, 9 | })); 10 | expect(response).toMatchObject(ssml(/Sorry, I can't understand the command/gi)); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /lambda/custom/intents/prophecypricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.ProphecyPriceCheck, 8 | slotName: SlotTypes.Prophecy, 9 | requestType: ItemRequestTypes.Prophecy, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquemappricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.UniqueMapPriceCheck, 8 | slotName: SlotTypes.UniqueMap, 9 | requestType: ItemRequestTypes.UniqueMap, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/currencypricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CurrencyRequestTypes } from "../../api"; 4 | import { CreateCurrencyItemCompletedHandler } from "../currencyitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateCurrencyItemCompletedHandler({ 7 | intentName: IntentTypes.CurrencyPriceCheck, 8 | slotName: SlotTypes.Currency, 9 | requestType: CurrencyRequestTypes.Currency, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/divinationpricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.DivinationPriceCheck, 8 | slotName: SlotTypes.Divination, 9 | requestType: ItemRequestTypes.DivinationCard, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/fragmentpricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CurrencyRequestTypes } from "../../api"; 4 | import { CreateCurrencyItemCompletedHandler } from "../currencyitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateCurrencyItemCompletedHandler({ 7 | intentName: IntentTypes.FragmentPriceCheck, 8 | slotName: SlotTypes.Fragment, 9 | requestType: CurrencyRequestTypes.Fragment, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquearmourpricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateLinkedItemCompletedHandler } from "../linkeditempricecheck"; 4 | import { ItemRequestTypes } from "../../api"; 5 | 6 | export const Completed: RequestHandler = CreateLinkedItemCompletedHandler({ 7 | intentName: IntentTypes.UniqueArmourPriceCheck, 8 | slotName: SlotTypes.UniqueArmour, 9 | requestType: ItemRequestTypes.UniqueArmour, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueflaskpricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.UniqueFlaskPriceCheck, 8 | slotName: SlotTypes.UniqueFlask, 9 | requestType: ItemRequestTypes.UniqueFlask, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniquejewelpricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.UniqueJewelPriceCheck, 8 | slotName: SlotTypes.UniqueJewel, 9 | requestType: ItemRequestTypes.UniqueJewel, 10 | }); 11 | -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueweaponpricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { CreateLinkedItemCompletedHandler } from "../linkeditempricecheck"; 4 | import { ItemRequestTypes } from "../../api"; 5 | 6 | export const Completed: RequestHandler = CreateLinkedItemCompletedHandler({ 7 | intentName: IntentTypes.UniqueWeaponPriceCheck, 8 | slotName: SlotTypes.UniqueWeapon, 9 | requestType: ItemRequestTypes.UniqueWeapon, 10 | }); 11 | -------------------------------------------------------------------------------- /slots/UNIQUE_FLASK.csv: -------------------------------------------------------------------------------- 1 | Lavianga's Spirit, 2 | Vessel of Vinktar, 3 | Dying Sun, 4 | Zerphi's Last Breath, 5 | Lion's Roar, 6 | Kiara's Determination, 7 | Taste of Hate, 8 | Witchfire Brew, 9 | Rumi's Concoction, 10 | The Sorrow of the Divine, 11 | Atziri's Promise, 12 | The Overflowing Chalice, 13 | Coralito's Signature, 14 | Divination Distillate, 15 | The Writhing Jar, 16 | Forbidden Taste, 17 | Doedre's Elixir, 18 | Soul Ripper, 19 | Sin's Rebirth, 20 | Blood of the Karui, 21 | Soul Catcher, 22 | The Wise Oak, 23 | Coruscating Elixir, 24 | Rotgut, -------------------------------------------------------------------------------- /lambda/custom/intents/uniqueaccessorypricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 3 | import { ItemRequestTypes } from "../../api"; 4 | import { CreateNormalItemCompletedHandler } from "../normalitempricecheck"; 5 | 6 | export const Completed: RequestHandler = CreateNormalItemCompletedHandler({ 7 | intentName: IntentTypes.UniqueAccessoryPriceCheck, 8 | slotName: SlotTypes.UniqueAccessory, 9 | requestType: ItemRequestTypes.UniqueAccessory, 10 | }); 11 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var ts = require("gulp-typescript"); 3 | var tsProject = ts.createProject("tsconfig.json"); 4 | 5 | var OUT_DIR = "dist"; 6 | var IN_DIR = "lambda"; 7 | 8 | // compile typescript 9 | gulp.task("compile", function () { 10 | return tsProject.src() 11 | .pipe(tsProject()) 12 | .js.pipe(gulp.dest(OUT_DIR)); 13 | }); 14 | 15 | // copy json files (e.g. localization json) 16 | gulp.task("json", function () { 17 | return gulp.src(IN_DIR + "/**/*.json").pipe(gulp.dest(OUT_DIR)); 18 | }); 19 | 20 | gulp.task("default", gulp.parallel(["compile", "json"])); -------------------------------------------------------------------------------- /lambda/custom/intents/Stop.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { IntentTypes, Strings } from "../lib/constants"; 3 | import { IsIntent, GetRequestAttributes } from "../lib/helpers"; 4 | 5 | export const Stop: RequestHandler = { 6 | canHandle(handlerInput) { 7 | return IsIntent(handlerInput, IntentTypes.Stop, IntentTypes.Cancel); 8 | }, 9 | handle(handlerInput) { 10 | const { tr } = GetRequestAttributes(handlerInput); 11 | 12 | const speechText = tr(Strings.GOODBYE_MSG); 13 | 14 | return handlerInput.responseBuilder 15 | .speak(speechText) 16 | .getResponse(); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /lambda/custom/intents/Help.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { IsIntent, GetRequestAttributes } from "../lib/helpers"; 3 | import { IntentTypes, Strings } from "../lib/constants"; 4 | 5 | export const Help: RequestHandler = { 6 | canHandle(handlerInput) { 7 | return IsIntent(handlerInput, IntentTypes.Help); 8 | }, 9 | handle(handlerInput) { 10 | const { t } = GetRequestAttributes(handlerInput); 11 | 12 | const speechText = t(Strings.HELP_MSG); 13 | 14 | return handlerInput.responseBuilder 15 | .speak(speechText) 16 | .reprompt(speechText) 17 | .getResponse(); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /lambda/custom/intents/SystemExceptionEncountered.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { RequestTypes } from "../lib/constants"; 3 | import { IsType } from "../lib/helpers"; 4 | 5 | export const SystemExceptionEncountered: RequestHandler = { 6 | canHandle(handlerInput) { 7 | return IsType(handlerInput, RequestTypes.SystemExceptionEncountered); 8 | }, 9 | handle(handlerInput) { 10 | console.log("\n******************* EXCEPTION **********************"); 11 | console.log("\n" + JSON.stringify(handlerInput.requestEnvelope, null, 2)); 12 | 13 | return handlerInput.responseBuilder 14 | .getResponse(); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /lambda/custom/intents/Launch.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { RequestTypes, Strings } from "../lib/constants"; 3 | import { IsType, GetRequestAttributes } from "../lib/helpers"; 4 | 5 | export const Launch: RequestHandler = { 6 | canHandle(handlerInput) { 7 | return IsType(handlerInput, RequestTypes.Launch); 8 | }, 9 | handle(handlerInput) { 10 | const { t } = GetRequestAttributes(handlerInput); 11 | 12 | const speechText = t(Strings.WELCOME_MSG); 13 | 14 | return handlerInput.responseBuilder 15 | .speak(speechText) 16 | .reprompt(t(Strings.HELP_MSG)) 17 | .getResponse(); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /lambda/custom/intents/Fallback.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { IsIntent, GetRequestAttributes } from "../lib/helpers"; 3 | import { IntentTypes, Strings } from "../lib/constants"; 4 | 5 | export const Fallback: RequestHandler = { 6 | canHandle(handlerInput) { 7 | return IsIntent(handlerInput, IntentTypes.Fallback); 8 | }, 9 | handle(handlerInput) { 10 | const { t } = GetRequestAttributes(handlerInput); 11 | 12 | const speechText = t(Strings.ERROR_MSG); 13 | 14 | return handlerInput.responseBuilder 15 | .speak(speechText) 16 | .reprompt(t(Strings.HELP_MSG)) 17 | .getResponse(); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /lambda/custom/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ask-sdk-core": { 8 | "version": "2.0.7", 9 | "resolved": "https://registry.npmjs.org/ask-sdk-core/-/ask-sdk-core-2.0.7.tgz", 10 | "integrity": "sha512-L0YoF7ls0iUoo/WYDYj7uDjAFThYZSDjzF8YvLHIEZyzKVgrNNqxetRorUB+odDoctPWW7RV1xcep8F4p7c1jg==" 11 | }, 12 | "ask-sdk-model": { 13 | "version": "1.3.1", 14 | "resolved": "https://registry.npmjs.org/ask-sdk-model/-/ask-sdk-model-1.3.1.tgz", 15 | "integrity": "sha512-ZDcmJ8sDRAzfIPz5WhRpy8HJ8SheBOyjoeHtYIcoVO6ZQEgxXtZ11GJGg8FhRDfwwIWj5Ma8G6m7OCgo4nuJDA==" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lambda/custom/errors/API.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from "ask-sdk-core"; 2 | import { GetRequestAttributes } from "../lib/helpers"; 3 | import { Strings, ErrorTypes } from "../lib/constants"; 4 | 5 | /** 6 | * Handles ErrorTypes.API errors which should be thrown whenever there is 7 | * an API error. 8 | */ 9 | export const API: ErrorHandler = { 10 | canHandle(_, error) { 11 | return error.name === ErrorTypes.API; 12 | }, 13 | handle(handlerInput, error) { 14 | console.log(`Error handled: ${error.message}`); 15 | 16 | const { t } = GetRequestAttributes(handlerInput); 17 | 18 | return handlerInput.responseBuilder 19 | .speak(t(Strings.ERROR_API_MSG)) 20 | .getResponse(); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "lib": [ 6 | "es6" 7 | ], 8 | "moduleResolution": "node", 9 | "rootDir": "lambda", 10 | "outDir": "dist", 11 | "sourceMap": false, 12 | "allowJs": false, 13 | "noImplicitAny": true, 14 | "noUnusedLocals": true, 15 | "noImplicitThis": true, 16 | "strictNullChecks": true, 17 | "noImplicitReturns": true, 18 | "preserveConstEnums": true, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "strict": true 22 | }, 23 | "exclude": [ 24 | "/**/node_modules", 25 | "lambda/local/", 26 | "lambda/tools/", 27 | "__tests__", 28 | "**/__mocks__" 29 | ] 30 | } -------------------------------------------------------------------------------- /lambda/custom/errors/Unexpected.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from "ask-sdk-core"; 2 | import { GetRequestAttributes } from "../lib/helpers"; 3 | import { Strings, ErrorTypes } from "../lib/constants"; 4 | 5 | /** 6 | * Handles ErrorTypes.Unexpected errors which should be thrown when something 7 | * unexpected happens. 8 | */ 9 | export const Unexpected: ErrorHandler = { 10 | canHandle(_, error) { 11 | return error.name === ErrorTypes.Unexpected; 12 | }, 13 | handle(handlerInput, error) { 14 | console.log(`Error handled: ${error.message}`); 15 | 16 | const { t } = GetRequestAttributes(handlerInput); 17 | 18 | return handlerInput.responseBuilder 19 | .speak(t(Strings.ERROR_UNEXPECTED_MSG)) 20 | .getResponse(); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /lambda/custom/interceptors/Slots.ts: -------------------------------------------------------------------------------- 1 | import { RequestInterceptor } from "ask-sdk-core"; 2 | import { RequestAttributes } from "../interfaces"; 3 | import { RequestTypes } from "../lib/constants"; 4 | import { GetSlotValues } from "../lib/helpers"; 5 | 6 | /** 7 | * Parses and adds the slot values to the RequestAttributes. 8 | */ 9 | export const Slots: RequestInterceptor = { 10 | process(handlerInput) { 11 | const attributes = handlerInput.attributesManager.getRequestAttributes() as RequestAttributes; 12 | 13 | if (handlerInput.requestEnvelope.request.type === RequestTypes.Intent) { 14 | attributes.slots = GetSlotValues(handlerInput.requestEnvelope.request.intent.slots); 15 | } else { 16 | attributes.slots = {}; 17 | } 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /lambda/custom/errors/Unknown.ts: -------------------------------------------------------------------------------- 1 | import { ErrorHandler } from "ask-sdk-core"; 2 | import { GetRequestAttributes } from "../lib/helpers"; 3 | import { Strings } from "../lib/constants"; 4 | 5 | /** 6 | * Handles unknown errors. Should be placed at the end, as it will catch 7 | * all errors. 8 | */ 9 | export const Unknown: ErrorHandler = { 10 | canHandle() { 11 | return true; 12 | }, 13 | handle(handlerInput, error) { 14 | console.log(`Error handled: ${error.message}`); 15 | 16 | const { t } = GetRequestAttributes(handlerInput); 17 | 18 | const speechText = t(Strings.ERROR_MSG); 19 | 20 | return handlerInput.responseBuilder 21 | .speak(speechText) 22 | .reprompt(speechText) 23 | .getResponse(); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.ask/config: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_settings": { 3 | "default": { 4 | "skill_id": "", 5 | "was_cloned": false, 6 | "merge": { 7 | "manifest": { 8 | "apis": { 9 | "custom": { 10 | "endpoint": { 11 | "uri": "alexa-poe-helper-default" 12 | } 13 | } 14 | } 15 | } 16 | } 17 | }, 18 | "local": { 19 | "skill_id": "", 20 | "was_cloned": false, 21 | "merge": { 22 | "manifest": { 23 | "apis": { 24 | "custom": { 25 | "endpoint": { 26 | "uri": "https://YOUR_URL.ngrok.io", 27 | "sslCertificateType": "Wildcard" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /__tests__/enUS/POENinjaClient.spec.ts: -------------------------------------------------------------------------------- 1 | import { POENinjaClient, LeagueTypes, CurrencyRequestTypes, ItemRequestTypes } from "../../lambda/custom/api"; 2 | import { CurrentDate } from "../../lambda/custom/lib/helpers"; 3 | 4 | describe("API Client", () => { 5 | const apiClient = new POENinjaClient(); 6 | 7 | it("Currencies", async () => { 8 | const res = await apiClient.currencies({ 9 | league: LeagueTypes.Challenge, 10 | type: CurrencyRequestTypes.Currency, 11 | date: CurrentDate(), 12 | }); 13 | 14 | expect(res).not.toBeNull(); 15 | expect(res.lines.length).not.toBe(0); 16 | }); 17 | 18 | it("Items", async () => { 19 | const res = await apiClient.items({ 20 | league: LeagueTypes.Challenge, 21 | type: ItemRequestTypes.Essence, 22 | date: CurrentDate(), 23 | }); 24 | 25 | expect(res).not.toBeNull(); 26 | expect(res.lines.length).not.toBe(0); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /slots/FRAGMENT.csv: -------------------------------------------------------------------------------- 1 | Sacrifice at Dusk,dusk,Dusk 2 | Sacrifice at Midnight,mid,Midnight 3 | Sacrifice at Dawn,dawn,Dawn 4 | Sacrifice at Noon,noon,Noon 5 | Mortal Grief,grie,Grief 6 | Mortal Rage,rage,Rage 7 | Mortal Hope,hope,Hope 8 | Mortal Ignorance,ign,Ignorance 9 | Eber's Key,eber 10 | Yriel's Key,yriel 11 | Inya's Key,inya 12 | Volkuur's Key,volkuur 13 | Fragment of the Hydra,hydra,Hydra,Hydra Fragment 14 | Fragment of the Phoenix,phoenix,Phoenix,Phoenix Fragment 15 | Fragment of the Minotaur,minot,Minotaur,Minotaur Fragment 16 | Fragment of the Chimera,chimer,Chimera,Chimera Fragment 17 | Sacrifice Set,sacrifice-set 18 | Mortal Set,mortal-set 19 | Pale Court Set,pale-court-set 20 | Shaper Set,shaper-set 21 | Xoph's Breachstone,xophs-breachstone 22 | Tul's Breachstone,tuls-breachstone 23 | Esh's Breachstone,eshs-breachstone 24 | Uul-Netol's Breachstone,uul-breachstone 25 | Chayula's Breachstone,chayulas-breachstone 26 | Offering to the Goddess,offer,Offering 27 | Divine Vessel,divine-vessel,Vessel 28 | Ancient Reliquary Key,ancient-reliquary-key -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mihail Cristian Dumitru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lambda/local/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import * as bodyParser from "body-parser"; 3 | import { AddressInfo } from "net"; 4 | import { LambdaHandler } from "ask-sdk-core/dist/skill/factory/BaseSkillFactory"; 5 | import { RequestEnvelope } from "ask-sdk-model"; 6 | 7 | import { handler as helloHandler } from "../custom"; 8 | 9 | function CreateHandler(handler: LambdaHandler): express.RequestHandler { 10 | return (req, res) => { 11 | handler(req.body as RequestEnvelope, null, (err, result) => { 12 | if (err) { 13 | return res.status(500).send(err); 14 | } 15 | return res.status(200).json(result); 16 | }); 17 | }; 18 | } 19 | 20 | // create server 21 | const server = express(); 22 | const listener = server.listen(process.env.port || process.env.PORT || 3980, function () { 23 | const { address, port } = listener.address() as AddressInfo; 24 | console.log('%s listening to %s%s', server.name, address, port); 25 | }); 26 | 27 | // parse json 28 | server.use(bodyParser.json()); 29 | 30 | // connect the lambda functions to http 31 | server.post("/", CreateHandler(helloHandler)); 32 | -------------------------------------------------------------------------------- /lambda/custom/intents/questrewards/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { GetRequestAttributes, IsIntentWithCompleteDialog, CreateError } from "../../lib/helpers"; 3 | import { SlotTypes, IntentTypes, ErrorTypes, Strings } from "../../lib/constants"; 4 | 5 | export const Completed: RequestHandler = { 6 | canHandle(handlerInput) { 7 | return IsIntentWithCompleteDialog(handlerInput, IntentTypes.QuestReward); 8 | }, 9 | handle(handlerInput) { 10 | const { t, slots } = GetRequestAttributes(handlerInput); 11 | 12 | const slot = slots[SlotTypes.Quest]; 13 | 14 | if (slot && slot.isMatch && !slot.isAmbiguous) { 15 | const rewards = t(Strings.QUEST_REWARDS) 16 | const reward = rewards[slot.resolved]; 17 | 18 | const speechText = t(Strings.QUEST_REWARD_MSG, slot.resolved, reward); 19 | 20 | return handlerInput.responseBuilder 21 | .speak(speechText) 22 | .getResponse(); 23 | } 24 | 25 | throw CreateError(`Got to the COMPLETED state of ${IntentTypes.QuestReward} without a slot.`, ErrorTypes.Unexpected); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /lambda/custom/interceptors/Localization.ts: -------------------------------------------------------------------------------- 1 | import { RequestInterceptor } from "ask-sdk-core"; 2 | import * as i18n from "i18next"; 3 | import * as sprintf from "i18next-sprintf-postprocessor"; 4 | import { strings } from "../lib/strings"; 5 | import { Random } from "../lib/helpers"; 6 | import { RequestAttributes } from "../interfaces"; 7 | 8 | type TranslationFunction = (...args: any[]) => string; 9 | 10 | /** 11 | * Adds translation functions to the RequestAttributes. 12 | */ 13 | export const Localization: RequestInterceptor = { 14 | process(handlerInput) { 15 | const localizationClient = i18n.use(sprintf).init({ 16 | lng: handlerInput.requestEnvelope.request.locale, 17 | overloadTranslationOptionHandler: sprintf.overloadTranslationOptionHandler, 18 | resources: strings, 19 | returnObjects: true, 20 | }); 21 | 22 | const attributes = handlerInput.attributesManager.getRequestAttributes() as RequestAttributes; 23 | attributes.t = function (...args: any[]) { 24 | return (localizationClient.t as TranslationFunction)(...args); 25 | }; 26 | attributes.tr = function (key: any) { 27 | const result = localizationClient.t(key) as string[]; 28 | 29 | return Random(result); 30 | }; 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /__tests__/enUS/BuiltIn.spec.ts: -------------------------------------------------------------------------------- 1 | import { skill, ssml, RequestWithIntent } from "../helpers"; 2 | import { IntentTypes, LocaleTypes } from "../../lambda/custom/lib/constants"; 3 | 4 | describe("BuiltIn Intents", () => { 5 | it("Help", async () => { 6 | const response = await skill(RequestWithIntent({ 7 | name: IntentTypes.Help, 8 | locale: LocaleTypes.enUS, 9 | })); 10 | expect(response).toMatchObject(ssml(/You can ask for/gi)); 11 | }); 12 | 13 | it("Stop", async () => { 14 | const response = await skill(RequestWithIntent({ 15 | name: IntentTypes.Stop, 16 | locale: LocaleTypes.enUS, 17 | })); 18 | expect(response).toMatchObject(ssml(/(May your maps be merciful|Good luck with your maps exile|Stay sane)/gi)); 19 | }); 20 | 21 | it("Cancel", async () => { 22 | const response = await skill(RequestWithIntent({ 23 | name: IntentTypes.Cancel, 24 | locale: LocaleTypes.enUS, 25 | })); 26 | expect(response).toMatchObject(ssml(/(May your maps be merciful|Good luck with your maps exile|Stay sane)/gi)); 27 | }); 28 | 29 | it("Fallback", async () => { 30 | const response = await skill(RequestWithIntent({ 31 | name: IntentTypes.Fallback, 32 | locale: LocaleTypes.enUS, 33 | })); 34 | expect(response).toMatchObject(ssml(/Sorry, I can't understand the command/gi)); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /slots/QUEST.csv: -------------------------------------------------------------------------------- 1 | Einhar's Hunt, 2 | Einhar's Bestiary, 3 | Einhar's Menagerie, 4 | The Caged Brute, 5 | Mercy Mission, 6 | Breaking Some Eggs, 7 | The Siren's Cadence, 8 | Enemy at the Gate, 9 | A Dirty Job, 10 | The Dweller of the Deep, 11 | The Marooned Mariner, 12 | The Way Forward, 13 | Intruders in Black, 14 | Through Sacred Ground, 15 | The Great White Beast, 16 | Sharp and Cruel, 17 | Deal with the Bandits, 18 | The Ribbon Spool, 19 | Sever the Right Hand, 20 | Lost in Love, 21 | A Fixture of Fate, 22 | Piety's Pets, 23 | Victario's Secrets, 24 | A Swig of Hope, 25 | Fiery Dust, 26 | Breaking the Seal, 27 | An Indomitable Spirit, 28 | The Eternal Nightmare, 29 | The King's Feast, 30 | In Service to Science, 31 | Kitava's Torments, 32 | Death to Purity, 33 | The Key to Freedom, 34 | Return to Oriath, 35 | Bestel's Epic, 36 | Fallen from Grace, 37 | The Cloven One, 38 | The Father of War, 39 | The Puppet Mistress, 40 | Essence of Umbra, 41 | In Memory of Greust, 42 | The Silver Locket, 43 | Kishara's Star, 44 | Queen of Despair, 45 | The Master of a Million Faces, 46 | Essence of the Artist, 47 | Web of Secrets, 48 | Love is Dead, 49 | Reflection of Terror, 50 | The Gemling Legion, 51 | The Wings of Vastiri, 52 | Essence of the Hag, 53 | The Storm Blade, 54 | Fastis Fortuna, 55 | Queen of the Sands, 56 | The Ruler of Highgate, 57 | Safe Passage, 58 | Death and Rebirth, 59 | No Love for Old Ghosts, 60 | An End to Hunger, 61 | Vilenta's Vengeance, 62 | Map to Tsoatha, 63 | From Nightmare into Dream, -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alexa-poe-helper", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "build": "npm run clean && npm run test:coverage && gulp", 9 | "deploy": "npm run build && ask deploy", 10 | "deploy:lambda": "npm run build && ask deploy --target lambda", 11 | "deploy:local": "ask deploy --target skill --profile local", 12 | "start": "nodemon", 13 | "generate-item-names": "ts-node lambda/tools/generateItemNames.ts", 14 | "test": "jest", 15 | "test:watch": "jest --watch", 16 | "test:coverage": "jest --coverage" 17 | }, 18 | "jest": { 19 | "testEnvironment": "node", 20 | "transform": { 21 | "^.+\\.tsx?$": "ts-jest" 22 | }, 23 | "moduleFileExtensions": [ 24 | "ts", 25 | "tsx", 26 | "js" 27 | ], 28 | "testMatch": [ 29 | "**/*.spec.ts" 30 | ], 31 | "coverageDirectory": ".coverage" 32 | }, 33 | "author": "Mihail Cristian Dumitru", 34 | "license": "MIT", 35 | "dependencies": { 36 | "ask-sdk-core": "^2.0.7", 37 | "ask-sdk-model": "^1.3.1", 38 | "i18next": "^11.3.5", 39 | "i18next-sprintf-postprocessor": "^0.2.2", 40 | "request": "^2.87.0" 41 | }, 42 | "devDependencies": { 43 | "@types/node": "^10.5.2", 44 | "@types/express": "^4.16.0", 45 | "@types/i18next": "^8.4.3", 46 | "@types/i18next-sprintf-postprocessor": "^0.0.29", 47 | "@types/request": "2.47.0", 48 | "@types/jest": "^23.3.0", 49 | "@types/lodash": "^4.14.112", 50 | "express": "^4.16.3", 51 | "gulp": "^4.0.0", 52 | "gulp-typescript": "^4.0.1", 53 | "rimraf": "^2.6.2", 54 | "ts-node": "^5.0.1", 55 | "typescript": "^2.9.2", 56 | "nodemon": "^1.17.5", 57 | "jest": "^23.6.0", 58 | "ts-jest": "^23.0.1", 59 | "lodash": "^4.17.10" 60 | } 61 | } -------------------------------------------------------------------------------- /lambda/custom/api/POENinjaClient.ts: -------------------------------------------------------------------------------- 1 | import { CurrencyRequest, CurrencyResponse, ItemRequest, ItemResponse, IPOENinjaClientSettings, IPOENinjaClient } from "./interfaces"; 2 | import * as request from "request"; 3 | 4 | const defaultSettings: IPOENinjaClientSettings = { 5 | baseUrl: "https://poe.ninja", 6 | }; 7 | 8 | export class POENinjaClient implements IPOENinjaClient { 9 | protected settings: IPOENinjaClientSettings; 10 | 11 | constructor(settings?: IPOENinjaClientSettings) { 12 | if (!settings) { 13 | settings = defaultSettings; 14 | } 15 | this.settings = defaultSettings; 16 | } 17 | 18 | request(options: (request.UriOptions & request.CoreOptions)): Promise { 19 | return new Promise((fulfill, reject) => { 20 | return request(options, (err, res, body) => { 21 | if (err) return reject(err); 22 | if (res.statusCode !== 200) { 23 | return reject(new Error(`Unexpected status code ${res.statusCode}`)); 24 | } 25 | return fulfill(body as T); 26 | }); 27 | }); 28 | } 29 | 30 | currencies(req: CurrencyRequest) { 31 | return this.request({ 32 | method: "GET", 33 | baseUrl: this.settings.baseUrl, 34 | uri: "/api/data/currencyoverview", 35 | qs: req, 36 | gzip: true, 37 | json: true, 38 | }); 39 | } 40 | 41 | items(req: ItemRequest) { 42 | return this.request({ 43 | method: "GET", 44 | baseUrl: this.settings.baseUrl, 45 | uri: "/api/data/itemoverview", 46 | qs: req, 47 | gzip: true, 48 | json: true, 49 | }); 50 | } 51 | 52 | } 53 | 54 | export const apiClient = new POENinjaClient(); 55 | -------------------------------------------------------------------------------- /lambda/custom/intents/normalitempricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { IntentRequest } from "ask-sdk-model"; 3 | import { IsIntentWithIncompleteDialog, GetRequestAttributes, DisambiguateSlot, ResetUnmatchedSlotValues, TryToResolveValue } from "../../lib/helpers"; 4 | 5 | /** 6 | * Creates a Dialog handler with incomplete state. 7 | * 8 | * @param options 9 | */ 10 | export function CreateNormalItemInProgressHandler(options: { 11 | intentName: string; 12 | slotName: string; 13 | }): RequestHandler { 14 | return { 15 | canHandle(handlerInput) { 16 | return IsIntentWithIncompleteDialog(handlerInput, options.intentName); 17 | }, 18 | handle(handlerInput) { 19 | const request = handlerInput.requestEnvelope.request as IntentRequest; 20 | const currentIntent = request.intent; 21 | 22 | const { slots } = GetRequestAttributes(handlerInput); 23 | 24 | const slot = slots[options.slotName]; 25 | 26 | // if we have a match but it's ambiguous 27 | // ask for clarification 28 | if (slot && slot.isMatch && slot.isAmbiguous) { 29 | // try to resolve the value 30 | if (!TryToResolveValue(currentIntent, slot)) { 31 | // otherwise try to disambiguate it 32 | return DisambiguateSlot(handlerInput, slot); 33 | } 34 | } 35 | 36 | // reset any unmatched slots 37 | ResetUnmatchedSlotValues(handlerInput, slots); 38 | 39 | // let Alexa reprompt the user 40 | // or switch to the completed handler if it's done 41 | return handlerInput.responseBuilder 42 | .addDelegateDirective(currentIntent) 43 | .getResponse(); 44 | } 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /__tests__/enUS/QuestReward.spec.ts: -------------------------------------------------------------------------------- 1 | import { IntentTypes, LocaleTypes, SlotTypes } from "../../lambda/custom/lib/constants"; 2 | import { skill, ssml, CreateIntentRequest, inProgressDelegate } from "../helpers"; 3 | 4 | describe("Quest reward", () => { 5 | const name = IntentTypes.QuestReward; 6 | const locale = LocaleTypes.enUS; 7 | 8 | it("InProgress match", async () => { 9 | const request = CreateIntentRequest({ 10 | name: name, 11 | locale: locale, 12 | dialogState: "IN_PROGRESS", 13 | }); 14 | const response = await skill(request); 15 | expect(response).toMatchObject(inProgressDelegate(name)); 16 | }); 17 | 18 | it("InProgress ambiguous", async () => { 19 | const request = CreateIntentRequest({ 20 | name: name, 21 | locale: locale, 22 | dialogState: "IN_PROGRESS", 23 | slots: { 24 | [SlotTypes.Quest]: { 25 | resolutions: { 26 | status: "ER_SUCCESS_MATCH", 27 | values: [{ 28 | name: "Einhar's Hunt", 29 | }, 30 | { 31 | name: "Einhar's Bestiary", 32 | }, 33 | { 34 | name: "Einhar's Menagerie", 35 | }] 36 | } 37 | } 38 | } 39 | }); 40 | const response = await skill(request); 41 | expect(response).toMatchObject(ssml(/Which would you like: Einhar's Hunt, Einhar's Bestiary or Einhar's Menagerie\?/gi)); 42 | }); 43 | 44 | it("Completed", async () => { 45 | const request = CreateIntentRequest({ 46 | name: name, 47 | locale: locale, 48 | dialogState: "COMPLETED", 49 | slots: { 50 | [SlotTypes.Quest]: { 51 | resolutions: { 52 | status: "ER_SUCCESS_MATCH", 53 | values: [{ 54 | name: "Vilenta's Vengeance", 55 | }] 56 | } 57 | } 58 | } 59 | }); 60 | const response = await skill(request); 61 | expect(response).toMatchObject(ssml(/The reward for 'Vilenta's Vengeance' is: Book of Skill/gi)); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /slots/UNIQUE_JEWEL.csv: -------------------------------------------------------------------------------- 1 | Anatomical Knowledge, 2 | Ancient Waystones, 3 | Apparitions, 4 | Assassin's Haste, 5 | Atziri's Reign, 6 | Blood Sacrifice, 7 | Brawn, 8 | Brittle Barrier, 9 | Brute Force Solution, 10 | Careful Planning, 11 | Cheap Construction, 12 | Chill of Corruption, 13 | Clear Mind, 14 | Coated Shrapnel, 15 | Cold Steel, 16 | Collateral Damage, 17 | Combat Focus, 18 | Combustibles, 19 | Conqueror's Efficiency, 20 | Conqueror's Longevity, 21 | Conqueror's Potency, 22 | Corrupted Energy, 23 | Dead Reckoning, 24 | Efficient Training, 25 | Eldritch Knowledge, 26 | Emperor's Cunning, 27 | Emperor's Mastery, 28 | Emperor's Might, 29 | Emperor's Wit, 30 | Energised Armour, 31 | Energy From Within, 32 | Fertile Mind, 33 | Fevered Mind, 34 | Fight for Survival, 35 | Fireborn, 36 | First Snow, 37 | Fluid Motion, 38 | Fortified Legion, 39 | Fragile Bloom, 40 | Fragility, 41 | From Dust, 42 | Frozen Trail, 43 | Grand Spectrum, 44 | Growing Agony, 45 | Hair Trigger, 46 | Hazardous Research, 47 | Healthy Mind, 48 | Hidden Potential, 49 | Hotfooted, 50 | Hungry Abyss, 51 | Inertia, 52 | Inevitability, 53 | Inspired Learning, 54 | Intuitive Leap, 55 | Izaro's Turmoil, 56 | Lioneye's Fall, 57 | Malicious Intent, 58 | Mantra of Flames, 59 | Martial Artistry, 60 | Might and Influence, 61 | Might in All Forms, 62 | Might of the Meek, 63 | Mutated Growth, 64 | Omen on the Winds, 65 | Overwhelming Odds, 66 | Pacifism, 67 | Pitch Darkness, 68 | Poacher's Aim, 69 | Powerlessness, 70 | Primordial Eminence, 71 | Primordial Harmony, 72 | Primordial Might, 73 | Pugilist, 74 | Pure Talent, 75 | Rain of Splinters, 76 | Rapid Expansion, 77 | Reckless Defence, 78 | Ring of Blades, 79 | Rolling Flames, 80 | Sacrificial Harvest, 81 | Self-Flagellation, 82 | Shattered Chains, 83 | Soul's Wick, 84 | Spire of Stone, 85 | Spirit Guards, 86 | Spirited Response, 87 | Spreading Rot, 88 | Static Electricity, 89 | Steel Spirit, 90 | Sudden Ignition, 91 | Survival Instincts, 92 | Survival Secrets, 93 | Survival Skills, 94 | Tempered Flesh, 95 | Tempered Mind, 96 | Tempered Spirit, 97 | The Anima Stone, 98 | The Blue Dream, 99 | The Blue Nightmare, 100 | The Golden Rule, 101 | The Green Dream, 102 | The Green Nightmare, 103 | The Long Winter, 104 | The Red Dream, 105 | The Red Nightmare, 106 | The Vigil, 107 | To Dust, 108 | Transcendent Flesh, 109 | Transcendent Mind, 110 | Transcendent Spirit, 111 | Unending Hunger, 112 | Unstable Payload, 113 | Vaal Sentencing, 114 | Violent Dead, 115 | Volley Fire, 116 | Warlord's Reach, 117 | Watcher's Eye, 118 | Weight of Sin, 119 | Weight of the Empire, 120 | Wildfire, 121 | Winter Burial, 122 | Winter's Bounty, -------------------------------------------------------------------------------- /lambda/custom/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Slot, slu, SlotConfirmationStatus } from "ask-sdk-model"; 2 | 3 | export interface RequestAttributes { 4 | /** 5 | * Searches for the translation of the given key, replaces the arguments 6 | * and returns the result. 7 | * 8 | * @param key 9 | * @param args 10 | */ 11 | t(key: string, ...args: any[]): any; 12 | 13 | /** 14 | * Randomly picks a translation for the given key and returns it. 15 | * 16 | * Note: The value for the key must be an array. 17 | * 18 | * @param key 19 | */ 20 | tr(key: string): string; 21 | 22 | /** 23 | * The slot values for the current request. 24 | */ 25 | slots: SlotValues; 26 | 27 | // [key: string]: any; 28 | } 29 | 30 | export interface SessionAttributes { 31 | // [key: string]: any; 32 | } 33 | 34 | export type Slots = { [key: string]: Slot }; 35 | 36 | /** 37 | * A matched slot value (if `status.code` = "ER_SUCCESS_MATCH"). 38 | */ 39 | export interface MatchedSlotValue { 40 | /** 41 | * Name of the slot. 42 | */ 43 | name: string; 44 | 45 | /** 46 | * Value that the user said (unresolved). 47 | */ 48 | value: string; 49 | 50 | /** 51 | * `statis.code` = "ER_SUCCESS_MATCH" 52 | */ 53 | isMatch: true; 54 | 55 | /** 56 | * The first resolved value. 57 | */ 58 | resolved: string; 59 | 60 | /** 61 | * The first resolved id. 62 | */ 63 | id: string; 64 | 65 | /** 66 | * `True` if there are multiple resolved values. 67 | */ 68 | isAmbiguous: boolean; 69 | 70 | /** 71 | * All resolved values. If there are multiple values, `isAmbiguous` will be `true`. 72 | */ 73 | values: slu.entityresolution.Value[]; 74 | 75 | /** 76 | * Whether the user has explicitly confirmed or denied the value of this slot. 77 | */ 78 | confirmationStatus: SlotConfirmationStatus; 79 | } 80 | 81 | /** 82 | * An unmatched slot value (if `status.code` != "ER_SUCCESS_MATCH"). 83 | */ 84 | export interface UnmatchedSlotValue { 85 | /** 86 | * Name of the slot. 87 | */ 88 | name: string; 89 | 90 | /** 91 | * Value that the user said (unresolved). 92 | */ 93 | value: string; 94 | 95 | /** 96 | * `statis.code` != "ER_SUCCESS_MATCH" 97 | */ 98 | isMatch: false; 99 | 100 | /** 101 | * Whether the user has explicitly confirmed or denied the value of this slot. 102 | */ 103 | confirmationStatus: SlotConfirmationStatus 104 | } 105 | 106 | export interface SlotValues { 107 | [key: string]: MatchedSlotValue | UnmatchedSlotValue | undefined; 108 | } 109 | -------------------------------------------------------------------------------- /slots/UNIQUE_ACCESSORY.csv: -------------------------------------------------------------------------------- 1 | Bisco's Collar, 2 | Perandus Signet, 3 | Shavronne's Revelation, 4 | Presence of Chayula, 5 | Astramentis, 6 | Gifts from Above, 7 | Choir of the Storm, 8 | Yoke of Suffering, 9 | Thief's Torment, 10 | Kaom's Way, 11 | Voidheart, 12 | Essence Worm, 13 | Kaom's Sign, 14 | Death Rush, 15 | Victario's Acuity, 16 | Berek's Pass, 17 | Ngamahu Tiki, 18 | Carnage Heart, 19 | Rigwald's Crest, 20 | Andvarius, 21 | Karui Charge, 22 | Night's Hold, 23 | Valyrium, 24 | The Pariah, 25 | Darkness Enthroned, 26 | Perseverance, 27 | Call of the Brotherhood, 28 | Kikazaru, 29 | Ascent From Flesh, 30 | Le Heup of All, 31 | Blackheart, 32 | Eye of Chayula, 33 | Dream Fragments, 34 | Emberwake, 35 | Auxium, 36 | The Ascetic, 37 | Bisco's Leash, 38 | Maligaro's Restraint, 39 | Meginord's Girdle, 40 | Timeclasp, 41 | The Magnate, 42 | Bated Breath, 43 | Pyre, 44 | Warped Timepiece, 45 | Doedre's Damning, 46 | Heartbound Loop, 47 | Belt of the Deceiver, 48 | Lori's Lantern, 49 | Shaper's Seed, 50 | Prismweave, 51 | Soulthirst, 52 | The Warden's Brand, 53 | Ventor's Gamble, 54 | Stone of Lazhwar, 55 | Maligaro's Cruelty, 56 | Marylene's Fallacy, 57 | The Anvil, 58 | The Ignomon, 59 | Ungil's Harmony, 60 | Ming's Heart, 61 | Immortal Flesh, 62 | Rashkaldor's Patience, 63 | Sunblast, 64 | Extractor Mentis, 65 | Mokou's Embrace, 66 | Sibyl's Lament, 67 | Sidhebreath, 68 | Karui Ward, 69 | Atziri's Foible, 70 | Wurm's Molt, 71 | Praxis, 72 | Soul Tether, 73 | Araku Tiki, 74 | Tear of Purity, 75 | Bloodboil, 76 | Perandus Blazon, 77 | Demigod's Bounty, 78 | Talisman of the Victor, 79 | Eyes of the Greatwolf, 80 | Headhunter, 81 | Demigod's Eye, 82 | Berek's Respite, 83 | The Taming, 84 | Natural Hierarchy, 85 | Xoph's Blood, 86 | Xoph's Heart, 87 | Impresence, 88 | Coward's Legacy, 89 | Blightwell, 90 | Voll's Devotion, 91 | The Retch, 92 | Voice of the Storm, 93 | Coward's Chains, 94 | Daresso's Salute, 95 | Berek's Grip, 96 | The Flow Untethered, 97 | Hinekora's Sight, 98 | Valako's Sign, 99 | Star of Wraeclast, 100 | Mark of the Shaper, 101 | The Pandemonius, 102 | The Tactician, 103 | Faminebind, 104 | Feastbind, 105 | The Nomad, 106 | The Halcyon, 107 | Rigwald's Curse, 108 | String of Servitude, 109 | The Effigon, 110 | First Piece of Time, 111 | Mark of the Elder, 112 | Umbilicus Immortalis, 113 | Blood of Corruption, 114 | Eye of Innocence, 115 | The Hungry Loop, 116 | Timetwist, 117 | Redblade Band, 118 | Snakepit, 119 | Tasalio's Sign, 120 | Stormfire, 121 | Voideye, 122 | Ngamahu's Sign, 123 | Cyclopean Coil, 124 | Second Piece of Time, 125 | Brinerot Mark, 126 | Winterweave, 127 | Doryani's Invitation, 128 | Winterheart, 129 | Sacrificial Heart, 130 | Mutewind Seal, 131 | Ryslatha's Coil, 132 | Bloodgrip, 133 | Malachai's Artifice, 134 | Romira's Banquet, 135 | Gloomfang, 136 | Gluttony, 137 | Dyadian Dawn, 138 | The Aylardex, 139 | Zerphi's Heart, -------------------------------------------------------------------------------- /slots/ESSENCE.csv: -------------------------------------------------------------------------------- 1 | Deafening Essence of Anger, 2 | Deafening Essence of Anguish, 3 | Deafening Essence of Contempt, 4 | Deafening Essence of Doubt, 5 | Deafening Essence of Dread, 6 | Deafening Essence of Envy, 7 | Deafening Essence of Fear, 8 | Deafening Essence of Greed, 9 | Deafening Essence of Hatred, 10 | Deafening Essence of Loathing, 11 | Deafening Essence of Misery, 12 | Deafening Essence of Rage, 13 | Deafening Essence of Scorn, 14 | Deafening Essence of Sorrow, 15 | Deafening Essence of Spite, 16 | Deafening Essence of Suffering, 17 | Deafening Essence of Torment, 18 | Deafening Essence of Woe, 19 | Deafening Essence of Wrath, 20 | Deafening Essence of Zeal, 21 | Essence of Delirium, 22 | Essence of Horror, 23 | Essence of Hysteria, 24 | Essence of Insanity, 25 | Muttering Essence of Anger, 26 | Muttering Essence of Contempt, 27 | Muttering Essence of Fear, 28 | Muttering Essence of Greed, 29 | Muttering Essence of Hatred, 30 | Muttering Essence of Sorrow, 31 | Muttering Essence of Torment, 32 | Muttering Essence of Woe, 33 | Remnant of Corruption, 34 | Screaming Essence of Anger, 35 | Screaming Essence of Anguish, 36 | Screaming Essence of Contempt, 37 | Screaming Essence of Doubt, 38 | Screaming Essence of Dread, 39 | Screaming Essence of Envy, 40 | Screaming Essence of Fear, 41 | Screaming Essence of Greed, 42 | Screaming Essence of Hatred, 43 | Screaming Essence of Loathing, 44 | Screaming Essence of Misery, 45 | Screaming Essence of Rage, 46 | Screaming Essence of Scorn, 47 | Screaming Essence of Sorrow, 48 | Screaming Essence of Spite, 49 | Screaming Essence of Suffering, 50 | Screaming Essence of Torment, 51 | Screaming Essence of Woe, 52 | Screaming Essence of Wrath, 53 | Screaming Essence of Zeal, 54 | Shrieking Essence of Anger, 55 | Shrieking Essence of Anguish, 56 | Shrieking Essence of Contempt, 57 | Shrieking Essence of Doubt, 58 | Shrieking Essence of Dread, 59 | Shrieking Essence of Envy, 60 | Shrieking Essence of Fear, 61 | Shrieking Essence of Greed, 62 | Shrieking Essence of Hatred, 63 | Shrieking Essence of Loathing, 64 | Shrieking Essence of Misery, 65 | Shrieking Essence of Rage, 66 | Shrieking Essence of Scorn, 67 | Shrieking Essence of Sorrow, 68 | Shrieking Essence of Spite, 69 | Shrieking Essence of Suffering, 70 | Shrieking Essence of Torment, 71 | Shrieking Essence of Woe, 72 | Shrieking Essence of Wrath, 73 | Shrieking Essence of Zeal, 74 | Wailing Essence of Anger, 75 | Wailing Essence of Anguish, 76 | Wailing Essence of Contempt, 77 | Wailing Essence of Doubt, 78 | Wailing Essence of Fear, 79 | Wailing Essence of Greed, 80 | Wailing Essence of Hatred, 81 | Wailing Essence of Loathing, 82 | Wailing Essence of Rage, 83 | Wailing Essence of Sorrow, 84 | Wailing Essence of Spite, 85 | Wailing Essence of Suffering, 86 | Wailing Essence of Torment, 87 | Wailing Essence of Woe, 88 | Wailing Essence of Wrath, 89 | Wailing Essence of Zeal, 90 | Weeping Essence of Anger, 91 | Weeping Essence of Contempt, 92 | Weeping Essence of Doubt, 93 | Weeping Essence of Fear, 94 | Weeping Essence of Greed, 95 | Weeping Essence of Hatred, 96 | Weeping Essence of Rage, 97 | Weeping Essence of Sorrow, 98 | Weeping Essence of Suffering, 99 | Weeping Essence of Torment, 100 | Weeping Essence of Woe, 101 | Weeping Essence of Wrath, 102 | Whispering Essence of Contempt, 103 | Whispering Essence of Greed, 104 | Whispering Essence of Hatred, 105 | Whispering Essence of Woe, -------------------------------------------------------------------------------- /slots/CURRENCY.csv: -------------------------------------------------------------------------------- 1 | Mirror of Kalandra,mir,Mirror,Kalandra,Mirrors,Mirrors of Kalandra 2 | Mirror Shard,mirror-shard,Mirror Shards 3 | Exalted Orb,exa,Exalted,Exalt,Exalts,Exalted Orbs 4 | Exalted Shard,exalted-shard,Exalted Shards 5 | Chaos Orb,chaos,Chaos 6 | Ancient Orb,ancient-orb,Ancient,Ancient Orbs 7 | Harbinger's Orb,harbingers-orb,Harbinger,Harbinger's,Harbinger's Orbs,Harbinger Orbs 8 | Divine Orb,divine,Divine,Divines 9 | Orb of Annulment,orb-of-annulment,Annulment,Annulments,Orbs of Annulment 10 | Master Cartographer's Sextant,master-sextant,Master,Master Sextant,Master Sextants 11 | Orb of Horizons,orb-of-horizons,Horizons,Orbs of Horizons 12 | Journeyman Cartographer's Sextant,journeyman-sextant,Journeyman,Journeyman Sextant,Journeyman Sextants 13 | Apprentice Cartographer's Sextant,apprentice-sextant,Apprentice,Apprentice Sextant,Apprentice Sextants 14 | Engineer's Orb,engineers-orb,Engineer,Engineer's,Engineers,Engineer's Orbs 15 | Vaal Orb,vaal,Vaal 16 | Blessing of Chayula,blessing-chayula,Blessings of Chayula 17 | Blessing of Uul-Netol,blessing-uul-netol,Blessings of Uul-Netol 18 | Blessing of Esh,blessing-esh,Esh's Blessing,Blessings of Esh 19 | Blessing of Xoph,blessing-xoph,Xoph's Blessing,Blessings of Xoph 20 | Blessing of Tul,blessing-tul,Tul's Blessing,Blessings of Tul 21 | Splinter of Chayula,splinter-chayula,Chayula Splinter,Splinters of Chayula,Chayula Splinters 22 | Splinter of Xoph,splinter-xoph,Xoph Splinter,Splinters of Xoph,Xoph Splinters 23 | Splinter of Esh,splinter-esh,Esh Splinter,Splinters of Esh,Esh Splinters 24 | Splinter of Uul-Netol,splinter-uul,Uul-Netol Splinter,Splinters of Uul-Netol,Uul-Netol Splinters 25 | Splinter of Tul,splinter-tul,Tul Splinter,Splinters of Tul,Tul Splinters 26 | Regal Orb,regal,Regal,Regals,Regal Orbs 27 | Gemcutter's Prism,gcp,Gemcutter,Gemcutter's,Gemcutters,Prisms,G. C. P. 28 | Orb of Regret,regret,Regret,Regrets,Orbs of Regret 29 | Cartographer's Chisel,chisel,Chisel,Chisels,Cartographer's Chisels 30 | Orb of Fusing,fuse,Fuse,Fusing,Fusings,Orbs of Fusing 31 | Orb of Alchemy,alch,Alchemy,Alchemies,Orbs of Alchemy 32 | Orb of Scouring,scour,Scouring,Scourings,Orbs of Scouring 33 | Blessed Orb,blessed,Blessed Orbs 34 | Silver Coin,silver,Silver Coins 35 | Orb of Binding,orb-of-binding,Orbs of Binding 36 | Glassblower's Bauble,ba,Glassblower,Glassblower's,Bauble 37 | Chromatic Orb,chrom,Chrom,Chrome,Chromatic,Chromes,Chromatics,Chromatic Orbs 38 | Jeweller's Orb,jew,Jeweller,Jeweller's,Jewellers,Jeweller's Orbs 39 | Orb of Alteration,alt,Alteration,Alteration Orb,Alterations,Orbs of Alteration,Alteration Orbs 40 | Orb of Chance,chance,Chance,Chance Orb,Orbs of Chance,Chance Orbs 41 | Orb of Augmentation,aug,Augmentation,Augmentation Orb,Orbs of Augmentation,Augmentation Orbs 42 | Orb of Transmutation,tra,Transmutation,Transmutation Orb,Transmutations,Transmutation Orbs,Orbs of Transmutation 43 | Blacksmith's Whetstone,whe,Blacksmith,Blacksmith's 44 | Armourer's Scrap,scr,Armourer,Armourer's,Scraps 45 | Perandus Coin,p,Perandus,Perandus Coins 46 | Portal Scroll,port,Portal,Portals,Portal Scrolls 47 | Scroll of Wisdom,wis,Wisdom,Wisdom Scroll,Wisdom Scrolls,Scrolls of Wisdom 48 | Eternal Orb,ete,Eternal,Eternal Orbs 49 | Annulment Shard,annulment-shard,Annulment Shards 50 | Binding Shard,binding-shard,Binding Shards 51 | Horizon Shard,horizon-shard,Horizon Shards 52 | Harbinger's Shard,harbingers-shard,Harbinger Shards,Harbinger's Shards 53 | Engineer's Shard,engineers-shard,Engineer Shards,Engineer's Shards 54 | Ancient Shard,ancient-shard,Ancient Shards 55 | Chaos Shard,chaos-shard,Chaos Shards 56 | Regal Shard,regal-shard,Regal Shards -------------------------------------------------------------------------------- /lambda/custom/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export enum RequestTypes { 2 | Launch = "LaunchRequest", 3 | Intent = "IntentRequest", 4 | SessionEnded = "SessionEndedRequest", 5 | SystemExceptionEncountered = "System.ExceptionEncountered", 6 | } 7 | 8 | export enum IntentTypes { 9 | Help = "AMAZON.HelpIntent", 10 | Stop = "AMAZON.StopIntent", 11 | Cancel = "AMAZON.CancelIntent", 12 | Fallback = "AMAZON.FallbackIntent", 13 | 14 | QuestReward = "QuestRewardIntent", 15 | CurrencyPriceCheck = "CurrencyPriceCheckIntent", 16 | FragmentPriceCheck = "FragmentPriceCheckIntent", 17 | UniqueAccessoryPriceCheck = "UniqueAccessoryPriceCheckIntent", 18 | UniqueArmourPriceCheck = "UniqueArmourPriceCheckIntent", 19 | UniqueWeaponPriceCheck = "UniqueWeaponPriceCheckIntent", 20 | UniqueFlaskPriceCheck = "UniqueFlaskPriceCheckIntent", 21 | UniqueJewelPriceCheck = "UniqueJewelPriceCheckIntent", 22 | MapPriceCheck = "MapPriceCheckIntent", 23 | UniqueMapPriceCheck = "UniqueMapPriceCheckIntent", 24 | EssencePriceCheck = "EssencePriceCheckIntent", 25 | DivinationPriceCheck = "DivinationPriceCheckIntent", 26 | ProphecyPriceCheck = "ProphecyPriceCheckIntent", 27 | GemPriceCheck = "GemPriceCheckIntent", 28 | } 29 | 30 | export enum SlotTypes { 31 | Quest = "quest", 32 | Quantity = "quantity", 33 | Currency = "currency", 34 | Fragment = "fragment", 35 | League = "league", 36 | UniqueAccessory = "uniqueaccessory", 37 | UniqueArmour = "uniquearmour", 38 | Links = "links", 39 | UniqueWeapon = "uniqueweapon", 40 | UniqueFlask = "uniqueflask", 41 | UniqueJewel = "uniquejewel", 42 | Map = "map", 43 | UniqueMap = "uniquemap", 44 | Essence = "essence", 45 | Divination = "divination", 46 | Prophecy = "prophecy", 47 | Gem = "gem", 48 | Level = "level", 49 | Quality = "quality", 50 | } 51 | 52 | export enum ErrorTypes { 53 | Unknown = "UnknownError", 54 | Unexpected = "UnexpectedError", 55 | API = "APIError", 56 | } 57 | 58 | export enum LocaleTypes { 59 | deDE = "de-DE", 60 | enAU = "en-AU", 61 | enCA = "en-CA", 62 | enGB = "en-GB", 63 | enIN = "en-IN", 64 | enUS = "en-US", 65 | esES = "es-ES", 66 | frFR = "fr-FR", 67 | itIT = "it-IT", 68 | jaJP = "ja-JP", 69 | } 70 | 71 | export enum Strings { 72 | SKILL_NAME = "SKILL_NAME", 73 | WELCOME_MSG = "WELCOME_MSG", 74 | GOODBYE_MSG = "GOODBYE_MSG", 75 | HELP_MSG = "HELP_MSG", 76 | ERROR_MSG = "ERROR_MSG", 77 | ERROR_UNEXPECTED_MSG = "ERROR_UNEXPECTED_MSG", 78 | SELECT_ONE_MSG = "SELECT_ONE_MSG", 79 | OR_MSG = "OR_MSG", 80 | CHECKING_PRICE_OF_MSG = "CHECKING_PRICE_OF_MSG", 81 | PRICE_OF_IS_MSG = "PRICE_OF_IS_MSG", 82 | PRICE_OF_IS_EXALTED_MSG = "PRICE_OF_IS_EXALTED_MSG", 83 | LINKED = "LINKED", 84 | LEVEL = "LEVEL", 85 | QUALITY = "QUALITY", 86 | ERROR_NOT_ENOUGH_DATA_MSG = "ERROR_NOT_ENOUGH_DATA_MSG", 87 | ERROR_CURRENCY_NOT_FOUND_MSG = "ERROR_CURRENCY_NOT_FOUND_MSG", 88 | ERROR_ITEM_NOT_FOUND_MSG = "ERROR_ITEM_NOT_FOUND_MSG", 89 | ERROR_API_MSG = "ERROR_API_MSG", 90 | QUEST_REWARD_MSG = "QUEST_REWARD_MSG", 91 | QUEST_REWARDS = "QUEST_REWARDS", 92 | } 93 | 94 | export enum LeagueSlotTypes { 95 | Challenge = "Incursion", 96 | HardcoreChallenge = "Hardcore Incursion", 97 | Standard = "Standard", 98 | Hardcore = "Hardcore", 99 | IncursionEvent = "Flashback Event", 100 | HarcoreIncursionEvent = "Hardcore Flashback Event", 101 | } 102 | -------------------------------------------------------------------------------- /lambda/custom/intents/normalitempricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { ItemRequestTypes, apiClient, LeagueTypeSlotToAPIMap } from "../../api"; 3 | import { IsIntentWithCompleteDialog, GetRequestAttributes, GetLeagueSlot, CurrentDate, IsHighConfidenceItemPrice, CreateError, FormatPrice } from "../../lib/helpers"; 4 | import { Strings, ErrorTypes } from "../../lib/constants"; 5 | 6 | /** 7 | * Creates an item Dialog handler with complete state. 8 | * 9 | * @param options 10 | */ 11 | export function CreateNormalItemCompletedHandler(options: { 12 | intentName: string; 13 | slotName: string; 14 | requestType: ItemRequestTypes; 15 | }): RequestHandler { 16 | return { 17 | canHandle(handlerInput) { 18 | return IsIntentWithCompleteDialog(handlerInput, options.intentName); 19 | }, 20 | async handle(handlerInput) { 21 | const { t, slots } = GetRequestAttributes(handlerInput); 22 | 23 | const item = slots[options.slotName]; 24 | 25 | if (item && item.isMatch && !item.isAmbiguous) { 26 | try { 27 | const league = GetLeagueSlot(slots); 28 | 29 | // get the item prices 30 | const res = await apiClient.items({ 31 | league: LeagueTypeSlotToAPIMap[league], 32 | type: options.requestType, 33 | date: CurrentDate(), 34 | }); 35 | 36 | // filter out low confidence elements 37 | res.lines = res.lines.filter(IsHighConfidenceItemPrice); 38 | 39 | // search for the item that the user requested 40 | for (let itemDetails of res.lines) { 41 | if (itemDetails.name === item.resolved) { 42 | const exaltedValue = itemDetails.exaltedValue; 43 | const chaosValue = itemDetails.chaosValue; 44 | 45 | // only include the exalted price equivalent if it's higher than 1 46 | if (exaltedValue >= 1) { 47 | return handlerInput.responseBuilder 48 | // TODO: - add plurals 49 | .speak(t(Strings.PRICE_OF_IS_EXALTED_MSG, 1, item.resolved, league, FormatPrice(exaltedValue).toString(), FormatPrice(chaosValue).toString())) // .toString() removes the trailing zeros 50 | .getResponse(); 51 | } 52 | 53 | // chaos only price 54 | return handlerInput.responseBuilder 55 | // TODO: - add plurals 56 | .speak(t(Strings.PRICE_OF_IS_MSG, 1, item.resolved, league, FormatPrice(itemDetails.chaosValue).toString())) // .toString() removes the trailing zeros 57 | .getResponse(); 58 | } 59 | } 60 | 61 | // item not found 62 | return handlerInput.responseBuilder 63 | .speak(t(Strings.ERROR_ITEM_NOT_FOUND_MSG)) 64 | .getResponse(); 65 | 66 | } catch (err) { 67 | throw CreateError(`Got error while getting ${options.requestType} prices: ${err}`, ErrorTypes.API) 68 | } 69 | } 70 | 71 | throw CreateError(`Got to the COMPLETED state of ${options.intentName} without a slot.`, ErrorTypes.Unexpected); 72 | } 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /lambda/tools/generateItemNames.ts: -------------------------------------------------------------------------------- 1 | import { CurrentDate } from "../custom/lib/helpers"; 2 | import { writeFileSync } from "fs"; 3 | import { POENinjaClient, ItemResponse, ItemEntity, ItemRequestTypes, LeagueTypes } from "../custom/api"; 4 | 5 | const client = new POENinjaClient(); 6 | 7 | function getUnique(res: ItemResponse[]) { 8 | const data = res 9 | // reduce it to an ItemEntity[] 10 | .reduce((acc, current) => { 11 | return current.lines.concat(acc); 12 | }, [] as ItemEntity[]) 13 | // take only the names 14 | .map((item) => item.name) 15 | // sanitize the names 16 | .map((item) => sanitize(item)) 17 | 18 | // extract unique 19 | return Array.from(new Set(data).keys()) 20 | // sort alphabetically 21 | .sort(); 22 | } 23 | 24 | function getRequestsForType(type: ItemRequestTypes) { 25 | return Promise.all([ 26 | client.items({ 27 | league: LeagueTypes.Challenge, 28 | type: type, 29 | date: CurrentDate(), 30 | }), 31 | client.items({ 32 | league: LeagueTypes.HardcoreChallenge, 33 | type: type, 34 | date: CurrentDate(), 35 | }), 36 | client.items({ 37 | league: LeagueTypes.Standard, 38 | type: type, 39 | date: CurrentDate(), 40 | }), 41 | client.items({ 42 | league: LeagueTypes.Hardcore, 43 | type: type, 44 | date: CurrentDate(), 45 | }), 46 | ]); 47 | } 48 | 49 | async function getUniqueForType(type: ItemRequestTypes) { 50 | const res = await getRequestsForType(type); 51 | 52 | return getUnique(res); 53 | } 54 | 55 | // async function printType(type: ItemRequestTypes) { 56 | // const unique = await getUniqueForType(type); 57 | // console.log(type, JSON.stringify(unique, null, 2)); 58 | // } 59 | 60 | async function stringForType(type: ItemRequestTypes, name: string) { 61 | const unique = await getUniqueForType(type); 62 | return `\nexport const ${name} = ${JSON.stringify(unique, null, 4)};\n`; 63 | console.log(type, JSON.stringify(unique, null, 2)); 64 | } 65 | 66 | function sanitize(str: string) { 67 | return str.replace(/,/g, ""); 68 | } 69 | 70 | async function main() { 71 | let finalString = `/** 72 | * THIS IS AN AUTOGENERATED FILE, DO NOT EDIT 73 | */\n`; 74 | 75 | finalString += await stringForType(ItemRequestTypes.Essence, "Essences"); 76 | finalString += await stringForType(ItemRequestTypes.DivinationCard, "Divinations"); 77 | finalString += await stringForType(ItemRequestTypes.UniqueMap, "UniqueMaps"); 78 | finalString += await stringForType(ItemRequestTypes.Prophecy, "Prophecies"); 79 | finalString += await stringForType(ItemRequestTypes.SkillGem, "SkillGems"); 80 | // finalString += await stringForType(ItemRequestTypes.HelmetEnchant, "HelmetEnchants"); 81 | finalString += await stringForType(ItemRequestTypes.Map, "Maps"); 82 | finalString += await stringForType(ItemRequestTypes.UniqueJewel, "UniqueJewels"); 83 | finalString += await stringForType(ItemRequestTypes.UniqueFlask, "UniqueFlasks"); 84 | finalString += await stringForType(ItemRequestTypes.UniqueWeapon, "UniqueWeapons"); 85 | finalString += await stringForType(ItemRequestTypes.UniqueArmour, "UniqueArmours"); 86 | finalString += await stringForType(ItemRequestTypes.UniqueAccessory, "UniqueAccessories"); 87 | 88 | console.log(finalString); 89 | 90 | const path = `${__dirname}/../custom/lib/items.ts`; 91 | 92 | writeFileSync(path, finalString); 93 | 94 | console.log(`Written to ${path}.`); 95 | } 96 | 97 | main(); 98 | -------------------------------------------------------------------------------- /lambda/custom/intents/gempricecheck/InProgress.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { IntentRequest } from "ask-sdk-model"; 3 | import { IsIntentWithIncompleteDialog, ResetUnmatchedSlotValues, ResetSlotValue, DisambiguateSlot, SetSlotValue, GetSlotValues, TryToResolveValue } from "../../lib/helpers"; 4 | import { SlotTypes, IntentTypes } from "../../lib/constants"; 5 | 6 | /** 7 | * Checks if the given value is in the form "2123". This happens if you say something 8 | * like "twenty one twenty three", Alexa will interpret it as a single number, so we 9 | * need to manually split it. If it detects this case, it will manually set the slot values. 10 | * 11 | * @param request 12 | * @param value 13 | */ 14 | function ParseLevelAndQualityInSingleSlot(request: IntentRequest, value?: string) { 15 | if (value && value.length === 4) { 16 | const first = value.substr(0, 2); 17 | const second = value.substr(2, 2); 18 | 19 | const firstValue = parseInt(first); 20 | const secondValue = parseInt(second); 21 | 22 | if (!isNaN(firstValue) && !isNaN(secondValue)) { 23 | SetSlotValue(request, SlotTypes.Level, first); 24 | SetSlotValue(request, SlotTypes.Quality, second); 25 | } 26 | } 27 | } 28 | 29 | export const InProgress: RequestHandler = { 30 | canHandle(handlerInput) { 31 | return IsIntentWithIncompleteDialog(handlerInput, IntentTypes.GemPriceCheck); 32 | }, 33 | handle(handlerInput) { 34 | const request = handlerInput.requestEnvelope.request as IntentRequest; 35 | const currentIntent = request.intent; 36 | 37 | if (currentIntent.slots) { 38 | const levelSlot = currentIntent.slots[SlotTypes.Level]; 39 | const qualitySlot = currentIntent.slots[SlotTypes.Quality]; 40 | 41 | if (levelSlot) { 42 | ParseLevelAndQualityInSingleSlot(request, levelSlot.value); 43 | } 44 | if (qualitySlot) { 45 | ParseLevelAndQualityInSingleSlot(request, qualitySlot.value); 46 | } 47 | } 48 | 49 | const slots = GetSlotValues(currentIntent.slots); 50 | 51 | const slot = slots[SlotTypes.Gem]; 52 | 53 | // if we have a match but it's ambiguous 54 | // ask for clarification 55 | if (slot && slot.isMatch && slot.isAmbiguous) { 56 | // try to resolve the value 57 | if (!TryToResolveValue(currentIntent, slot)) { 58 | // otherwise try to disambiguate it 59 | return DisambiguateSlot(handlerInput, slot); 60 | } 61 | } 62 | 63 | // validate the level and quality 64 | const levelSlot = slots[SlotTypes.Level]; 65 | const qualitySlot = slots[SlotTypes.Quality]; 66 | 67 | if (levelSlot && levelSlot.isMatch && !levelSlot.isAmbiguous) { 68 | const level = parseInt(levelSlot.value); 69 | if (level != 4 && (level < 20 || level > 21 || isNaN(level))) { 70 | ResetSlotValue(request, SlotTypes.Level); 71 | } 72 | } 73 | 74 | if (qualitySlot && qualitySlot.isMatch && !qualitySlot.isAmbiguous) { 75 | const quality = parseInt(qualitySlot.value); 76 | if (quality < 20 || quality > 23 || isNaN(quality)) { 77 | ResetSlotValue(request, SlotTypes.Quality); 78 | } 79 | } 80 | 81 | // reset any unmatched slots 82 | ResetUnmatchedSlotValues(handlerInput, slots); 83 | 84 | // let Alexa reprompt the user 85 | // or switch to the completed handler if it's done 86 | return handlerInput.responseBuilder 87 | .addDelegateDirective(currentIntent) 88 | .getResponse(); 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /lambda/custom/index.ts: -------------------------------------------------------------------------------- 1 | import * as Alexa from "ask-sdk-core"; 2 | import * as Intents from "./intents"; 3 | import * as Errors from "./errors"; 4 | import * as Interceptors from "./interceptors"; 5 | import * as QuestRewardIntents from "./intents/questrewards"; 6 | import * as CurrencyPriceCheckIntents from "./intents/currencypricecheck"; 7 | import * as FragmentPriceCheckIntents from "./intents/fragmentpricecheck"; 8 | import * as UniqueAccessoryPriceCheckIntents from "./intents/uniqueaccessorypricecheck"; 9 | import * as UniqueArmourPriceCheckIntents from "./intents/uniquearmourpricecheck"; 10 | import * as UniqueWeaponPriceCheckIntents from "./intents/uniqueweaponpricecheck"; 11 | import * as UniqueFlaskPriceCheckIntents from "./intents/uniqueflaskpricecheck"; 12 | import * as UniqueJewelPriceCheckIntents from "./intents/uniquejewelpricecheck"; 13 | import * as MapPriceCheckIntents from "./intents/mappricecheck"; 14 | import * as UniqueMapPriceCheckIntents from "./intents/uniquemappricecheck"; 15 | import * as EssencePriceCheckIntents from "./intents/essencepricecheck"; 16 | import * as DivinationPriceCheckIntents from "./intents/divinationpricecheck"; 17 | import * as ProphecyPriceCheckIntents from "./intents/prophecypricecheck"; 18 | import * as GemPriceCheckIntents from "./intents/gempricecheck"; 19 | 20 | export const handler = Alexa.SkillBuilders.custom() 21 | .addRequestHandlers( 22 | // Intents.Debug, 23 | 24 | // Default intents 25 | Intents.Launch, 26 | Intents.Help, 27 | Intents.Stop, 28 | Intents.SessionEnded, 29 | Intents.SystemExceptionEncountered, 30 | Intents.Fallback, 31 | 32 | // Quest reward intents 33 | QuestRewardIntents.InProgress, 34 | QuestRewardIntents.Completed, 35 | 36 | // Currency price check 37 | CurrencyPriceCheckIntents.InProgress, 38 | CurrencyPriceCheckIntents.Completed, 39 | 40 | // Fragment price check 41 | FragmentPriceCheckIntents.InProgress, 42 | FragmentPriceCheckIntents.Completed, 43 | 44 | // Unique accessory price check 45 | UniqueAccessoryPriceCheckIntents.InProgress, 46 | UniqueAccessoryPriceCheckIntents.Completed, 47 | 48 | // Unique armour price check 49 | UniqueArmourPriceCheckIntents.InProgress, 50 | UniqueArmourPriceCheckIntents.Completed, 51 | 52 | // Unique weapon price check 53 | UniqueWeaponPriceCheckIntents.InProgress, 54 | UniqueWeaponPriceCheckIntents.Completed, 55 | 56 | // Unique flask price check 57 | UniqueFlaskPriceCheckIntents.InProgress, 58 | UniqueFlaskPriceCheckIntents.Completed, 59 | 60 | // Unique jewel price check 61 | UniqueJewelPriceCheckIntents.InProgress, 62 | UniqueJewelPriceCheckIntents.Completed, 63 | 64 | // Map price check 65 | MapPriceCheckIntents.InProgress, 66 | MapPriceCheckIntents.Completed, 67 | 68 | // Unique map price check 69 | UniqueMapPriceCheckIntents.InProgress, 70 | UniqueMapPriceCheckIntents.Completed, 71 | 72 | // Essence price check 73 | EssencePriceCheckIntents.InProgress, 74 | EssencePriceCheckIntents.Completed, 75 | 76 | // Divination price check 77 | DivinationPriceCheckIntents.InProgress, 78 | DivinationPriceCheckIntents.Completed, 79 | 80 | // Prophecy price check 81 | ProphecyPriceCheckIntents.InProgress, 82 | ProphecyPriceCheckIntents.Completed, 83 | 84 | // Skill gem price check 85 | GemPriceCheckIntents.InProgress, 86 | GemPriceCheckIntents.Completed 87 | ) 88 | .addErrorHandlers( 89 | Errors.Unexpected, 90 | Errors.API, 91 | Errors.Unknown 92 | ) 93 | .addRequestInterceptors( 94 | Interceptors.Localization, 95 | Interceptors.Slots 96 | ) 97 | .lambda(); 98 | -------------------------------------------------------------------------------- /lambda/custom/intents/currencyitempricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { apiClient, CurrencyEntity, CurrencyRequestTypes, LeagueTypeSlotToAPIMap } from "../../api"; 3 | import { IsIntentWithCompleteDialog, GetRequestAttributes, GetLeagueSlot, CurrentDate, CreateError, FormatPrice } from "../../lib/helpers"; 4 | import { Strings, ErrorTypes, SlotTypes } from "../../lib/constants"; 5 | 6 | function filterLowConfidence(value: CurrencyEntity) { 7 | return value.receive && value.receive.count >= 5 || value.pay && value.pay.count >= 5; 8 | } 9 | 10 | /** 11 | * Creates a currency item Dialog handler with complete state. 12 | * 13 | * @param options 14 | */ 15 | export function CreateCurrencyItemCompletedHandler(options: { 16 | intentName: string; 17 | slotName: string; 18 | requestType: CurrencyRequestTypes; 19 | }): RequestHandler { 20 | return { 21 | canHandle(handlerInput) { 22 | return IsIntentWithCompleteDialog(handlerInput, options.intentName); 23 | }, 24 | async handle(handlerInput) { 25 | const { t, slots } = GetRequestAttributes(handlerInput); 26 | 27 | const currency = slots[options.slotName]; 28 | 29 | if (currency && currency.isMatch && !currency.isAmbiguous) { 30 | // get the league 31 | const league = GetLeagueSlot(slots); 32 | 33 | // get the quantity 34 | let quantity = 1; 35 | 36 | const quantitySlot = slots[SlotTypes.Quantity]; 37 | if (quantitySlot && quantitySlot.isMatch) { 38 | quantity = parseFloat(quantitySlot.value); 39 | if (quantity < 1 || isNaN(quantity)) { 40 | quantity = 1; 41 | } 42 | } 43 | 44 | try { 45 | // get the currency exchange details 46 | const res = await apiClient.currencies({ 47 | league: LeagueTypeSlotToAPIMap[league], 48 | type: options.requestType, 49 | date: CurrentDate(), 50 | }); 51 | 52 | // filter out low confidence elements 53 | res.lines = res.lines.filter(filterLowConfidence); 54 | 55 | // search for the currency that the user requested 56 | for (let exchange of res.lines) { 57 | if (exchange.currencyTypeName === currency.resolved) { 58 | // if we don't have `receive`, it means there is not enough data for that currency 59 | if (!exchange.receive) { 60 | return handlerInput.responseBuilder 61 | .speak(t(Strings.ERROR_NOT_ENOUGH_DATA_MSG)) 62 | .getResponse(); 63 | } 64 | 65 | const totalPrice = quantity * exchange.receive.value; 66 | 67 | // exchange found 68 | return handlerInput.responseBuilder 69 | // TODO: - add plurals 70 | .speak(t(Strings.PRICE_OF_IS_MSG, quantity, currency.resolved, league, FormatPrice(totalPrice).toString())) // .toString() removes the trailing zeros 71 | .getResponse(); 72 | } 73 | } 74 | 75 | // currency not found 76 | return handlerInput.responseBuilder 77 | .speak(t(Strings.ERROR_CURRENCY_NOT_FOUND_MSG)) 78 | .getResponse(); 79 | 80 | } catch (err) { 81 | throw CreateError(`Got error while getting currency exchange: ${err}`, ErrorTypes.API) 82 | } 83 | } 84 | 85 | throw CreateError(`Got to the COMPLETED state of ${options.intentName} without a slot.`, ErrorTypes.Unexpected); 86 | } 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /lambda/custom/api/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { LeagueSlotTypes } from "../lib/constants"; 2 | 3 | export enum LeagueTypes { 4 | Challenge = "Incursion", // "tmpstandard", 5 | HardcoreChallenge = "Hardcore Incursion", // "tmphardcore", 6 | Standard = "Standard", 7 | Hardcore = "Hardcore", 8 | IncursionEvent = "Incursion Event (IRE001)", 9 | HarcoreIncursionEvent = "Incursion Event HC (IRE002)", 10 | } 11 | 12 | export const LeagueTypeSlotToAPIMap: { [key: string]: LeagueTypes } = { 13 | [LeagueSlotTypes.Challenge]: LeagueTypes.Challenge, 14 | [LeagueSlotTypes.HardcoreChallenge]: LeagueTypes.HardcoreChallenge, 15 | [LeagueSlotTypes.Standard]: LeagueTypes.Standard, 16 | [LeagueSlotTypes.Hardcore]: LeagueTypes.Hardcore, 17 | [LeagueSlotTypes.IncursionEvent]: LeagueTypes.IncursionEvent, 18 | [LeagueSlotTypes.HarcoreIncursionEvent]: LeagueTypes.HarcoreIncursionEvent, 19 | }; 20 | 21 | export enum CurrencyRequestTypes { 22 | Currency = "Currency", 23 | Fragment = "Fragment", 24 | } 25 | 26 | export interface CurrencyRequest { 27 | league: LeagueTypes, 28 | type: CurrencyRequestTypes, 29 | date: string; 30 | } 31 | 32 | export interface CurrencyResponse { 33 | lines: CurrencyEntity[]; 34 | currencyDetails: CurrencyDetailsEntity[]; 35 | } 36 | 37 | export interface CurrencyEntity { 38 | currencyTypeName: string; 39 | pay?: ReceiveOrPay; 40 | receive?: ReceiveOrPay; 41 | paySparkLine: SparkLine; 42 | receiveSparkLine: SparkLine; 43 | chaosEquivalent: number; 44 | lowConfidencePaySparkLine: SparkLine; 45 | lowConfidenceReceiveSparkLine: SparkLine; 46 | } 47 | 48 | export interface ReceiveOrPay { 49 | id: number; 50 | league_id: number; 51 | pay_currency_id: number; 52 | get_currency_id: number; 53 | sample_time_utc: string; 54 | count: number; 55 | value: number; 56 | data_point_count: number; 57 | includes_secondary: boolean; 58 | } 59 | 60 | export interface SparkLine { 61 | data: number[]; 62 | totalChange: number; 63 | } 64 | 65 | export interface CurrencyDetailsEntity { 66 | id: number; 67 | icon: string; 68 | name: string; 69 | poeTradeId: number; 70 | } 71 | 72 | export enum ItemRequestTypes { 73 | Essence = "Essence", 74 | DivinationCard = "DivinationCard", 75 | UniqueMap = "UniqueMap", 76 | Prophecy = "Prophecy", 77 | SkillGem = "SkillGem", 78 | HelmetEnchant = "HelmetEnchant", 79 | Map = "Map", 80 | UniqueJewel = "UniqueJewel", 81 | UniqueFlask = "UniqueFlask", 82 | UniqueWeapon = "UniqueWeapon", 83 | UniqueArmour = "UniqueArmour", 84 | UniqueAccessory = "UniqueAccessory", 85 | } 86 | 87 | export interface ItemRequest { 88 | league: LeagueTypes, 89 | type: ItemRequestTypes, 90 | date: string; 91 | } 92 | 93 | export interface ItemResponse { 94 | lines: ItemEntity[]; 95 | } 96 | 97 | export interface ItemEntity { 98 | id: number; 99 | name: string; 100 | icon: string; 101 | mapTier: number; 102 | levelRequired: number; 103 | baseType?: string; 104 | stackSize: number; 105 | variant?: string; 106 | prophecyText?: string; 107 | artFilename?: string; 108 | links: number; 109 | itemClass: number; 110 | sparkline: SparkLine; 111 | lowConfidenceSparkline: SparkLine; 112 | implicitModifiers: ModifierEntity[]; 113 | explicitModifiers: ModifierEntity[]; 114 | flavourText: string; 115 | corrupted: boolean; 116 | gemLevel: number; 117 | gemQuality: number; 118 | itemType: string; 119 | chaosValue: number; 120 | exaltedValue: number; 121 | count: number; 122 | } 123 | 124 | export interface ModifierEntity { 125 | text: string; 126 | optional: boolean; 127 | } 128 | 129 | /** 130 | * APIClient 131 | */ 132 | 133 | export interface IPOENinjaClientSettings { 134 | baseUrl: string; 135 | } 136 | 137 | export declare class IPOENinjaClient { 138 | constructor(settings?: IPOENinjaClientSettings); 139 | currencies(req: CurrencyRequest): Promise; 140 | items(req: ItemRequest): Promise; 141 | } 142 | -------------------------------------------------------------------------------- /lambda/custom/intents/gempricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { GetRequestAttributes, IsIntentWithCompleteDialog, CreateError, FormatPrice, CurrentDate, GetLeagueSlot, IsHighConfidenceItemPrice } from "../../lib/helpers"; 3 | import { SlotTypes, IntentTypes, ErrorTypes, Strings } from "../../lib/constants"; 4 | import { ItemRequestTypes, apiClient, LeagueTypeSlotToAPIMap } from "../../api"; 5 | 6 | export const Completed: RequestHandler = { 7 | canHandle(handlerInput) { 8 | return IsIntentWithCompleteDialog(handlerInput, IntentTypes.GemPriceCheck); 9 | }, 10 | async handle(handlerInput) { 11 | const { t, slots } = GetRequestAttributes(handlerInput); 12 | 13 | const item = slots[SlotTypes.Gem]; 14 | const levelSlot = slots[SlotTypes.Level]; 15 | const qualitySlot = slots[SlotTypes.Quality]; 16 | 17 | if ((item && item.isMatch && !item.isAmbiguous) && 18 | (levelSlot && levelSlot.isMatch)) { 19 | // get the league 20 | const league = GetLeagueSlot(slots); 21 | 22 | // get the level 23 | const level = parseInt(levelSlot.value); 24 | 25 | // get the quality 26 | let quality: number | null = null; 27 | if (qualitySlot && qualitySlot.isMatch) { 28 | quality = parseInt(qualitySlot.value); 29 | } 30 | 31 | try { 32 | // get the item prices 33 | const res = await apiClient.items({ 34 | league: LeagueTypeSlotToAPIMap[league], 35 | type: ItemRequestTypes.SkillGem, 36 | date: CurrentDate(), 37 | }); 38 | 39 | // filter out low confidence elements 40 | res.lines = res.lines.filter(IsHighConfidenceItemPrice); 41 | 42 | // find all items with a matching name 43 | res.lines = res.lines.filter((i) => i.name === item.resolved); 44 | 45 | // filter by level 46 | res.lines = res.lines.filter((i) => i.gemLevel === level); 47 | 48 | // filter by quality 49 | if (quality) { 50 | res.lines = res.lines.filter((i) => i.gemQuality === quality); 51 | } 52 | 53 | // sort the item by price, cheaper first 54 | res.lines = res.lines.sort((a, b) => a.chaosValue - b.chaosValue); 55 | 56 | // search for the item that the user requested 57 | for (let itemDetails of res.lines) { 58 | const exaltedValue = itemDetails.exaltedValue; 59 | const chaosValue = itemDetails.chaosValue; 60 | let propsString = t(Strings.LEVEL, level); 61 | if (quality) { 62 | propsString += t(Strings.QUALITY, quality); 63 | } 64 | 65 | // only include the exalted price equivalent if it's higher than 1 66 | if (exaltedValue >= 1) { 67 | return handlerInput.responseBuilder 68 | // TODO: - add plurals 69 | .speak(t(Strings.PRICE_OF_IS_EXALTED_MSG, propsString, item.resolved, league, FormatPrice(exaltedValue).toString(), FormatPrice(chaosValue).toString())) // .toString() removes the trailing zeros 70 | .getResponse(); 71 | } 72 | 73 | // chaos only price 74 | return handlerInput.responseBuilder 75 | // TODO: - add plurals 76 | .speak(t(Strings.PRICE_OF_IS_MSG, propsString, item.resolved, league, FormatPrice(itemDetails.chaosValue).toString())) // .toString() removes the trailing zeros 77 | .getResponse(); 78 | } 79 | 80 | // item not found 81 | return handlerInput.responseBuilder 82 | .speak(t(Strings.ERROR_ITEM_NOT_FOUND_MSG)) 83 | .getResponse(); 84 | 85 | } catch (err) { 86 | throw CreateError(`Got error while getting skill gem prices: ${err}`, ErrorTypes.API) 87 | } 88 | } 89 | 90 | throw CreateError(`Got to the COMPLETED state of ${IntentTypes.GemPriceCheck} without a slot.`, ErrorTypes.Unexpected); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /lambda/custom/intents/linkeditempricecheck/Completed.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "ask-sdk-core"; 2 | import { GetRequestAttributes, IsIntentWithCompleteDialog, CreateError, FormatPrice, CurrentDate, GetLeagueSlot, IsHighConfidenceItemPrice, GetLinksSlot } from "../../lib/helpers"; 3 | import { ErrorTypes, Strings } from "../../lib/constants"; 4 | import { ItemRequestTypes, apiClient, LeagueTypeSlotToAPIMap } from "../../api"; 5 | 6 | /** 7 | * Creates a linked item Dialog handler with complete state. 8 | * 9 | * @param options 10 | */ 11 | export function CreateLinkedItemCompletedHandler(options: { 12 | intentName: string; 13 | slotName: string; 14 | requestType: ItemRequestTypes; 15 | }): RequestHandler { 16 | return { 17 | canHandle(handlerInput) { 18 | return IsIntentWithCompleteDialog(handlerInput, options.intentName); 19 | }, 20 | async handle(handlerInput) { 21 | const { t, slots } = GetRequestAttributes(handlerInput); 22 | 23 | const item = slots[options.slotName]; 24 | 25 | if (item && item.isMatch && !item.isAmbiguous) { 26 | // get the league 27 | const league = GetLeagueSlot(slots); 28 | 29 | // get the links 30 | const links = GetLinksSlot(slots); 31 | 32 | try { 33 | // get the item prices 34 | const res = await apiClient.items({ 35 | league: LeagueTypeSlotToAPIMap[league], 36 | type: options.requestType, 37 | date: CurrentDate(), 38 | }); 39 | 40 | // filter out low confidence elements 41 | res.lines = res.lines.filter(IsHighConfidenceItemPrice); 42 | 43 | // find all items with a matching name 44 | res.lines = res.lines.filter((i) => i.name === item.resolved); 45 | 46 | // sort the item by price, cheaper first 47 | res.lines = res.lines.sort((a, b) => a.chaosValue - b.chaosValue); 48 | 49 | // search for the item that the user requested 50 | for (let itemDetails of res.lines) { 51 | // if the user didn't mention the links, we take the first item 52 | // if he did mention the links, then we need to check and make sure it matches 53 | if (links === 0 || links === itemDetails.links) { 54 | const exaltedValue = itemDetails.exaltedValue; 55 | const chaosValue = itemDetails.chaosValue; 56 | const linkedString = links !== 0 ? t(Strings.LINKED, links) : 1; 57 | 58 | // only include the exalted price equivalent if it's higher than 1 59 | if (exaltedValue >= 1) { 60 | return handlerInput.responseBuilder 61 | // TODO: - add plurals 62 | .speak(t(Strings.PRICE_OF_IS_EXALTED_MSG, linkedString, item.resolved, league, FormatPrice(exaltedValue).toString(), FormatPrice(chaosValue).toString())) // .toString() removes the trailing zeros 63 | .getResponse(); 64 | } 65 | 66 | // chaos only price 67 | return handlerInput.responseBuilder 68 | // TODO: - add plurals 69 | .speak(t(Strings.PRICE_OF_IS_MSG, linkedString, item.resolved, league, FormatPrice(itemDetails.chaosValue).toString())) // .toString() removes the trailing zeros 70 | .getResponse(); 71 | } 72 | } 73 | 74 | // item not found 75 | return handlerInput.responseBuilder 76 | .speak(t(Strings.ERROR_ITEM_NOT_FOUND_MSG)) 77 | .getResponse(); 78 | 79 | } catch (err) { 80 | throw CreateError(`Got error while getting unique weapon prices: ${err}`, ErrorTypes.API) 81 | } 82 | } 83 | 84 | throw CreateError(`Got to the COMPLETED state of ${options.intentName} without a slot.`, ErrorTypes.Unexpected); 85 | } 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /slots/DIVINATION.csv: -------------------------------------------------------------------------------- 1 | A Mother's Parting Gift, 2 | Abandoned Wealth, 3 | Anarchy's Price, 4 | Assassin's Favour, 5 | Atziri's Arsenal, 6 | Audacity, 7 | Birth of the Three, 8 | Blessing of God, 9 | Blind Venture, 10 | Boundless Realms, 11 | Bowyer's Dream, 12 | Call to the First Ones, 13 | Cartographer's Delight, 14 | Chaotic Disposition, 15 | Coveted Possession, 16 | Death, 17 | Destined to Crumble, 18 | Dialla's Subjugation, 19 | Doedre's Madness, 20 | Dying Anguish, 21 | Earth Drinker, 22 | Emperor of Purity, 23 | Emperor's Luck, 24 | Forbidden Power, 25 | Gemcutter's Promise, 26 | Gift of the Gemling Queen, 27 | Glimmer of Hope, 28 | Grave Knowledge, 29 | Harmony of Souls, 30 | Her Mask, 31 | Heterochromia, 32 | Hope, 33 | House of Mirrors, 34 | Hubris, 35 | Humility, 36 | Hunter's Resolve, 37 | Hunter's Reward, 38 | Immortal Resolve, 39 | Jack in the Box, 40 | Lantador's Lost Love, 41 | Last Hope, 42 | Left to Fate, 43 | Light and Truth, 44 | Lingering Remnants, 45 | Lost Worlds, 46 | Loyalty, 47 | Lucky Connections, 48 | Lucky Deck, 49 | Lysah's Respite, 50 | Mawr Blaidd, 51 | Merciless Armament, 52 | Might is Right, 53 | Mitts, 54 | No Traces, 55 | Perfection, 56 | Pride Before the Fall, 57 | Prosperity, 58 | Rain Tempter, 59 | Rain of Chaos, 60 | Rats, 61 | Rebirth, 62 | Scholar of the Seas, 63 | Shard of Fate, 64 | Struck by Lightning, 65 | The Admirer, 66 | The Aesthete, 67 | The Arena Champion, 68 | The Army of Blood, 69 | The Artist, 70 | The Avenger, 71 | The Battle Born, 72 | The Beast, 73 | The Betrayal, 74 | The Blazing Fire, 75 | The Body, 76 | The Breach, 77 | The Brittle Emperor, 78 | The Calling, 79 | The Carrion Crow, 80 | The Cartographer, 81 | The Cataclysm, 82 | The Catalyst, 83 | The Celestial Justicar, 84 | The Celestial Stone, 85 | The Chains that Bind, 86 | The Coming Storm, 87 | The Conduit, 88 | The Cursed King, 89 | The Dapper Prodigy, 90 | The Dark Mage, 91 | The Darkest Dream, 92 | The Deceiver, 93 | The Demoness, 94 | The Doctor, 95 | The Doppelganger, 96 | The Dragon, 97 | The Dragon's Heart, 98 | The Dreamer, 99 | The Dreamland, 100 | The Drunken Aristocrat, 101 | The Encroaching Darkness, 102 | The Endurance, 103 | The Enlightened, 104 | The Ethereal, 105 | The Explorer, 106 | The Eye of the Dragon, 107 | The Fathomless Depths, 108 | The Feast, 109 | The Fiend, 110 | The Fletcher, 111 | The Flora's Gift, 112 | The Formless Sea, 113 | The Forsaken, 114 | The Fox, 115 | The Gambler, 116 | The Garish Power, 117 | The Gemcutter, 118 | The Gentleman, 119 | The Gladiator, 120 | The Hale Heart, 121 | The Harvester, 122 | The Hermit, 123 | The Hoarder, 124 | The Hunger, 125 | The Immortal, 126 | The Incantation, 127 | The Inoculated, 128 | The Insatiable, 129 | The Inventor, 130 | The Iron Bard, 131 | The Jester, 132 | The Jeweller's Boon, 133 | The King's Blade, 134 | The King's Heart, 135 | The Last One Standing, 136 | The Lich, 137 | The Lion, 138 | The Lord in Black, 139 | The Lover, 140 | The Lunaris Priestess, 141 | The Master, 142 | The Mayor, 143 | The Mercenary, 144 | The Metalsmith's Gift, 145 | The Oath, 146 | The Obscured, 147 | The Offering, 148 | The One With All, 149 | The Opulent, 150 | The Pack Leader, 151 | The Pact, 152 | The Penitent, 153 | The Poet, 154 | The Polymath, 155 | The Porcupine, 156 | The Professor, 157 | The Puzzle, 158 | The Queen, 159 | The Rabid Rhoa, 160 | The Realm, 161 | The Risk, 162 | The Rite of Elements, 163 | The Road to Power, 164 | The Ruthless Ceinture, 165 | The Saint's Treasure, 166 | The Samurai's Eye, 167 | The Scarred Meadow, 168 | The Scavenger, 169 | The Scholar, 170 | The Sephirot, 171 | The Sigil, 172 | The Siren, 173 | The Soul, 174 | The Spark and the Flame, 175 | The Spoiled Prince, 176 | The Standoff, 177 | The Stormcaller, 178 | The Summoner, 179 | The Sun, 180 | The Surgeon, 181 | The Surveyor, 182 | The Survivalist, 183 | The Sword King's Salute, 184 | The Thaumaturgist, 185 | The Throne, 186 | The Tower, 187 | The Traitor, 188 | The Trial, 189 | The Twins, 190 | The Tyrant, 191 | The Undaunted, 192 | The Undisputed, 193 | The Union, 194 | The Valkyrie, 195 | The Valley of Steel Boxes, 196 | The Vast, 197 | The Visionary, 198 | The Void, 199 | The Warden, 200 | The Warlord, 201 | The Watcher, 202 | The Web, 203 | The Wind, 204 | The Witch, 205 | The Wolf, 206 | The Wolf's Shadow, 207 | The Wolven King's Bite, 208 | The Wolverine, 209 | The World Eater, 210 | The Wrath, 211 | The Wretched, 212 | Three Faces in the Dark, 213 | Three Voices, 214 | Thunderous Skies, 215 | Time-Lost Relic, 216 | Tranquillity, 217 | Treasure Hunter, 218 | Turn the Other Cheek, 219 | Vinia's Token, 220 | Volatile Power, 221 | Wealth and Power, -------------------------------------------------------------------------------- /slots/UNIQUE_WEAPON.csv: -------------------------------------------------------------------------------- 1 | Edge of Madness, 2 | Nycta's Lantern, 3 | Severed in Sleep, 4 | Starforge, 5 | The Whispering Ice, 6 | Pledge of Hands, 7 | Windripper, 8 | Death's Opus, 9 | Atziri's Disfavour,,Disfavour 10 | Reach of the Council, 11 | The Enmity Divine, 12 | The Rippling Thoughts, 13 | Mark of the Doubting Knight, 14 | Silverbranch, 15 | Mjölner, 16 | Lioneye's Glare, 17 | Oni-Goroshi, 18 | Geofri's Baptism, 19 | Soul Taker, 20 | The Scourge,,Scourge 21 | The Searing Touch, 22 | Taryn's Shiver, 23 | Moonsorrow, 24 | Wings of Entropy, 25 | Brain Rattler, 26 | Doon Cuebiyari, 27 | Voltaxic Rift, 28 | Trypanon, 29 | Ephemeral Edge, 30 | Ngamahu's Flame,,Ngamahu,Ngamahu's 31 | Sire of Shards, 32 | Slivertongue, 33 | The Dark Seer, 34 | Xirgil's Crank, 35 | Hand of Thought and Motion, 36 | The Wasp Nest, 37 | Sinvicta's Mettle, 38 | Tremor Rod, 39 | Callinellus Malleus, 40 | Rigwald's Charge, 41 | Tulborn, 42 | Bloodseeker, 43 | Hopeshredder, 44 | Hand of Wisdom and Action, 45 | Terminus Est, 46 | The Stormheart, 47 | Agnerod East, 48 | Agnerod West, 49 | Essentia Sanguis, 50 | Apep's Rage, 51 | Hegemony's Era, 52 | Mortem Morsu, 53 | The Cauteriser, 54 | Dreamfeather, 55 | Scaeva, 56 | Marohi Erqi, 57 | Widowmaker, 58 | Kondo's Pride, 59 | Bino's Kitchen Knife, 60 | Reaper's Pursuit, 61 | Oro's Sacrifice, 62 | Ichimonji, 63 | Fidelitas' Spike, 64 | Heartbreaker, 65 | Reverberation Rod, 66 | Hezmana's Bloodlust, 67 | Kongor's Undying Rage, 68 | Agnerod North, 69 | The Dancing Dervish, 70 | Storm Cloud, 71 | Agnerod South, 72 | Chin Sol, 73 | Eclipse Solaris, 74 | Jack the Axe, 75 | Earendel's Embrace, 76 | Obliteration, 77 | Cane of Unravelling, 78 | Dyadus, 79 | Dying Breath, 80 | Daresso's Passion, 81 | Prismatic Eclipse, 82 | Infractem, 83 | Ashcaller, 84 | Hyaon's Fury, 85 | The Screaming Eagle, 86 | The Tempestuous Steel, 87 | Brightbeak, 88 | Redbeak, 89 | Lifesprig, 90 | Rebuke of the Vaal, 91 | Chitus' Needle, 92 | Mightflay, 93 | Darkscorn, 94 | Innsbury Edge, 95 | Piscator's Vigil,,Piscator,Piscator's 96 | The Princess, 97 | Advancing Fortress, 98 | Axiom Perpetuum, 99 | The Blood Reaper, 100 | Mon'tregul's Grasp, 101 | Kaom's Primacy, 102 | Twyzel, 103 | Uul-Netol's Kiss, 104 | Death's Harp, 105 | Cybil's Paw, 106 | Cameria's Maul, 107 | Al Dhih, 108 | Quill Rain, 109 | Taproot, 110 | Femurs of the Saints, 111 | Lakishu's Blade, 112 | Midnight Bargain, 113 | Abberath's Horn, 114 | Chober Chaber, 115 | Aurumvorax, 116 | Bitterdream, 117 | Bloodplay, 118 | Ewar's Mirage, 119 | The Blood Thorn, 120 | Storm Prison, 121 | Goredrill, 122 | Fencoil, 123 | Limbsplit, 124 | Gorebreaker, 125 | Debeon's Dirge, 126 | Flesh-Eater, 127 | Roth's Reach, 128 | Relentless Fury, 129 | Queen's Decree, 130 | Realmshaper, 131 | Frostbreath, 132 | Clayshaper, 133 | Dreadarc, 134 | Moonbender's Wing, 135 | Ornament of the East, 136 | Pillar of the Caged God, 137 | Quecholli, 138 | The Goddess Bound, 139 | Ungil's Gauche, 140 | Voidhome, 141 | Wideswing, 142 | Wildslash, 143 | Doomfletch, 144 | Lavianga's Wisdom, 145 | Hrimnor's Hymn, 146 | Last Resort, 147 | Shiversting, 148 | Iron Commander, 149 | Jorrhast's Blacksteel, 150 | The Goddess Unleashed, 151 | Queen's Escape, 152 | The Tempest, 153 | Duskdawn, 154 | Silverbough, 155 | Voidforge, 156 | Rigwald's Savagery, 157 | Tidebreaker, 158 | Disintegrator, 159 | Martyr of Innocence, 160 | Kitava's Feast, 161 | The Stormwall, 162 | The Dancing Duo, 163 | Arborix, 164 | Fate of the Vaal, 165 | Doomfletch's Prism, 166 | Kingmaker, 167 | Xoph's Nurture, 168 | Null's Inclination, 169 | Realm Ender, 170 | United in Dream, 171 | Panquetzaliztli, 172 | Doomsower, 173 | Nuro's Harp, 174 | Uul-Netol's Embrace, 175 | The Harvest, 176 | The Supreme Truth, 177 | Geofri's Devotion, 178 | Shimmeron, 179 | Rigwald's Command, 180 | Void Battery, 181 | The Poet's Pen,,Poet's Pen,pen 182 | Varunastra, 183 | Hiltless, 184 | Brutus' Lead Sprinkler,,Lead Sprinkler,Sprinkler 185 | Breath of the Council, 186 | Hrimnor's Dirge, 187 | Cameria's Avarice, 188 | Doryani's Catalyst,,Doryani,Doryani's 189 | Cospri's Malice,,Cospri,Cospri's 190 | The Goddess Scorned, 191 | Story of the Vaal, 192 | Dreadbeak, 193 | Corona Solaris, 194 | Mirebough, 195 | Shade of Solaris, 196 | Dreadsurge, 197 | Divinarius, 198 | The Gryphon, 199 | Sanguine Gambol, 200 | Ahn's Might, 201 | Arakaali's Fang, 202 | Beltimber Blade, 203 | Razor of the Seventh Sun, 204 | Tulfall, 205 | Augyre, 206 | Amplification Rod, 207 | Singularity, 208 | Vulconus, 209 | Nebuloch, 210 | Allure, 211 | Death's Hand, 212 | Izaro's Dilemma, 213 | Touch of Anguish, 214 | Balefire, 215 | Grelwood Shank, 216 | Spine of the First Claimant, 217 | Xoph's Inception, 218 | Rive, 219 | The Consuming Dark, 220 | White Wind, -------------------------------------------------------------------------------- /__tests__/helpers.ts: -------------------------------------------------------------------------------- 1 | import { RequestEnvelope, ResponseEnvelope, DialogState, SlotConfirmationStatus, slu, Slot } from "ask-sdk-model"; 2 | import { handler } from "../lambda/custom"; 3 | import { IntentTypes, RequestTypes, LocaleTypes } from "../lambda/custom/lib/constants"; 4 | 5 | export type PartialDeep = { 6 | [P in keyof T]?: PartialDeep; 7 | }; 8 | 9 | /** 10 | * Accepts a partial T object and returns it as T. 11 | * Useful when you only need a few fields of T but still want to get type 12 | * completions and to suppress compilation error. 13 | * 14 | * @param value 15 | */ 16 | export function partial(value: PartialDeep): T { 17 | return (value as any) as T; 18 | } 19 | 20 | /** 21 | * Calls the skill handler with the given RequestEnvelope. Returns a promise 22 | * with the response. 23 | * 24 | * @param event 25 | */ 26 | export function skill(event: RequestEnvelope) { 27 | return new Promise((fulfill, reject) => { 28 | return handler(event, null, (err, res) => { 29 | if (err) return reject(err); 30 | return fulfill(res); 31 | }); 32 | }); 33 | } 34 | 35 | /** 36 | * Returns a partial ResponseEnvelope with the given ssml pattern. 37 | * 38 | * @param pattern 39 | */ 40 | export function ssml(pattern: string | RegExp) { 41 | return partial({ 42 | response: { 43 | outputSpeech: { 44 | ssml: expect.stringMatching(pattern) 45 | } 46 | } 47 | }); 48 | } 49 | 50 | /** 51 | * Returns a partial ResponseEnvelope for the given intent matching a 52 | * Dialog.Delegate directive. 53 | * 54 | * @param intentName 55 | */ 56 | export function inProgressDelegate(intentName: string) { 57 | return partial({ 58 | response: { 59 | directives: [ 60 | { 61 | type: "Dialog.Delegate", 62 | updatedIntent: { 63 | name: intentName, 64 | } 65 | } 66 | ] 67 | } 68 | }); 69 | } 70 | 71 | /** 72 | * Returns a RequestEnvelope with the given type. 73 | * 74 | * @param options 75 | */ 76 | export function RequestWithType(options: { 77 | type: RequestTypes; 78 | locale: LocaleTypes; 79 | }) { 80 | return partial<{}>({ 81 | "context": { 82 | "System": {} 83 | }, 84 | "request": { 85 | "type": options.type, 86 | "locale": options.locale 87 | } 88 | }) as RequestEnvelope; 89 | } 90 | 91 | /** 92 | * Returns a RequestEnvelope with the given intent. 93 | * 94 | * @param options 95 | */ 96 | export function RequestWithIntent(options: { 97 | name: IntentTypes; 98 | locale: LocaleTypes; 99 | }) { 100 | return partial({ 101 | "context": { 102 | "System": {} 103 | }, 104 | "request": { 105 | "type": "IntentRequest", 106 | "locale": options.locale, 107 | "intent": { 108 | "name": options.name 109 | } 110 | } 111 | }); 112 | } 113 | 114 | /** 115 | * Creates an intent request envelope with the given parameters. 116 | * 117 | * @param options 118 | */ 119 | export function CreateIntentRequest(options: { 120 | name: string; 121 | locale: LocaleTypes; 122 | dialogState?: DialogState; 123 | slots?: { 124 | [key: string]: { 125 | value?: string; 126 | confirmationStatus?: SlotConfirmationStatus; 127 | resolutions?: { 128 | status: slu.entityresolution.StatusCode, 129 | values?: { 130 | name: string; 131 | id?: string; 132 | }[]; 133 | } 134 | } 135 | }, 136 | }) { 137 | return partial({ 138 | "context": { 139 | "System": {} 140 | }, 141 | "request": { 142 | "type": "IntentRequest", 143 | "locale": options.locale, 144 | "intent": { 145 | "name": options.name, 146 | "confirmationStatus": "NONE", 147 | "slots": options.slots ? (() => { 148 | const slots: { 149 | [key: string]: Slot; 150 | } = {}; 151 | 152 | for (let slotName of Object.keys(options.slots)) { 153 | const slot = options.slots[slotName]; 154 | slots[slotName] = { 155 | name: slotName, 156 | value: slot.value ? slot.value : "", 157 | confirmationStatus: slot.confirmationStatus ? slot.confirmationStatus : "NONE", 158 | resolutions: slot.resolutions ? { 159 | resolutionsPerAuthority: [ 160 | { 161 | authority: "", 162 | status: { 163 | code: slot.resolutions.status 164 | }, 165 | values: slot.resolutions.values ? slot.resolutions.values.map((item) => { 166 | return { 167 | value: { 168 | name: item.name, 169 | id: item.id ? item.id : "" 170 | } 171 | }; 172 | }) : [] 173 | } 174 | ] 175 | } : undefined, 176 | } 177 | } 178 | return slots; 179 | })() : undefined, 180 | }, 181 | "dialogState": options.dialogState ? options.dialogState : "STARTED", 182 | } 183 | }); 184 | } 185 | -------------------------------------------------------------------------------- /slots/PROPHECY.csv: -------------------------------------------------------------------------------- 1 | A Call into the Void, 2 | A Dishonourable Death, 3 | A Firm Foothold, 4 | A Forest of False Idols, 5 | A Gracious Master, 6 | A Master Seeks Help, 7 | A Prodigious Hand, 8 | A Regal Death, 9 | A Rift in Time, 10 | A Valuable Combination, 11 | A Vision of Ice and Fire, 12 | A Whispered Prayer, 13 | Abnormal Effulgence, 14 | Against the Tide, 15 | Agony at Dusk, 16 | An Unseen Peril, 17 | Anarchy's End I,,Anarchy's End One 18 | Anarchy's End II,,Anarchy's End Two 19 | Anarchy's End III,,Anarchy's End Three 20 | Anarchy's End IV,,Anarchy's End Four 21 | Ancient Doom, 22 | Ancient Rivalries I,,Ancient Rivalries One 23 | Ancient Rivalries II,,Ancient Rivalries Two 24 | Baptism by Death, 25 | Beyond Sight I,,Beyond Sight One 26 | Beyond Sight II,,Beyond Sight Two 27 | Beyond Sight III,,Beyond Sight Three 28 | Beyond Sight IV,,Beyond Sight Four 29 | Black Devotion, 30 | Blind Faith, 31 | Blinding Light, 32 | Blood in the Eyes, 33 | Blood of the Betrayed, 34 | Bountiful Traps, 35 | Brothers in Arms, 36 | Burning Dread, 37 | Cleanser of Sins, 38 | Cold Blooded Fury, 39 | Cold Greed, 40 | Crimson Hues, 41 | Crushing Squall, 42 | Custodians of Silence, 43 | Dance of Steel, 44 | Dark Instincts, 45 | Darktongue's Shriek, 46 | Day of Sacrifice I,,Day of Sacrifice One 47 | Day of Sacrifice II,,Day of Sacrifice Two 48 | Day of Sacrifice III,,Day of Sacrifice Three 49 | Day of Sacrifice IV,,Day of Sacrifice Four 50 | Deadly Rivalry I,,Deadly Rivalry One 51 | Deadly Rivalry II,,Deadly Rivalry Two 52 | Deadly Rivalry III,,Deadly Rivalry Three 53 | Deadly Rivalry IV,,Deadly Rivalry Four 54 | Deadly Rivalry V,,Deadly Rivalry Five 55 | Deadly Twins, 56 | Defiled in the Sceptre, 57 | Dying Cry, 58 | Echoes of Witchcraft, 59 | End of the Light, 60 | Ending the Torment, 61 | Erased from Memory, 62 | Erasmus' Gift, 63 | Fallow At Last, 64 | Fated Connections, 65 | Fear's Wide Reach, 66 | Fire Wood and Stone, 67 | Fire and Brimstone, 68 | Fire and Ice, 69 | Fire from the Sky, 70 | Flesh of the Beast, 71 | Forceful Exorcism, 72 | From Death Springs Life, 73 | From The Void, 74 | Gilded Within, 75 | Golden Touch, 76 | Graceful Flames, 77 | Greed's Folly, 78 | Heart of the Fire, 79 | Heavy Blows, 80 | Hidden Reinforcements, 81 | Hidden Vaal Pathways, 82 | Holding the Bridge, 83 | Hunter's Lesson, 84 | Ice from Above, 85 | In the Grasp of Corruption, 86 | Kalandra's Craft, 87 | Last of the Wildmen, 88 | Lasting Impressions, 89 | Lightning Falls, 90 | Living Fires, 91 | Lost in the Pages, 92 | Monstrous Treasure, 93 | Mouth of Horrors, 94 | Mysterious Invaders, 95 | Nature's Resilience, 96 | Nemesis of Greed, 97 | Notched Flesh, 98 | Overflowing Riches, 99 | Path of Betrayal, 100 | Plague of Frogs, 101 | Plague of Rats, 102 | Pleasure and Pain, 103 | Pools of Wealth, 104 | Possessed Foe, 105 | Power Magnified, 106 | Rebirth, 107 | Reforged Bonds, 108 | Resistant to Change, 109 | Risen Blood, 110 | Roth's Legacy, 111 | Severed Limbs, 112 | Smothering Tendrils, 113 | Soil Worms and Blood, 114 | Song of the Sekhema, 115 | Storm on the Horizon, 116 | Storm on the Reef, 117 | Strong as a Bull, 118 | Sun's Punishment, 119 | Thaumaturgical History I,,Thaumaturgical History One 120 | Thaumaturgical History II,,Thaumaturgical History Two 121 | Thaumaturgical History III,,Thaumaturgical History Three 122 | Thaumaturgical History IV,,Thaumaturgical History Four 123 | The Aesthete's Spirit, 124 | The Alchemist, 125 | The Ambitious Bandit I,,The Ambitious Bandit One 126 | The Ambitious Bandit II,,The Ambitious Bandit Two 127 | The Ambitious Bandit III,,The Ambitious Bandit Three 128 | The Apex Predator, 129 | The Beautiful Guide, 130 | The Beginning and the End, 131 | The Blacksmith, 132 | The Bloody Flowers Redux, 133 | The Bowstring's Music, 134 | The Brothers of Necromancy, 135 | The Brutal Enforcer, 136 | The Child of Lunaris, 137 | The Corrupt, 138 | The Cursed Choir, 139 | The Dreaded Rhoa, 140 | The Dream Trial, 141 | The Dreamer's Dream, 142 | The Eagle's Cry, 143 | The Emperor's Trove, 144 | The Fall of an Empire, 145 | The Feral Lord I,,The Feral Lord One 146 | The Feral Lord II,,The Feral Lord Two 147 | The Feral Lord III,,The Feral Lord Three 148 | The Feral Lord IV,,The Feral Lord Four 149 | The Feral Lord V,,The Feral Lord Five 150 | The Flayed Man, 151 | The Flow of Energy, 152 | The Forgotten Garrison, 153 | The Forgotten Soldiers, 154 | The Fortune Teller's Collection, 155 | The Four Feral Exiles, 156 | The God of Misfortune, 157 | The Great Leader of the North, 158 | The Great Mind of the North, 159 | The Hardened Armour, 160 | The Hollow Pledge, 161 | The Hungering Swarm, 162 | The Invader, 163 | The Jeweller's Touch, 164 | The Karui Rebellion, 165 | The King and the Brambles, 166 | The King's Path, 167 | The Lady in Black, 168 | The Last Watch, 169 | The Lost Maps, 170 | The Lost Undying, 171 | The Malevolent Witch, 172 | The Misunderstood Queen, 173 | The Mysterious Gift, 174 | The Nest, 175 | The Nightmare Awakens, 176 | The Petrified, 177 | The Plaguemaw I,,The Plaguemaw One 178 | The Plaguemaw II,,The Plaguemaw Two 179 | The Plaguemaw III,,The Plaguemaw Three 180 | The Plaguemaw IV,,The Plaguemaw Four 181 | The Plaguemaw V,,The Plaguemaw Five 182 | The Prison Guard, 183 | The Prison Key, 184 | The Queen's Sacrifice, 185 | The Queen's Vaults, 186 | The Scout, 187 | The Servant's Heart, 188 | The Sharpened Blade, 189 | The Silverwood, 190 | The Singular Spirit, 191 | The Sinner's Stone, 192 | The Snuffed Flame, 193 | The Soulless Beast, 194 | The Stockkeeper, 195 | The Storm Spire, 196 | The Sword King's Passion, 197 | The Trembling Earth, 198 | The Twins, 199 | The Unbreathing Queen I,,The Unbreathing Queen One 200 | The Unbreathing Queen II,,The Unbreathing Queen Two 201 | The Unbreathing Queen III,,The Unbreathing Queen Three 202 | The Unbreathing Queen IV,,The Unbreathing Queen Four 203 | The Unbreathing Queen V,,The Unbreathing Queen Five 204 | The Undead Brutes, 205 | The Undead Storm, 206 | The Vanguard, 207 | The Walking Mountain, 208 | The Ward's Ward, 209 | The Warmongers I,,The Warmongers One 210 | The Warmongers II,,The Warmongers Two 211 | The Warmongers III,,The Warmongers Three 212 | The Warmongers IV,,The Warmongers Four 213 | The Watcher's Watcher, 214 | The Wealthy Exile, 215 | Touched by the Wind, 216 | Trapped in the Tower, 217 | Trash to Treasure, 218 | Twice Enchanted, 219 | Unbearable Whispers I,,Unbearable Whispers One 220 | Unbearable Whispers II,,Unbearable Whispers Two 221 | Unbearable Whispers III,,Unbearable Whispers Three 222 | Unbearable Whispers IV,,Unbearable Whispers Four 223 | Unbearable Whispers V,,Unbearable Whispers Five 224 | Undead Uprising, 225 | Unnatural Energy, 226 | Vaal Invasion, 227 | Vaal Winds, 228 | Visions of the Drowned, 229 | Vital Transformation, 230 | Waiting in Ambush, 231 | Weeping Death, 232 | Wind and Thunder, 233 | Winter's Mournful Melodies, -------------------------------------------------------------------------------- /lambda/custom/lib/strings.ts: -------------------------------------------------------------------------------- 1 | import { Resource, ResourceLanguage } from "i18next"; 2 | import { Strings, LocaleTypes } from "./constants"; 3 | 4 | interface IStrings { 5 | [Strings.SKILL_NAME]: string; 6 | [Strings.WELCOME_MSG]: string; 7 | [Strings.GOODBYE_MSG]: string[]; 8 | [Strings.HELP_MSG]: string; 9 | [Strings.ERROR_MSG]: string; 10 | [Strings.ERROR_UNEXPECTED_MSG]: string; 11 | [Strings.SELECT_ONE_MSG]: string; 12 | [Strings.OR_MSG]: string; 13 | [Strings.CHECKING_PRICE_OF_MSG]: string; 14 | [Strings.PRICE_OF_IS_MSG]: string; 15 | [Strings.PRICE_OF_IS_EXALTED_MSG]: string; 16 | [Strings.LINKED]: string; 17 | [Strings.LEVEL]: string; 18 | [Strings.QUALITY]: string; 19 | [Strings.ERROR_NOT_ENOUGH_DATA_MSG]: string; 20 | [Strings.ERROR_CURRENCY_NOT_FOUND_MSG]: string; 21 | [Strings.ERROR_ITEM_NOT_FOUND_MSG]: string; 22 | [Strings.ERROR_API_MSG]: string; 23 | [Strings.QUEST_REWARD_MSG]: string; 24 | [Strings.QUEST_REWARDS]: any; 25 | } 26 | 27 | const enUS: ResourceLanguage = { 28 | translation: { 29 | SKILL_NAME: "Path of Exile Helper", 30 | WELCOME_MSG: "Welcome to P.O.E. Helper. How can I help?", 31 | GOODBYE_MSG: [ 32 | "May your maps be merciful.", 33 | "Good luck with your maps exile.", 34 | "Stay sane.", 35 | ], 36 | HELP_MSG: `You can ask for the price of an item, or the reward of a quest. Here are some things you can try: "What is the price of an Exalted Orb?", or, "What is the price of one hundred fusings in Standard league?", or, "What is the price of a six linked Loreweave?", or, "What is the price of a twenty one twenty three Kinetic Blast?", or "What is the quest reward for The Dweller of the Deep?". How can I help?`, 37 | ERROR_MSG: "Sorry, I can't understand the command. Please say again.", 38 | ERROR_UNEXPECTED_MSG: "Sorry, an unexpected error has occured. Please try again later.", 39 | SELECT_ONE_MSG: "Which would you like: %s?", 40 | OR_MSG: " or ", 41 | CHECKING_PRICE_OF_MSG: "Checking the price of %s in %s league...", 42 | PRICE_OF_IS_MSG: "The price of %s '%s' in %s league is %s Chaos Orbs", 43 | PRICE_OF_IS_EXALTED_MSG: "The price of %s '%s' in %s league is %s Exalted Orbs or %s Chaos Orbs", 44 | LINKED: "a %s linked", 45 | LEVEL: "a level %s", 46 | QUALITY: " %s quality", 47 | ERROR_NOT_ENOUGH_DATA_MSG: "Sorry, there is not enough exchange data for the item you requested. Please try again later.", 48 | ERROR_CURRENCY_NOT_FOUND_MSG: "Sorry, I couldn't find the exchange for the currency you requested.", 49 | ERROR_ITEM_NOT_FOUND_MSG: "Sorry, I couldn't find the price of the item you requested.", 50 | ERROR_API_MSG: "Sorry, there was a problem while getting the data. Please try again later.", 51 | QUEST_REWARD_MSG: "The reward for '%s' is: %s", 52 | QUEST_REWARDS: { 53 | "Einhar's Hunt": "Reinforced Rope Net", 54 | "Einhar's Bestiary": "Simple Rope Net", 55 | "Einhar's Menagerie": "Simple Rope Net", 56 | "The Caged Brute": "Gem", 57 | "Mercy Mission": "Flask", 58 | "Breaking Some Eggs": "Gem", 59 | "The Siren's Cadence": "Gem", 60 | "Enemy at the Gate": "Gem", 61 | "A Dirty Job": "Book of Regrets", 62 | "The Dweller of the Deep": "Book of Skill", 63 | "The Marooned Mariner": "Book of Skill", 64 | "The Way Forward": "Book of Skill", 65 | "Intruders in Black": "Herald gem", 66 | "Through Sacred Ground": "Book of Regrets and Survival jewel", 67 | "The Great White Beast": "Flask or rare belt", 68 | "Sharp and Cruel": "Support gem", 69 | "Deal with the Bandits": "The Apex", 70 | "The Ribbon Spool": "Rare amulet", 71 | "Sever the Right Hand": "Gem with level 28 requirement", 72 | "Lost in Love": "Sewer Keys", 73 | "A Fixture of Fate": "Support gem with level 31 requirement such as Blasphemy Support", 74 | "Piety's Pets": "Book of Skill", 75 | "Victario's Secrets": "Book of Skill", 76 | "A Swig of Hope": "Rare ring", 77 | "Fiery Dust": "Infernal Talc", 78 | "Breaking the Seal": "Golem gem", 79 | "An Indomitable Spirit": "Book of Skill", 80 | "The Eternal Nightmare": "Support gem with level 38 requirement such as Greater Multiple Projectiles Support", 81 | "The King's Feast": "Assassin's Haste, Poacher's Aim or Warlord's Reach unique jewel", 82 | "In Service to Science": "Book of Skill", 83 | "Kitava's Torments": "Book of Skill", 84 | "Death to Purity": "Threshold jewel", 85 | "The Key to Freedom": "Flask", 86 | "Return to Oriath": "Rare ring", 87 | "Bestel's Epic": "Rare amulet or belt", 88 | "Fallen from Grace": "Book of Regrets", 89 | "The Cloven One": "Book of Skill", 90 | "The Father of War": "Book of Skill", 91 | "The Puppet Mistress": "Book of Skill", 92 | "Essence of Umbra": "Rare helmet", 93 | "In Memory of Greust": "Rare amulet", 94 | "The Silver Locket": "Flask", 95 | "Kishara's Star": "Book of Skill", 96 | "Queen of Despair": "Book of Skill", 97 | "The Master of a Million Faces": "Book of Skill", 98 | "Essence of the Artist": "Rare boots", 99 | "Web of Secrets": "Obsidian Key", 100 | "Love is Dead": "Book of Skill", 101 | "Reflection of Terror": "Book of Skill", 102 | "The Gemling Legion": "Book of Skill", 103 | "The Wings of Vastiri": "Conqueror's unique jewel", 104 | "Essence of the Hag": "Rare ring", 105 | "The Storm Blade": "Rare weapon, shield or quiver", 106 | "Fastis Fortuna": "Book of Regrets", 107 | "Queen of the Sands": "Book of Skill", 108 | "The Ruler of Highgate": "Book of Skill", 109 | "Safe Passage": "Flask", 110 | "Death and Rebirth": "Rare chest", 111 | "No Love for Old Ghosts": "Book of Regrets", 112 | "An End to Hunger": "Book of Skill", 113 | "Vilenta's Vengeance": "Book of Skill", 114 | "Map to Tsoatha": "Rare belt", 115 | "From Nightmare into Dream": "Map", 116 | }, 117 | } as IStrings 118 | }; 119 | 120 | export const strings: Resource = { 121 | [LocaleTypes.enUS]: enUS, 122 | [LocaleTypes.enAU]: enUS, 123 | [LocaleTypes.enCA]: enUS, 124 | [LocaleTypes.enGB]: enUS, 125 | [LocaleTypes.enIN]: enUS, 126 | }; 127 | -------------------------------------------------------------------------------- /slots/UNIQUE_ARMOUR.csv: -------------------------------------------------------------------------------- 1 | Atziri's Acuity,,acuity 2 | Demigod's Triumph, 3 | Kaom's Heart, 4 | Death's Door, 5 | Doedre's Scorn, 6 | Demigod's Touch, 7 | Bubonic Trail, 8 | Belly of the Beast,,belly 9 | Scold's Bridle, 10 | Shavronne's Wrappings,,shav,shavs 11 | Lightning Coil, 12 | Sadima's Touch, 13 | The Tempest's Binding, 14 | Daresso's Defiance, 15 | Saffell's Frame, 16 | Craiceann's Carapace, 17 | Skyforth, 18 | The Surrender, 19 | Abberath's Hooves, 20 | The Bringer of Rain, 21 | Goldwyrm, 22 | The Fracturing Spinner, 23 | Tombfist, 24 | Greed's Embrace, 25 | Skin of the Loyal, 26 | Loreweave, 27 | Gruthkul's Pelt, 28 | Queen of the Forest, 29 | Rigwald's Quills, 30 | Atziri's Splendour,,Splendour 31 | Second Piece of Storms, 32 | Trolltimber Spire, 33 | The Formless Flame, 34 | Winds of Change, 35 | Hyrri's Ire, 36 | Abyssus, 37 | Tabula Rasa,,tabula 38 | The Vertex, 39 | Wall of Brambles, 40 | Lycosidae, 41 | Esh's Visage, 42 | Springleaf, 43 | Crest of Perandus, 44 | Speaker's Wreath, 45 | Allelopathy, 46 | Kaom's Roots, 47 | The Perfect Form, 48 | The Snowblind Grace, 49 | Doryani's Fist, 50 | The Blood Dance, 51 | The Formless Inferno, 52 | Aurseize, 53 | Empire's Grasp, 54 | Lioneye's Vision, 55 | Rathpith Globe, 56 | Voll's Protector, 57 | Voll's Vision, 58 | Chains of Command, 59 | Inpulsa's Broken Heart,,Inpulsa,Inpulsa's 60 | Devoto's Devotion, 61 | Lightpoacher, 62 | Carcass Jack,,Carcass 63 | Vis Mortis, 64 | Heretic's Veil, 65 | Hrimburn, 66 | Viper's Scales, 67 | Cospri's Will, 68 | Rainbowstride, 69 | The Infinite Pursuit, 70 | Crown of Eyes, 71 | Aegis Aurora, 72 | Fox's Fortune, 73 | Incandescent Heart, 74 | The Covenant, 75 | Cloak of Tawm'r Isley, 76 | Mutewind Whispersteps, 77 | The Three Dragons, 78 | Alpha's Howl, 79 | Wyrmsign, 80 | Drillneck, 81 | Prism Guardian, 82 | Atziri's Step, 83 | The Beast Fur Shawl, 84 | Haemophilia, 85 | Great Old One's Ward, 86 | Black Sun Crest, 87 | Rise of the Phoenix, 88 | Soul Strike, 89 | Veruso's Battering Rams, 90 | Stormcharger, 91 | Death's Oath, 92 | Rearguard, 93 | Cherrubim's Maleficence, 94 | Mutewind Pennant, 95 | Victario's Charity, 96 | Ambu's Charge, 97 | Goldrim, 98 | Heatshiver, 99 | Hyrri's Bite, 100 | Malachai's Loop, 101 | Maligaro's Virtuosity, 102 | The Deep One's Hide, 103 | The Embalmer, 104 | Rat's Nest, 105 | Geofri's Sanctuary, 106 | Southbound, 107 | Darkray Vectors, 108 | Mark of the Red Covenant, 109 | Voidbringer, 110 | Victario's Influence, 111 | Soul Mantle, 112 | Ahn's Heritage, 113 | The Restless Ward, 114 | Giantsbane, 115 | Deidbell, 116 | Wanderlust, 117 | Alberon's Warpath, 118 | Repentance, 119 | Thunderfist, 120 | Windscream, 121 | Lioneye's Paws, 122 | Asenath's Gentle Touch, 123 | Asphyxia's Wrath, 124 | Deidbellow, 125 | Rime Gaze, 126 | Saemus' Gift, 127 | Thousand Teeth Temu, 128 | Veil of the Night, 129 | Sunspite, 130 | Fairgraves' Tricorne, 131 | Bones of Ullr, 132 | Hrimnor's Resolve, 133 | Three-step Assault, 134 | Bronn's Lithe, 135 | Leper's Alms, 136 | The Broken Crown, 137 | The Peregrine, 138 | Hrimsorrow, 139 | Ezomyte Peak, 140 | Atziri's Mirror, 141 | Kongming's Stratagem, 142 | Redblade Tramplers, 143 | Sentari's Answer, 144 | Kalisa's Grace, 145 | Dendrobate, 146 | Starkonja's Head, 147 | Geofri's Crest, 148 | Zahndethus' Cassock, 149 | Jaws of Agony, 150 | Ondar's Clasp, 151 | Skullhead, 152 | Tinkerskin, 153 | Wraithlord, 154 | Blackgleam, 155 | Solaris Lorica, 156 | Daresso's Courage, 157 | Icetomb, 158 | Meginord's Vise, 159 | Ralakesh's Impatience, 160 | Wake of Destruction, 161 | Wheel of the Stormsail, 162 | Gang's Momentum, 163 | Nomic's Storm, 164 | Wondertrap, 165 | Shavronne's Pace, 166 | The Coming Calamity, 167 | Sin Trek, 168 | Chalice of Horrors, 169 | Asenath's Mark, 170 | Chitus' Apex, 171 | Slitherpinch, 172 | Honourhome, 173 | The Rat Cage, 174 | Mindspiral, 175 | Foxshade, 176 | Facebreaker, 177 | Ashrend, 178 | Cloak of Flame, 179 | Craghead, 180 | Crown of Thorns, 181 | Kingsguard, 182 | Matua Tupuna, 183 | Victario's Flight, 184 | Deerstalker, 185 | Dusktoe, 186 | Kaltenhalt, 187 | Doedre's Tenure, 188 | Malachai's Simula, 189 | Sundance, 190 | Atziri's Reflection, 191 | Shroud of the Lightless, 192 | Farrul's Fur, 193 | Apep's Supremacy, 194 | Demigod's Dominance, 195 | Fenumus' Shroud, 196 | Slavedriver's Hand, 197 | Bloodbond, 198 | Saqawal's Nest, 199 | Mask of the Stitched Demon, 200 | Cloak of Defiance, 201 | Omeyocan, 202 | Yriel's Fostering, 203 | The Brass Dome, 204 | Dialla's Malefaction, 205 | Frostferno, 206 | Farrul's Bite, 207 | Wildwrap, 208 | Farrul's Pounce, 209 | Infernal Mantle, 210 | Crystal Vault, 211 | Lightbane Raiment, 212 | Iron Heart, 213 | Briskwrap, 214 | Kintsugi, 215 | Farrul's Chase, 216 | Seven-League Step, 217 | Null and Void, 218 | Mind of the Council, 219 | Ylfeban's Trickery, 220 | Fenumus' Weave, 221 | Dance of the Offered, 222 | Tukohama's Fortress, 223 | The Anticipation, 224 | Inya's Epiphany, 225 | Windshriek, 226 | Indigon, 227 | Fenumus' Toxins, 228 | Apep's Slumber, 229 | Fenumus' Spinnerets, 230 | Broadstroke, 231 | Craiceann's Pincers, 232 | Craiceann's Chitin, 233 | Craiceann's Tracks, 234 | Magna Eclipsis, 235 | First Piece of Storms, 236 | Hyrri's Demise, 237 | Malachai's Awakening, 238 | Saqawal's Flock, 239 | Doedre's Skin, 240 | Unyielding Flame, 241 | Third Piece of Storms, 242 | Mask of the Spirit Drinker, 243 | The Unshattered Will, 244 | The Signal Fire, 245 | Saqawal's Winds, 246 | The Red Trail, 247 | Garukhan's Flight, 248 | Shadows and Dust, 249 | The Gull, 250 | Volkuur's Guidance, 251 | Voidfletcher, 252 | Esh's Mirror, 253 | Bramblejack, 254 | Doedre's Malevolence, 255 | Thousand Ribbons, 256 | Invictus Solaris, 257 | Crown of the Pale King, 258 | Oskarm, 259 | Saqawal's Talons, 260 | Shaper's Touch, 261 | Vix Lunaris, 262 | Asenath's Chant, 263 | Grip of the Council, 264 | Light of Lunaris, 265 | Third Piece of Focus, 266 | Duskblight, 267 | The Brine Crown, 268 | First Piece of Directions, 269 | Flesh and Spirit, 270 | First Piece of Focus, 271 | Third Piece of Directions, 272 | The Oak, 273 | Second Piece of Focus, 274 | Eber's Unification, 275 | Fourth Piece of Focus, 276 | Second Piece of Directions, 277 | Voidwalker, 278 | Shavronne's Gambit, 279 | Broken Faith, 280 | Obscurantis, 281 | Lioneye's Remorse, 282 | Greedtrap, 283 | Martyr's Crown, 284 | The Baron, 285 | Ezomyte Hold, 286 | Malachai's Mark, 287 | Lochtonial Caress, 288 | Memory Vault, 289 | Steppan Eard, 290 | Kaltensoul, 291 | Vaal Caress, 292 | Brinerot Flag, 293 | Brinerot Whalers, 294 | Leer Cast, 295 | Maloney's Nightfall, 296 | Redblade Banner, 297 | Blasphemer's Grasp, 298 | Gorgon's Gaze, 299 | Zeel's Amplifier, 300 | Kitava's Thirst, 301 | Cragfall, 302 | Titucus Span, 303 | Thirst for Horrors, 304 | Chernobog's Pillar, 305 | Glitterdisc, 306 | Maligaro's Lens, 307 | Skirmish, 308 | Snakebite, 309 | Surgebinders, 310 | Titucius' Span, 311 | Ahn's Contempt, 312 | Shackles of the Wretched, 313 | Architect's Hand, -------------------------------------------------------------------------------- /__tests__/enUS/Gem.spec.ts: -------------------------------------------------------------------------------- 1 | import { ResponseEnvelope } from "ask-sdk-model"; 2 | import { IntentTypes, SlotTypes, LocaleTypes } from "../../lambda/custom/lib/constants"; 3 | import { CreateIntentRequest, skill, inProgressDelegate, ssml, partial } from "../helpers"; 4 | import { LeagueTypes } from "../../lambda/custom/api"; 5 | 6 | // mock the poe.ninja api client 7 | jest.mock("../../lambda/custom/api/POENinjaClient"); 8 | 9 | describe("Skill Gems", () => { 10 | const name = IntentTypes.GemPriceCheck; 11 | const locale = LocaleTypes.enUS; 12 | 13 | it("InProgress", async () => { 14 | const request = CreateIntentRequest({ 15 | name: name, 16 | locale: locale, 17 | dialogState: "IN_PROGRESS", 18 | }); 19 | const response = await skill(request); 20 | expect(response).toMatchObject(inProgressDelegate(name)); 21 | }); 22 | 23 | it("InProgress ambiguous", async () => { 24 | const request = CreateIntentRequest({ 25 | name: name, 26 | locale: locale, 27 | dialogState: "IN_PROGRESS", 28 | slots: { 29 | [SlotTypes.Gem]: { 30 | resolutions: { 31 | status: "ER_SUCCESS_MATCH", 32 | values: [{ 33 | name: "Value 1", 34 | }, 35 | { 36 | name: "Value 2", 37 | }] 38 | } 39 | } 40 | } 41 | }); 42 | const response = await skill(request); 43 | expect(response).toMatchObject(ssml(/Which would you like:/gi)); 44 | }); 45 | 46 | it("Completed, chaos only price", async () => { 47 | const request = CreateIntentRequest({ 48 | name: name, 49 | locale: locale, 50 | dialogState: "COMPLETED", 51 | slots: { 52 | [SlotTypes.Gem]: { 53 | resolutions: { 54 | status: "ER_SUCCESS_MATCH", 55 | values: [{ 56 | name: "Value 2", 57 | }] 58 | } 59 | }, 60 | [SlotTypes.Level]: { 61 | value: "21" 62 | } 63 | } 64 | }); 65 | const response = await skill(request); 66 | expect(response).toMatchObject(ssml(/The price of a level 21 '.+' in Incursion league is 123.5 Chaos Orbs/gi)); 67 | }); 68 | 69 | it("Completed, chaos and exalt price and league", async () => { 70 | const request = CreateIntentRequest({ 71 | name: name, 72 | locale: locale, 73 | dialogState: "COMPLETED", 74 | slots: { 75 | [SlotTypes.Gem]: { 76 | resolutions: { 77 | status: "ER_SUCCESS_MATCH", 78 | values: [{ 79 | name: "Value 1", 80 | }] 81 | } 82 | }, 83 | [SlotTypes.Level]: { 84 | value: "21" 85 | }, 86 | [SlotTypes.Quality]: { 87 | value: "23" 88 | }, 89 | [SlotTypes.League]: { 90 | resolutions: { 91 | status: "ER_SUCCESS_MATCH", 92 | values: [{ 93 | name: "Standard", 94 | id: LeagueTypes.Standard 95 | }] 96 | } 97 | }, 98 | } 99 | }); 100 | const response = await skill(request); 101 | expect(response).toMatchObject(ssml(/The price of a level 21 23 quality '.+' in Standard league is 12.3 Exalted Orbs or 123.5 Chaos Orbs/gi)); 102 | }); 103 | 104 | it("Completed, fix level or quality slot detecting 2023 instead of splitting it", async () => { 105 | const expectedResponse = partial({ 106 | response: { 107 | directives: [ 108 | { 109 | type: "Dialog.Delegate", 110 | updatedIntent: { 111 | name: name, 112 | slots: { 113 | [SlotTypes.Level]: { 114 | name: SlotTypes.Level, 115 | value: "21" 116 | }, 117 | [SlotTypes.Quality]: { 118 | name: SlotTypes.Quality, 119 | value: "23" 120 | }, 121 | } 122 | } 123 | } 124 | ] 125 | } 126 | }); 127 | let request = CreateIntentRequest({ 128 | name: name, 129 | locale: locale, 130 | dialogState: "STARTED", 131 | slots: { 132 | [SlotTypes.Gem]: { 133 | resolutions: { 134 | status: "ER_SUCCESS_MATCH", 135 | values: [{ 136 | name: "Value 1", 137 | }] 138 | } 139 | }, 140 | [SlotTypes.Level]: { 141 | value: "2123" 142 | }, 143 | [SlotTypes.League]: { 144 | resolutions: { 145 | status: "ER_SUCCESS_MATCH", 146 | values: [{ 147 | name: "Standard", 148 | id: LeagueTypes.Standard 149 | }] 150 | } 151 | }, 152 | } 153 | }); 154 | let response = await skill(request); 155 | expect(response).toMatchObject(expectedResponse); 156 | 157 | request = CreateIntentRequest({ 158 | name: name, 159 | locale: locale, 160 | dialogState: "STARTED", 161 | slots: { 162 | [SlotTypes.Gem]: { 163 | resolutions: { 164 | status: "ER_SUCCESS_MATCH", 165 | values: [{ 166 | name: "Value 1", 167 | }] 168 | } 169 | }, 170 | [SlotTypes.Level]: { 171 | value: "" 172 | }, 173 | [SlotTypes.Quality]: { 174 | value: "2123" 175 | }, 176 | [SlotTypes.League]: { 177 | resolutions: { 178 | status: "ER_SUCCESS_MATCH", 179 | values: [{ 180 | name: "Standard", 181 | id: LeagueTypes.Standard 182 | }] 183 | } 184 | }, 185 | } 186 | }); 187 | response = await skill(request); 188 | expect(response).toMatchObject(expectedResponse); 189 | }); 190 | 191 | it("Completed, not found", async () => { 192 | const request = CreateIntentRequest({ 193 | name: name, 194 | locale: locale, 195 | dialogState: "COMPLETED", 196 | slots: { 197 | [SlotTypes.Gem]: { 198 | resolutions: { 199 | status: "ER_SUCCESS_MATCH", 200 | values: [{ 201 | name: "Value 3", 202 | }] 203 | } 204 | }, 205 | [SlotTypes.Level]: { 206 | value: "21" 207 | }, 208 | } 209 | }); 210 | const response = await skill(request); 211 | expect(response).toMatchObject(ssml(/Sorry, I couldn't find the price of the item you requested/gi)); 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Xzya/alexa-poe-helper.svg?branch=master)](https://travis-ci.org/Xzya/alexa-poe-helper) 2 | [![codecov](https://codecov.io/gh/Xzya/alexa-poe-helper/branch/master/graph/badge.svg)](https://codecov.io/gh/Xzya/alexa-poe-helper) 3 | 4 | # Path of Exile Helper for Alexa 5 | 6 | This is helper Alexa skill for [Path of Exile](https://www.pathofexile.com/). 7 | 8 | ## What it can do 9 | 10 | The skill allows you to price check items and find the rewards for quests. 11 | 12 | ### Price checking 13 | 14 | Items are split into four categories, each supporting different combinations of variables: 15 | 16 | | Category | Item types | 17 | | --- | --- | 18 | | Currency | `Currency` and `fragments` | 19 | | Linked items | `Unique weapons` and `unique armours` | 20 | | Skill gems | `Skill gems` | 21 | | Other items | `Divination cards`, `prophecies`, `maps`, `unique maps`, `unique jewels`, `unique flasks`, `unique accessories` | 22 | 23 | You can include the league in all queries. If no league is specified, it will default to the current temporary challenge league. 24 | 25 | #### Currency 26 | 27 | | Type | Variable | 28 | | --- | --- | 29 | | Required | `item name` | 30 | | Optional | `quantity`, `league` | 31 | 32 | Example queries: 33 | 34 | - Alexa, ask p. o. e. what is the price of a `Mirror of Kalandra` in `Standard` 35 | - Alexa, ask p. o. e. what is the price of `ten` `Exalted Orbs` in `Hardcore` league 36 | - Alexa, ask p. o. e. what is the price of `five hundred` `fusings` 37 | - Alexa, ask p. o. e. what is the price of a `Fragment of the Minotaur` 38 | 39 | #### Linked items 40 | 41 | | Type | Variable | 42 | | --- | --- | 43 | | Required | `item name` | 44 | | Optional | `links`, `league` | 45 | 46 | Example queries: 47 | 48 | - Alexa, ask p. o. e. what is the price of a `six linked` `Loreweave` in `Standard` 49 | - Alexa, ask p. o. e. what is the price of a `five linked` `belly` 50 | - Alexa, ask p. o. e. what is the price of a `Starforge` 51 | - Alexa, ask p. o. e. what is the price of a `six linked` `Disfavour` 52 | 53 | #### Skill gems 54 | 55 | | Type | Variable | 56 | | --- | --- | 57 | | Required | `item name`, `level` | 58 | | Optional | `quality`, `league` | 59 | 60 | Example queries: 61 | 62 | - Alexa, ask p. o. e. what is the price of a `twenty one` `twenty three` `Kinetic Blast` in `Incursion` league 63 | - Alexa, ask p. o. e. what is the price a `level four` `Empower` 64 | 65 | #### Other items 66 | 67 | | Type | Variable | 68 | | --- | --- | 69 | | Required | `item name` | 70 | | Optional | `league` | 71 | 72 | Example queries: 73 | 74 | - Alexa, ask p. o. e. what is the price of a `Headhunter` 75 | - Alexa, ask p. o. e. what is the price of a `Dying Sun` in `Standard` 76 | - Alexa, ask p. o. e. what is the price of a `Pit of the Chimera Map` 77 | - Alexa, ask p. o. e. what is the price of a `House of Mirrors` 78 | 79 | ### Quest rewards 80 | 81 | You can check what is the reward for a quest, e.g.: 82 | 83 | - Alexa, ask p. o. e. what is the quest reward for `The Dweller of the Deep` 84 | - Alexa, ask p. o. e. what is the reward for `Kitava's Torments` 85 | 86 | --- 87 | 88 | ## Running the project 89 | 90 | ### Pre-requisites 91 | 92 | - Node.js 93 | - Register for an [AWS Account](https://aws.amazon.com/) 94 | - Register for an [Amazon Developer Account](https://developer.amazon.com/) 95 | - Install and Setup [ASK CLI](https://developer.amazon.com/docs/smapi/quick-start-alexa-skills-kit-command-line-interface.html) 96 | 97 | ### Installation 98 | 99 | - Install the dependencies 100 | 101 | ```bash 102 | $ npm install 103 | ``` 104 | 105 | ### Deployment 106 | 107 | **ASK CLI** will create the skill and the Lambda function for you. The Lambda function will be created in `us-east-1 (Northern Virginia)` by default. 108 | 109 | 1. Navigate to the project's root directory. you should see a file named 'skill.json' there. 110 | 111 | 2. Deploy the skill and the Lambda function in one step by running the following command: 112 | 113 | ```bash 114 | $ ask deploy 115 | ``` 116 | 117 | ### Local development 118 | 119 | In order to develop locally and see your changes reflected instantly, you will need to create an SSH tunnel or expose somehow your local development server. There are several services that allow you to do this, for example [ngrok](https://ngrok.com/) or [serveo.net](https://serveo.net/). 120 | 121 | #### Using servo.net 122 | 123 | This is the easiest to setup 124 | 125 | 1. You need to have an SSH client installed, then simply run 126 | 127 | ```bash 128 | $ ssh -R 80:localhost:3980 serveo.net 129 | Forwarding HTTP traffic from [https://YOUR_URL] 130 | Press g to start a GUI session and ctrl-c to quit. 131 | ``` 132 | 133 | 2. Once you see the URL, copy it and go to your Skill console. 134 | 135 | 3. Open the `Endpoint` menu and select `HTTPS` 136 | 137 | 4. Under `Default Region` paste the previous URL you copied. 138 | 139 | 5. On the select box choose: `My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority`. 140 | 141 | 6. You are done! Just run `npm start` to start the local server and begin testing the skill. 142 | 143 | #### Using ngrok.io 144 | 145 | 1. [Install ngrok](https://ngrok.com/download) 146 | 147 | 2. Run `ngrok http 3980` 148 | 149 | 3. Copy the URL and follow the same steps above from 3 to 6. 150 | 151 | ## Developer tasks 152 | 153 | | Command | Description | 154 | | --- | --- | 155 | | `clean` | Deletes the `dist` folder | 156 | | `build` | Builds the lambda function and exports it to the `dist` folder | 157 | | `deploy` | Builds the lambda function and deploys EVERYTHING (skill, model, lambda) | 158 | | `deploy:lambda` | Builds the lambda function and deploys it (just the lambda function) | 159 | | `deploy:local` | Deploys the skill details for the local profile, which will update the HTTPS endpoint | 160 | | `start` | Starts the local `express` server using `nodemon` for local development | 161 | 162 | To see the actual commands, check `package.json`. 163 | 164 | Also check the [ASK CLI Command Reference](https://developer.amazon.com/docs/smapi/ask-cli-command-reference.html) for more details on using the `ASK CLI`. 165 | 166 | ## Testing 167 | 168 | The project contains automated tests using [jest](https://jestjs.io/). 169 | 170 | ```bash 171 | $ npm run test 172 | ``` 173 | 174 | Taken from [the official hello world project](https://github.com/alexa/skill-sample-nodejs-hello-world/blob/master/instructions/7-cli.md#testing). 175 | 176 | 1. To test, you need to login to Alexa Developer Console, and **enable the "Test" switch on your skill from the "Test" Tab**. 177 | 178 | 2. Simulate verbal interaction with your skill through the command line (this might take a few moments) using the following example: 179 | 180 | ```bash 181 | $ ask simulate -l en-US -t "open greeter" 182 | 183 | ✓ Simulation created for simulation id: 4a7a9ed8-94b2-40c0-b3bd-fb63d9887fa7 184 | ◡ Waiting for simulation response{ 185 | "status": "SUCCESSFUL", 186 | ... 187 | ``` 188 | 189 | 3. Once the "Test" switch is enabled, your skill can be tested on devices associated with the developer account as well. Speak to Alexa from any enabled device, from your browser at [echosim.io](https://echosim.io/welcome), or through your Amazon Mobile App and say : 190 | 191 | ```text 192 | Alexa, start hello world 193 | ``` 194 | 195 | ## Customization 196 | 197 | Taken from [the official hello world project](https://github.com/alexa/skill-sample-nodejs-hello-world/blob/master/instructions/7-cli.md#customization). 198 | 199 | 1. ```./skill.json``` 200 | 201 | Change the skill name, example phrase, icons, testing instructions etc ... 202 | 203 | Remember than many information are locale-specific and must be changed for each locale (e.g. en-US, en-GB, de-DE, etc.) 204 | 205 | See the Skill [Manifest Documentation](https://developer.amazon.com/docs/smapi/skill-manifest.html) for more information. 206 | 207 | 2. ```./lambda/custom/index.ts``` 208 | 209 | Modify messages, and data from the source code to customize the skill. 210 | 211 | 3. ```./models/*.json``` 212 | 213 | Change the model definition to replace the invocation name and the sample phrase for each intent. Repeat the operation for each locale you are planning to support. 214 | 215 | 4. Remember to re-deploy your skill and Lambda function for your changes to take effect. 216 | 217 | ```bash 218 | $ ask deploy 219 | ``` 220 | 221 | ## Contributing 222 | 223 | Feel free to submit a pull request or create a new issue. 224 | 225 | ## License 226 | 227 | Open sourced under the [MIT license](./LICENSE.md). -------------------------------------------------------------------------------- /Intents.md: -------------------------------------------------------------------------------- 1 | # Intents 2 | 3 | Price check intents can be split into several groups: 4 | 5 | | Type | Variables | Intents | 6 | | --- | --- | --- | 7 | | Currency items | `{quantity}`, `{name}`, `{league}` | `CurrencyPriceCheckIntent`, `FragmentPriceCheckIntent` | 8 | | Linked items | `{links}`, `{name}`, `{league}` | `UniqueWeaponPriceCheckIntent`, `UniqueWeaponPriceCheckIntent` | 9 | | Skill gems | `{level}`, `{quality}`, `{name}`, `{league}` | `GemPriceCheckIntent` | 10 | | Normal items | `{name}`, `{league}` | `DivinationPriceCheckIntent`, `EssencePriceCheckIntent`, `MapPriceCheckIntent`, `ProphecyPriceCheckIntent`, `UniqueAccessoryPriceCheckIntent`, `UniqueFlaskPriceCheckIntent`, `UniqueJewelPriceCheckIntent`, `UniqueMapPriceCheckIntent` | 11 | 12 | All utterances have been generated on [this website](http://www.makermusings.com/amazon-echo-utterance-expander/), and they all follow similar patterns. 13 | 14 | ## Currency price check intents 15 | 16 | ### User utterances 17 | 18 | ```text 19 | {whatis} (/{article}/{quantity}) {} (/worth) (/in {league} (/league)) 20 | (/{whatis}) (/the) price of (/{article}/{quantity}) {} (/in {league} (/league)) 21 | (/{quantity}) {} (/price) (/in {league} (/league)) 22 | (/check) price (/in {league} (/league)) 23 | ``` 24 | 25 | ### Reprompts 26 | 27 | ```text 28 | What are you looking for? 29 | What do you want? 30 | What do you want to know the price of? 31 | ``` 32 | 33 | ### Reprompt user Utterances 34 | 35 | ```text 36 | (/{quantity}) {} (/in {league} (/league)) (/please) 37 | ``` 38 | 39 | ## Normal item price check intents 40 | 41 | ### User utterances 42 | 43 | ```text 44 | {whatis} (/{article}) {} (/worth) (/in {league} (/league)) 45 | (/{whatis}) (/the) price of (/{article}) {} (/in {league} (/league)) 46 | {} (/price) (/in {league} (/league)) 47 | (/check) price (/in {league} (/league)) 48 | ``` 49 | 50 | ### Reprompts 51 | 52 | ```text 53 | What are you looking for? 54 | What do you want? 55 | What do you want to know the price of? 56 | ``` 57 | 58 | ### Reprompt user Utterances 59 | 60 | ```text 61 | {} (/in {league} (/league)) (/please) 62 | ``` 63 | 64 | ## Linked item price check intents 65 | 66 | ### User utterances 67 | 68 | ```text 69 | {whatis} (/{article}) (/{links} (/link(/ed))) {} (/worth) (/in {league} (/league)) 70 | (/{whatis}) (/the) price of (/{article}) (/{links} (/link(/ed))) {} (/in {league} (/league)) 71 | (/{links} (/link(/ed))) {} (/price) (/in {league} (/league)) 72 | (/check) price (/in {league} (/league)) 73 | ``` 74 | 75 | ### Reprompts 76 | 77 | ```text 78 | What are you looking for? 79 | What do you want? 80 | What do you want to know the price of? 81 | ``` 82 | 83 | ### Reprompt user Utterances 84 | 85 | ```text 86 | (/{links} (/link(/ed))) {} (/in {league} (/league)) (/please) 87 | ``` 88 | 89 | ## Skill gem price check intents 90 | 91 | ### User utterances 92 | 93 | ```text 94 | {whatis} (/{article}) (/(/level) {level}) (/{quality} (/quality)) {} (/worth) (/in {league} (/league)) 95 | (/{whatis}) (/the) price of (/{article}) (/(/level) {level}) (/{quality} (/quality)) {} (/in {league} (/league)) 96 | (/(/level) {level}) (/{quality} (/quality)) {} (/price) (/in {league} (/league)) 97 | (/check) price (/in {league} (/league)) 98 | ``` 99 | 100 | ### Reprompts 101 | 102 | ```text 103 | What are you looking for? 104 | What do you want? 105 | What do you want to know the price of? 106 | ``` 107 | 108 | ### Reprompt user Utterances 109 | 110 | ```text 111 | (/(/level) {level}) (/{quality} (/quality)) {} (/in {league} (/league)) (/please) 112 | ``` 113 | 114 | ## Quest reward intents 115 | 116 | ### User utterances 117 | 118 | ```text 119 | (/{whatis}) (/the) (/quest) reward (/for) (/the) (/{quest}) (/quest) 120 | {quest} 121 | ``` 122 | 123 | ### Reprompts 124 | 125 | ```text 126 | What quest are you looking for? 127 | What is the name of the quest? 128 | Which quest are you looking for? 129 | ``` 130 | 131 | ### Reprompt user Utterances 132 | 133 | ```text 134 | {questName} 135 | {questName} quest 136 | ``` 137 | 138 | ## Currency 139 | 140 | ```text 141 | {whatis} (/{article}/{quantity}) {currency} (/worth) (/in {league} (/league)) 142 | (/{whatis}) (/the) price of (/{article}/{quantity}) {currency} (/in {league} (/league)) 143 | (/{quantity}) {currency} (/price) (/in {league} (/league)) 144 | (/check) currency price (/in {league} (/league)) 145 | ``` 146 | 147 | ## Fragments 148 | 149 | ```text 150 | {whatis} (/{article}/{quantity}) {fragment} (/worth) (/in {league} (/league)) 151 | (/{whatis}) (/the) price of (/{article}/{quantity}) {fragment} (/in {league} (/league)) 152 | (/{quantity}) {fragment} (/price) (/in {league} (/league)) 153 | (/check) fragment price (/in {league} (/league)) 154 | ``` 155 | 156 | ## Unique accessory 157 | 158 | ```text 159 | {whatis} (/{article}) {uniqueaccessory} (/worth) (/in {league} (/league)) 160 | (/{whatis}) (/the) price of (/{article}) {uniqueaccessory} (/in {league} (/league)) 161 | {uniqueaccessory} (/price) (/in {league} (/league)) 162 | (/check) accessory price (/in {league} (/league)) 163 | ``` 164 | 165 | ## Unique flasks 166 | 167 | ```text 168 | {whatis} (/{article}) {uniqueflask} (/worth) (/in {league} (/league)) 169 | (/{whatis}) (/the) price of (/{article}) {uniqueflask} (/in {league} (/league)) 170 | {uniqueflask} (/price) (/in {league} (/league)) 171 | (/check) flask price (/in {league} (/league)) 172 | ``` 173 | 174 | ## Unique jewels 175 | 176 | ```text 177 | {whatis} (/{article}) {uniquejewel} (/worth) (/in {league} (/league)) 178 | (/{whatis}) (/the) price of (/{article}) {uniquejewel} (/in {league} (/league)) 179 | {uniquejewel} (/price) (/in {league} (/league)) 180 | (/check) jewel price (/in {league} (/league)) 181 | ``` 182 | 183 | ## Maps 184 | 185 | ```text 186 | {whatis} (/{article}) {map} (/worth) (/in {league} (/league)) 187 | (/{whatis}) (/the) price of (/{article}) {map} (/in {league} (/league)) 188 | {map} (/price) (/in {league} (/league)) 189 | (/check) map price (/in {league} (/league)) 190 | ``` 191 | 192 | ## Unique maps 193 | 194 | ```text 195 | {whatis} (/{article}) {uniquemap} (/worth) (/in {league} (/league)) 196 | (/{whatis}) (/the) price of (/{article}) {uniquemap} (/in {league} (/league)) 197 | {uniquemap} (/price) (/in {league} (/league)) 198 | (/check) unique map price (/in {league} (/league)) 199 | ``` 200 | 201 | ## Essences 202 | 203 | ```text 204 | {whatis} (/{article}) {essence} (/worth) (/in {league} (/league)) 205 | (/{whatis}) (/the) price of (/{article}) {essence} (/in {league} (/league)) 206 | {essence} (/price) (/in {league} (/league)) 207 | (/check) essence price (/in {league} (/league)) 208 | ``` 209 | 210 | ## Divination cards 211 | 212 | ```text 213 | {whatis} (/{article}) {divination} (/worth) (/in {league} (/league)) 214 | (/{whatis}) (/the) price of (/{article}) {divination} (/in {league} (/league)) 215 | {divination} (/price) (/in {league} (/league)) 216 | (/check) divination price (/in {league} (/league)) 217 | ``` 218 | 219 | ## Prophecies 220 | 221 | ```text 222 | {whatis} (/{article}) {prophecy} (/worth) (/in {league} (/league)) 223 | (/{whatis}) (/the) price of (/{article}) {prophecy} (/in {league} (/league)) 224 | {prophecy} (/price) (/in {league} (/league)) 225 | (/check) prophecy price (/in {league} (/league)) 226 | ``` 227 | 228 | ## Unique armours 229 | 230 | ```text 231 | {whatis} (/{article}) (/{links} (/link(/ed))) {uniquearmour} (/worth) (/in {league} (/league)) 232 | (/{whatis}) (/the) price of (/{article}) (/{links} (/link(/ed))) {uniquearmour} (/in {league} (/league)) 233 | (/{links} (/link(/ed))) {uniquearmour} (/price) (/in {league} (/league)) 234 | (/check) armour price (/in {league} (/league)) 235 | ``` 236 | 237 | ## Unique weapons 238 | 239 | ```text 240 | {whatis} (/{article}) (/{links} (/link(/ed))) {uniqueweapon} (/worth) (/in {league} (/league)) 241 | (/{whatis}) (/the) price of (/{article}) (/{links} (/link(/ed))) {uniqueweapon} (/in {league} (/league)) 242 | (/{links} (/link(/ed))) {uniqueweapon} (/price) (/in {league} (/league)) 243 | (/check) weapon price (/in {league} (/league)) 244 | ``` 245 | 246 | ## Skill gems 247 | 248 | ```text 249 | {whatis} (/{article}) (/(/level) {level}) (/{quality} (/quality)) {gem} (/worth) (/in {league} (/league)) 250 | (/{whatis}) (/the) price of (/{article}) (/(/level) {level}) (/{quality} (/quality)) {gem} (/in {league} (/league)) 251 | (/(/level) {level}) (/{quality} (/quality)) {gem} (/price) (/in {league} (/league)) 252 | (/check) gem price (/in {league} (/league)) 253 | ``` 254 | -------------------------------------------------------------------------------- /slots/GEM.csv: -------------------------------------------------------------------------------- 1 | Abyssal Cry, 2 | Added Chaos Damage Support,,Added Chaos Damage, 3 | Added Cold Damage Support,,Added Cold Damage, 4 | Added Fire Damage Support,,Added Fire Damage, 5 | Added Lightning Damage Support,,Added Lightning Damage, 6 | Additional Accuracy Support,,Additional Accuracy, 7 | Advanced Traps Support,,Advanced Traps, 8 | Ancestral Call Support,,Ancestral Call, 9 | Ancestral Protector, 10 | Ancestral Warchief, 11 | Anger, 12 | Animate Guardian, 13 | Animate Weapon, 14 | Arc, 15 | Arcane Surge Support,,Arcane Surge, 16 | Arctic Armour, 17 | Arctic Breath, 18 | Assassin's Mark, 19 | Ball Lightning, 20 | Barrage, 21 | Bear Trap, 22 | Blade Flurry, 23 | Blade Vortex, 24 | Bladefall, 25 | Blasphemy Support,,Blasphemy, 26 | Blast Rain, 27 | Blight, 28 | Blind Support,,Blind, 29 | Blink Arrow, 30 | Block Chance Reduction Support,,Block Chance Reduction, 31 | Blood Magic Support,,Blood Magic, 32 | Blood Rage, 33 | Bloodlust Support,,Bloodlust, 34 | Bodyswap, 35 | Bone Offering, 36 | Brutality Support,,Brutality, 37 | Burning Arrow, 38 | Burning Damage Support,,Burning Damage, 39 | Cast On Critical Strike Support,,Cast On Critical Strike, 40 | Cast on Death Support,,Cast on Death, 41 | Cast on Melee Kill Support,,Cast on Melee Kill, 42 | Cast when Damage Taken Support,,Cast when Damage Taken, 43 | Cast when Stunned Support,,Cast when Stunned, 44 | Cast while Channelling Support,,Cast while Channelling, 45 | Caustic Arrow, 46 | Chain Support,,Chain, 47 | Chance to Bleed Support,,Chance to Bleed, 48 | Chance to Flee Support,,Chance to Flee, 49 | Charged Dash, 50 | Charged Traps Support,,Charged Traps, 51 | Clarity, 52 | Cleave, 53 | Cluster Traps Support,,Cluster Traps, 54 | Cold Penetration Support,,Cold Penetration, 55 | Cold Snap, 56 | Cold to Fire Support,,Cold to Fire, 57 | Combustion Support,,Combustion, 58 | Concentrated Effect Support,,Concentrated Effect, 59 | Conductivity, 60 | Contagion, 61 | Controlled Destruction Support,,Controlled Destruction, 62 | Conversion Trap, 63 | Convocation, 64 | Cremation, 65 | Culling Strike Support,,Culling Strike, 66 | Curse On Hit Support,,Curse On Hit, 67 | Cyclone, 68 | Damage on Full Life Support,,Damage on Full Life, 69 | Dark Pact, 70 | Deadly Ailments Support,,Deadly Ailments, 71 | Decay Support,,Decay, 72 | Decoy Totem, 73 | Desecrate, 74 | Despair, 75 | Determination, 76 | Detonate Dead, 77 | Detonate Mines, 78 | Devouring Totem, 79 | Discharge, 80 | Discipline, 81 | Dominating Blow, 82 | Double Strike, 83 | Dual Strike, 84 | Earthquake, 85 | Efficacy Support,,Efficacy, 86 | Elemental Damage with Attacks Support,,Elemental Damage with Attacks, 87 | Elemental Focus Support,,Elemental Focus, 88 | Elemental Hit, 89 | Elemental Proliferation Support,,Elemental Proliferation, 90 | Elemental Weakness, 91 | Empower Support,,Empower, 92 | Endurance Charge on Melee Stun Support,,Endurance Charge on Melee Stun, 93 | Enduring Cry, 94 | Enfeeble, 95 | Enhance Support,,Enhance, 96 | Enlighten Support,,Enlighten, 97 | Essence Drain, 98 | Ethereal Knives, 99 | Explosive Arrow, 100 | Explosive Trap, 101 | Faster Attacks Support,,Faster Attacks, 102 | Faster Casting Support,,Faster Casting, 103 | Faster Projectiles Support,,Faster Projectiles, 104 | Fire Nova Mine, 105 | Fire Penetration Support,,Fire Penetration, 106 | Fire Trap, 107 | Fireball, 108 | Firestorm, 109 | Flame Dash, 110 | Flame Surge, 111 | Flame Totem, 112 | Flameblast, 113 | Flamethrower Trap, 114 | Flammability, 115 | Flesh Offering, 116 | Flicker Strike, 117 | Fork Support,,Fork, 118 | Fortify Support,,Fortify, 119 | Freeze Mine, 120 | Freezing Pulse, 121 | Frenzy, 122 | Frost Blades, 123 | Frost Bomb, 124 | Frost Wall, 125 | Frostbite, 126 | Frostbolt, 127 | Generosity Support,,Generosity, 128 | Glacial Cascade, 129 | Glacial Hammer, 130 | Grace, 131 | Greater Multiple Projectiles Support,,Greater Multiple Projectiles,G. M. P., 132 | Ground Slam, 133 | Haste, 134 | Hatred, 135 | Heavy Strike, 136 | Herald of Ash, 137 | Herald of Ice, 138 | Herald of Thunder, 139 | Hypothermia Support,,Hypothermia, 140 | Ice Bite Support,,Ice Bite, 141 | Ice Crash, 142 | Ice Nova, 143 | Ice Shot, 144 | Ice Spear, 145 | Ice Trap, 146 | Ignite Proliferation Support,,Ignite Proliferation, 147 | Immolate Support,,Immolate, 148 | Immortal Call, 149 | Incinerate, 150 | Increased Area of Effect Support,,Increased Area of Effect, 151 | Increased Critical Damage Support,,Increased Critical Damage, 152 | Increased Critical Strikes Support,,Increased Critical Strikes, 153 | Increased Duration Support,,Increased Duration, 154 | Infernal Blow, 155 | Innervate Support,,Innervate, 156 | Iron Grip Support,,Iron Grip, 157 | Iron Will Support,,Iron Will, 158 | Item Quantity Support,,Item Quantity, 159 | Item Rarity Support,,Item Rarity, 160 | Kinetic Blast, 161 | Knockback Support,,Knockback, 162 | Lacerate, 163 | Leap Slam, 164 | Less Duration Support,,Less Duration, 165 | Lesser Multiple Projectiles Support,,Lesser Multiple Projectiles, 166 | Lesser Poison Support,,Lesser Poison, 167 | Life Gain on Hit Support,,Life Gain on Hit, 168 | Life Leech Support,,Life Leech, 169 | Lightning Arrow, 170 | Lightning Penetration Support,,Lightning Penetration, 171 | Lightning Spire Trap, 172 | Lightning Strike, 173 | Lightning Tendrils, 174 | Lightning Trap, 175 | Lightning Warp, 176 | Magma Orb, 177 | Maim Support,,Maim, 178 | Mana Leech Support,,Mana Leech, 179 | Melee Physical Damage Support,,Melee Physical Damage, 180 | Melee Splash Support,,Melee Splash, 181 | Minefield Support,,Minefield, 182 | Minion Damage Support,,Minion Damage, 183 | Minion Life Support,,Minion Life, 184 | Minion Speed Support,,Minion Speed, 185 | Minion and Totem Elemental Resistance Support,,Minion and Totem Elemental Resistance, 186 | Mirage Archer Support,,Mirage Archer, 187 | Mirror Arrow, 188 | Molten Shell, 189 | Molten Strike, 190 | Multiple Traps Support,,Multiple Traps, 191 | Multistrike Support,,Multistrike, 192 | Onslaught Support,,Onslaught, 193 | Orb of Storms, 194 | Phase Run, 195 | Physical Projectile Attack Damage Support,,Physical Projectile Attack Damage, 196 | Physical to Lightning Support,,Physical to Lightning, 197 | Pierce Support,,Pierce, 198 | Poacher's Mark, 199 | Point Blank Support,,Point Blank, 200 | Poison Support,,Poison, 201 | Portal, 202 | Power Charge On Critical Support,,Power Charge On Critical, 203 | Power Siphon, 204 | Projectile Weakness, 205 | Puncture, 206 | Punishment, 207 | Purity of Elements, 208 | Purity of Fire, 209 | Purity of Ice, 210 | Purity of Lightning, 211 | Rain of Arrows, 212 | Raise Spectre, 213 | Raise Zombie, 214 | Rallying Cry, 215 | Ranged Attack Totem Support,,Ranged Attack Totem, 216 | Reave, 217 | Reckoning, 218 | Reduced Mana Support,,Reduced Mana, 219 | Rejuvenation Totem, 220 | Remote Mine Support,,Remote Mine, 221 | Righteous Fire, 222 | Riposte, 223 | Ruthless Support,,Ruthless, 224 | Scorching Ray, 225 | Searing Bond, 226 | Seismic Trap, 227 | Shield Charge, 228 | Shock Nova, 229 | Shockwave Totem, 230 | Shrapnel Shot, 231 | Siege Ballista, 232 | Siphoning Trap, 233 | Slower Projectiles Support,,Slower Projectiles, 234 | Smoke Mine, 235 | Spark, 236 | Spectral Shield Throw, 237 | Spectral Throw, 238 | Spell Cascade Support,,Spell Cascade, 239 | Spell Echo Support,,Spell Echo, 240 | Spell Totem Support,,Spell Totem, 241 | Spirit Offering, 242 | Split Arrow, 243 | Static Strike, 244 | Storm Barrier Support,,Storm Barrier, 245 | Storm Burst, 246 | Storm Call, 247 | Stun Support,,Stun, 248 | Summon Chaos Golem, 249 | Summon Flame Golem, 250 | Summon Ice Golem, 251 | Summon Lightning Golem, 252 | Summon Phantasm on Kill Support,,Summon Phantasm on Kill, 253 | Summon Raging Spirit, 254 | Summon Skeleton, 255 | Summon Stone Golem, 256 | Sunder, 257 | Sweep, 258 | Swift Affliction Support,,Swift Affliction, 259 | Tectonic Slam, 260 | Tempest Shield, 261 | Temporal Chains, 262 | Tornado Shot, 263 | Trap Support,,Trap, 264 | Trap and Mine Damage Support,,Trap and Mine Damage, 265 | Unbound Ailments Support,,Unbound Ailments, 266 | Unearth, 267 | Vaal Arc, 268 | Vaal Blade Vortex, 269 | Vaal Blight, 270 | Vaal Burning Arrow, 271 | Vaal Clarity, 272 | Vaal Cold Snap, 273 | Vaal Cyclone, 274 | Vaal Detonate Dead, 275 | Vaal Discipline, 276 | Vaal Double Strike, 277 | Vaal Earthquake, 278 | Vaal Fireball, 279 | Vaal Flameblast, 280 | Vaal Glacial Hammer, 281 | Vaal Grace, 282 | Vaal Ground Slam, 283 | Vaal Haste, 284 | Vaal Ice Nova, 285 | Vaal Immortal Call, 286 | Vaal Impurity of Fire, 287 | Vaal Impurity of Ice, 288 | Vaal Impurity of Lightning, 289 | Vaal Lightning Strike, 290 | Vaal Lightning Trap, 291 | Vaal Lightning Warp, 292 | Vaal Molten Shell, 293 | Vaal Power Siphon, 294 | Vaal Rain of Arrows, 295 | Vaal Reave, 296 | Vaal Righteous Fire, 297 | Vaal Spark, 298 | Vaal Spectral Throw, 299 | Vaal Storm Call, 300 | Vaal Summon Skeletons, 301 | Vengeance, 302 | Vigilant Strike, 303 | Vile Toxins Support,,Vile Toxins, 304 | Viper Strike, 305 | Vitality, 306 | Void Manipulation Support,,Void Manipulation, 307 | Volatile Dead, 308 | Volley Support,,Volley, 309 | Vortex, 310 | Vulnerability, 311 | Warlord's Mark, 312 | Whirling Blades, 313 | Wild Strike, 314 | Wither, 315 | Wrath, -------------------------------------------------------------------------------- /lambda/custom/api/__mocks__/POENinjaClient.ts: -------------------------------------------------------------------------------- 1 | import { IPOENinjaClient, IPOENinjaClientSettings, CurrencyRequest, CurrencyResponse, ItemResponse, ItemRequest, SparkLine, CurrencyRequestTypes, ItemRequestTypes } from "../interfaces"; 2 | 3 | class DummyClient implements IPOENinjaClient { 4 | constructor(settings?: IPOENinjaClientSettings) { 5 | } 6 | 7 | currencies(req: CurrencyRequest) { 8 | return new Promise((fulfill, reject) => { 9 | const empty: SparkLine = { 10 | data: [], 11 | totalChange: 0, 12 | }; 13 | 14 | switch (req.type) { 15 | case CurrencyRequestTypes.Currency: 16 | case CurrencyRequestTypes.Fragment: 17 | return fulfill({ 18 | lines: [ 19 | { 20 | "currencyTypeName": "Value 1", 21 | "pay": { 22 | "id": -1, 23 | "league_id": -1, 24 | "pay_currency_id": -1, 25 | "get_currency_id": -1, 26 | "sample_time_utc": "2018-07-15T12:12:24.9206511Z", 27 | "count": -1, 28 | "value": -1, 29 | "data_point_count": -1, 30 | "includes_secondary": false 31 | }, 32 | "receive": { 33 | "id": -1, 34 | "league_id": -1, 35 | "pay_currency_id": -1, 36 | "get_currency_id": -1, 37 | "sample_time_utc": "2018-07-15T12:12:24.9206511Z", 38 | "count": 100, 39 | "value": 123.45, 40 | "data_point_count": -1, 41 | "includes_secondary": false 42 | }, 43 | "paySparkLine": empty, 44 | "receiveSparkLine": empty, 45 | "chaosEquivalent": -1, 46 | "lowConfidencePaySparkLine": empty, 47 | "lowConfidenceReceiveSparkLine": empty, 48 | } 49 | ], 50 | currencyDetails: [], 51 | }); 52 | } 53 | }); 54 | } 55 | 56 | items(req: ItemRequest) { 57 | return new Promise((fulfill, reject) => { 58 | const empty: SparkLine = { 59 | data: [], 60 | totalChange: 0, 61 | }; 62 | 63 | switch (req.type) { 64 | case ItemRequestTypes.DivinationCard: 65 | case ItemRequestTypes.Essence: 66 | case ItemRequestTypes.Map: 67 | case ItemRequestTypes.Prophecy: 68 | case ItemRequestTypes.UniqueAccessory: 69 | case ItemRequestTypes.UniqueFlask: 70 | case ItemRequestTypes.UniqueJewel: 71 | case ItemRequestTypes.UniqueMap: 72 | return fulfill({ 73 | lines: [ 74 | { 75 | "id": -1, 76 | "name": "Value 1", 77 | "icon": "", 78 | "mapTier": -1, 79 | "levelRequired": -1, 80 | "stackSize": -1, 81 | "links": -1, 82 | "itemClass": -1, 83 | "sparkline": empty, 84 | "lowConfidenceSparkline": empty, 85 | "implicitModifiers": [], 86 | "explicitModifiers": [], 87 | "flavourText": "", 88 | "corrupted": false, 89 | "gemLevel": -1, 90 | "gemQuality": -1, 91 | "itemType": "", 92 | "chaosValue": 123.45, 93 | "exaltedValue": 12.34, 94 | "count": 5 95 | }, 96 | { 97 | "id": -1, 98 | "name": "Value 2", 99 | "icon": "", 100 | "mapTier": -1, 101 | "levelRequired": -1, 102 | "stackSize": -1, 103 | "links": -1, 104 | "itemClass": -1, 105 | "sparkline": empty, 106 | "lowConfidenceSparkline": empty, 107 | "implicitModifiers": [], 108 | "explicitModifiers": [], 109 | "flavourText": "", 110 | "corrupted": false, 111 | "gemLevel": -1, 112 | "gemQuality": -1, 113 | "itemType": "", 114 | "chaosValue": 123.45, 115 | "exaltedValue": 0.5, 116 | "count": 5 117 | } 118 | ] 119 | }); 120 | 121 | case ItemRequestTypes.UniqueWeapon: 122 | case ItemRequestTypes.UniqueArmour: 123 | return fulfill({ 124 | lines: [ 125 | { 126 | "id": -1, 127 | "name": "Value 1", 128 | "icon": "", 129 | "mapTier": -1, 130 | "levelRequired": -1, 131 | "stackSize": -1, 132 | "links": 6, 133 | "itemClass": -1, 134 | "sparkline": empty, 135 | "lowConfidenceSparkline": empty, 136 | "implicitModifiers": [], 137 | "explicitModifiers": [], 138 | "flavourText": "", 139 | "corrupted": false, 140 | "gemLevel": -1, 141 | "gemQuality": -1, 142 | "itemType": "", 143 | "chaosValue": 123.45, 144 | "exaltedValue": 12.34, 145 | "count": 5 146 | }, 147 | { 148 | "id": -1, 149 | "name": "Value 2", 150 | "icon": "", 151 | "mapTier": -1, 152 | "levelRequired": -1, 153 | "stackSize": -1, 154 | "links": 5, 155 | "itemClass": -1, 156 | "sparkline": empty, 157 | "lowConfidenceSparkline": empty, 158 | "implicitModifiers": [], 159 | "explicitModifiers": [], 160 | "flavourText": "", 161 | "corrupted": false, 162 | "gemLevel": -1, 163 | "gemQuality": -1, 164 | "itemType": "", 165 | "chaosValue": 123.45, 166 | "exaltedValue": 0.5, 167 | "count": 5 168 | } 169 | ] 170 | }); 171 | 172 | case ItemRequestTypes.SkillGem: 173 | return fulfill({ 174 | lines: [ 175 | { 176 | "id": -1, 177 | "name": "Value 1", 178 | "icon": "", 179 | "mapTier": -1, 180 | "levelRequired": -1, 181 | "stackSize": -1, 182 | "links": -1, 183 | "itemClass": -1, 184 | "sparkline": empty, 185 | "lowConfidenceSparkline": empty, 186 | "implicitModifiers": [], 187 | "explicitModifiers": [], 188 | "flavourText": "", 189 | "corrupted": true, 190 | "gemLevel": 21, 191 | "gemQuality": 23, 192 | "itemType": "", 193 | "chaosValue": 123.45, 194 | "exaltedValue": 12.34, 195 | "count": 5 196 | }, 197 | { 198 | "id": -1, 199 | "name": "Value 2", 200 | "icon": "", 201 | "mapTier": -1, 202 | "levelRequired": -1, 203 | "stackSize": -1, 204 | "links": -1, 205 | "itemClass": -1, 206 | "sparkline": empty, 207 | "lowConfidenceSparkline": empty, 208 | "implicitModifiers": [], 209 | "explicitModifiers": [], 210 | "flavourText": "", 211 | "corrupted": true, 212 | "gemLevel": 21, 213 | "gemQuality": 23, 214 | "itemType": "", 215 | "chaosValue": 123.45, 216 | "exaltedValue": 0.5, 217 | "count": 5 218 | } 219 | ] 220 | }); 221 | } 222 | }); 223 | } 224 | } 225 | 226 | export const apiClient = new DummyClient(); 227 | -------------------------------------------------------------------------------- /skill.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest": { 3 | "publishingInformation": { 4 | "locales": { 5 | "en-US": { 6 | "summary": "Helper for Path of Exile which allows you to price check items and find the rewards for quests.", 7 | "examplePhrases": [ 8 | "Alexa, ask p. o. e. for the price of a Headhunter", 9 | "Alexa, ask p. o. e. for the price of twenty five Exalted Orbs in Standard league", 10 | "Alexa, ask p. o. e. for the price of a six linked Belly of the Beast" 11 | ], 12 | "keywords": [ 13 | "path", 14 | "of", 15 | "exile", 16 | "poe", 17 | "p.o.e.", 18 | "helper" 19 | ], 20 | "name": "Path of Exile Helper", 21 | "smallIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/ad51/05ca7196729a437c9e2c8d7304f94d2c/APP_ICON?versionId=2hkkdKqx.x7rE_urSCKkCn5QkRZyVopH&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=WXxyHS5SYNarUC7RB3vTNgk%2Bfow%3D", 22 | "description": "This is helper for Path of Exile by Grinding Gear Games. It can check the price of an item, or lookup a quest reward.\n\nCurrent items supported for price checking: Currency, fragments, divination cards, prophecies, skill gems, maps, unique maps, unique jewels, unique flasks, unique weapons, unique armors, unique accessories.\n\nYou can include the league in all queries. If no league is specified, it will default to the current temporary challenge league.\n\n# Currency\n\nRequired: item name\nOptional: quantity, league\n\n\"What is the price of ten Exalted Orbs in Incursion league?\"\n\n# Linked items\n\nRequired: item name\nOptional: links, league\n\n\"What is the price of a six linked Loreweave in Standard?\"\n\n# Skill gems\n\nRequired: item name, level\nOptional: quality, league\n\n\"What is the price of a twenty one twenty three Kinetic Blast in Hardcore Incursion league?\"\n\n# Other items\n\nRequired: item name\nOptional: league\n\n\"What is the price of a Headhunter in Incursion?\"\n\n\nThis skill is fan-made and not affiliated with Grinding Gear Games in any way.", 23 | "largeIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/144a/50bde57e2c0a428fb1d2d65862a35e1b/APP_ICON_LARGE?versionId=X5Edp0BeKCqGtzLTybLqq9_od9MtJR7c&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=xam1xrwElP2VmDiW4wXhl4qEL4w%3D" 24 | }, 25 | "en-CA": { 26 | "summary": "Helper for Path of Exile which allows you to price check items and find the rewards for quests.", 27 | "examplePhrases": [ 28 | "Alexa, ask p. o. e. for the price of a Headhunter", 29 | "Alexa, ask p. o. e. for the price of twenty five Exalted Orbs in Standard league", 30 | "Alexa, ask p. o. e. for the price of a six linked Belly of the Beast" 31 | ], 32 | "keywords": [ 33 | "path", 34 | "of", 35 | "exile", 36 | "poe", 37 | "p.o.e.", 38 | "helper" 39 | ], 40 | "name": "Path of Exile Helper", 41 | "smallIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/711b/ec53544d69b4483ca41be2ff19476e0c/APP_ICON?versionId=OaPqkQtm3Dhc0aO8PMCE5JyThCeJjxdE&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=XADMUmcQP00sZQ%2FTjGgNI%2FG6vV4%3D", 42 | "description": "This is helper for Path of Exile by Grinding Gear Games. It can check the price of an item, or lookup a quest reward.\n\nCurrent items supported for price checking: Currency, fragments, divination cards, prophecies, skill gems, maps, unique maps, unique jewels, unique flasks, unique weapons, unique armors, unique accessories.\n\nYou can include the league in all queries. If no league is specified, it will default to the current temporary challenge league.\n\n# Currency\n\nRequired: item name\nOptional: quantity, league\n\n\"What is the price of ten Exalted Orbs in Incursion league?\"\n\n# Linked items\n\nRequired: item name\nOptional: links, league\n\n\"What is the price of a six linked Loreweave in Standard?\"\n\n# Skill gems\n\nRequired: item name, level\nOptional: quality, league\n\n\"What is the price of a twenty one twenty three Kinetic Blast in Hardcore Incursion league?\"\n\n# Other items\n\nRequired: item name\nOptional: league\n\n\"What is the price of a Headhunter in Incursion?\"\n\n\nThis skill is fan-made and not affiliated with Grinding Gear Games in any way.", 43 | "largeIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/f1c0/48c171012a2f4c8fbecd24f90b6dbcb2/APP_ICON_LARGE?versionId=zMFFXdapFO8vYBKPzXNmDIbR4h6eSHtE&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=MGpxGaBOhRXpeBDOocJPZnjq91s%3D" 44 | }, 45 | "en-IN": { 46 | "summary": "Helper for Path of Exile which allows you to price check items and find the rewards for quests.", 47 | "examplePhrases": [ 48 | "Alexa, ask p. o. e. for the price of a Headhunter", 49 | "Alexa, ask p. o. e. for the price of twenty five Exalted Orbs in Standard league", 50 | "Alexa, ask p. o. e. for the price of a six linked Belly of the Beast" 51 | ], 52 | "keywords": [ 53 | "path", 54 | "of", 55 | "exile", 56 | "poe", 57 | "p.o.e.", 58 | "helper" 59 | ], 60 | "name": "Path of Exile Helper", 61 | "smallIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/b1a2/6145b15814964dea9eb560e8a75daa28/APP_ICON?versionId=mEogyU0dgU.p5q5n4r.kwtxBQ_gdlQFY&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=iIcI1fDuCSMQNAwGOagAfrQhqVU%3D", 62 | "description": "This is helper for Path of Exile by Grinding Gear Games. It can check the price of an item, or lookup a quest reward.\n\nCurrent items supported for price checking: Currency, fragments, divination cards, prophecies, skill gems, maps, unique maps, unique jewels, unique flasks, unique weapons, unique armors, unique accessories.\n\nYou can include the league in all queries. If no league is specified, it will default to the current temporary challenge league.\n\n# Currency\n\nRequired: item name\nOptional: quantity, league\n\n\"What is the price of ten Exalted Orbs in Incursion league?\"\n\n# Linked items\n\nRequired: item name\nOptional: links, league\n\n\"What is the price of a six linked Loreweave in Standard?\"\n\n# Skill gems\n\nRequired: item name, level\nOptional: quality, league\n\n\"What is the price of a twenty one twenty three Kinetic Blast in Hardcore Incursion league?\"\n\n# Other items\n\nRequired: item name\nOptional: league\n\n\"What is the price of a Headhunter in Incursion?\"\n\n\nThis skill is fan-made and not affiliated with Grinding Gear Games in any way.", 63 | "largeIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/629b/13a08a6428ce44da91a8ea20d7c6b1fb/APP_ICON_LARGE?versionId=LpaBJt1YA7ncFyoPeSBs6pugs0ec_Qdk&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=ULvnoX0IJfIpojDz1ZREKQj6TAM%3D" 64 | }, 65 | "en-AU": { 66 | "summary": "Helper for Path of Exile which allows you to price check items and find the rewards for quests.", 67 | "examplePhrases": [ 68 | "Alexa, ask p. o. e. for the price of a Headhunter", 69 | "Alexa, ask p. o. e. for the price of twenty five Exalted Orbs in Standard league", 70 | "Alexa, ask p. o. e. for the price of a six linked Belly of the Beast" 71 | ], 72 | "keywords": [ 73 | "path", 74 | "of", 75 | "exile", 76 | "poe", 77 | "p.o.e.", 78 | "helper" 79 | ], 80 | "name": "Path of Exile Helper", 81 | "smallIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/fd44/4eabe746fc114a2ba3b0375d78be8d0a/APP_ICON?versionId=lzSnKCQWxMuph4rPVXHg6khfO3I3GcnY&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=qUDylA5QG8etETs3mydF7sI0BfE%3D", 82 | "description": "This is helper for Path of Exile by Grinding Gear Games. It can check the price of an item, or lookup a quest reward.\n\nCurrent items supported for price checking: Currency, fragments, divination cards, prophecies, skill gems, maps, unique maps, unique jewels, unique flasks, unique weapons, unique armors, unique accessories.\n\nYou can include the league in all queries. If no league is specified, it will default to the current temporary challenge league.\n\n# Currency\n\nRequired: item name\nOptional: quantity, league\n\n\"What is the price of ten Exalted Orbs in Incursion league?\"\n\n# Linked items\n\nRequired: item name\nOptional: links, league\n\n\"What is the price of a six linked Loreweave in Standard?\"\n\n# Skill gems\n\nRequired: item name, level\nOptional: quality, league\n\n\"What is the price of a twenty one twenty three Kinetic Blast in Hardcore Incursion league?\"\n\n# Other items\n\nRequired: item name\nOptional: league\n\n\"What is the price of a Headhunter in Incursion?\"\n\n\nThis skill is fan-made and not affiliated with Grinding Gear Games in any way.", 83 | "largeIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/3f2f/424cadf6061b45b6bdeefe6dc69d4707/APP_ICON_LARGE?versionId=NKEMIoJT3OCucD6SeSnRg99JsYnPifQB&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=DOPg5uQDEOqjB8V1YX46OxAJe3Q%3D" 84 | }, 85 | "en-GB": { 86 | "summary": "Helper for Path of Exile which allows you to price check items and find the rewards for quests.", 87 | "examplePhrases": [ 88 | "Alexa, ask p. o. e. for the price of a Headhunter", 89 | "Alexa, ask p. o. e. for the price of twenty five Exalted Orbs in Standard league", 90 | "Alexa, ask p. o. e. for the price of a six linked Belly of the Beast" 91 | ], 92 | "keywords": [ 93 | "path", 94 | "of", 95 | "exile", 96 | "poe", 97 | "p.o.e.", 98 | "helper" 99 | ], 100 | "name": "Path of Exile Helper", 101 | "smallIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/38d6/9c4e58822e874bd48422e1f1ea46fb8c/APP_ICON?versionId=NywQCWTvy1Yccg3RpzmvQMFGWGCNpYK2&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=QQWKH2RSzyesK34JvW3K3drgsS4%3D", 102 | "description": "This is helper for Path of Exile by Grinding Gear Games. It can check the price of an item, or lookup a quest reward.\n\nCurrent items supported for price checking: Currency, fragments, divination cards, prophecies, skill gems, maps, unique maps, unique jewels, unique flasks, unique weapons, unique armors, unique accessories.\n\nYou can include the league in all queries. If no league is specified, it will default to the current temporary challenge league.\n\n# Currency\n\nRequired: item name\nOptional: quantity, league\n\n\"What is the price of ten Exalted Orbs in Incursion league?\"\n\n# Linked items\n\nRequired: item name\nOptional: links, league\n\n\"What is the price of a six linked Loreweave in Standard?\"\n\n# Skill gems\n\nRequired: item name, level\nOptional: quality, league\n\n\"What is the price of a twenty one twenty three Kinetic Blast in Hardcore Incursion league?\"\n\n# Other items\n\nRequired: item name\nOptional: league\n\n\"What is the price of a Headhunter in Incursion?\"\n\n\nThis skill is fan-made and not affiliated with Grinding Gear Games in any way.", 103 | "largeIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/5967/29e0d3af592e4a348acf34d5a123f050/APP_ICON_LARGE?versionId=r75y9CMj3rC0bcCZI.hpBFQ0Fdrz5TM4&AWSAccessKeyId=AKIAIEYXX6DHY7O4XWMA&Expires=1532635050&Signature=bpu7WkndLhFR6jnYiDiOvPCowtI%3D" 104 | } 105 | }, 106 | "isAvailableWorldwide": true, 107 | "testingInstructions": "The purpose of the skill is to price check items, and to check the rewards for quests.\n\nHere is how to check the reward of a quest:\n- \"What is the quest reward for The Dweller of the Deep?\"\n\nYou can find the full list of quests here [1].\n\nHere is how to check the price of an item:\n- \"What is the price of an Exalted Orb?\"\n- \"What is the price of one hundred fusings in Standard league?\"\n- \"What is the price of a six linked Loreweave?\"\n- \"What is the price of a twenty one twenty three Kinetic Blast?\"\n\nMost items found on this page [2] are available for price checking.\n\n[1]: https://pathofexile.gamepedia.com/Quest_Rewards\n[2]: https://poe.ninja/challenge/currency", 108 | "category": "KNOWLEDGE_AND_TRIVIA", 109 | "distributionCountries": [] 110 | }, 111 | "apis": { 112 | "custom": { 113 | "endpoint": { 114 | "sourceDir": "dist/custom" 115 | }, 116 | "interfaces": [] 117 | } 118 | }, 119 | "manifestVersion": "1.0", 120 | "privacyAndCompliance": { 121 | "allowsPurchases": false, 122 | "isExportCompliant": true, 123 | "containsAds": false, 124 | "isChildDirected": false, 125 | "usesPersonalInfo": false 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /__tests__/generic.ts: -------------------------------------------------------------------------------- 1 | import { skill, ssml, CreateIntentRequest, inProgressDelegate, partial } from "./helpers"; 2 | import { IntentTypes, LocaleTypes, SlotTypes } from "../lambda/custom/lib/constants"; 3 | import { LeagueTypes } from "../lambda/custom/api"; 4 | import { RequestEnvelope } from "../node_modules/ask-sdk-model"; 5 | 6 | // mock the poe.ninja api client 7 | jest.mock("../lambda/custom/api/POENinjaClient"); 8 | 9 | /** 10 | * Runs some generic tests for the given itent. This can be used for all 11 | * intents that just have the item + league. 12 | * 13 | * @param options 14 | */ 15 | export function GenericTest(options: { 16 | intentName: IntentTypes; 17 | slotName: SlotTypes; 18 | }) { 19 | const name = options.intentName; 20 | const locale = LocaleTypes.enUS; 21 | 22 | it("InProgress", async () => { 23 | const request = CreateIntentRequest({ 24 | name: name, 25 | locale: locale, 26 | dialogState: "IN_PROGRESS", 27 | }); 28 | const response = await skill(request); 29 | expect(response).toMatchObject(inProgressDelegate(name)); 30 | }); 31 | 32 | it("InProgress ambiguous", async () => { 33 | const request = CreateIntentRequest({ 34 | name: name, 35 | locale: locale, 36 | dialogState: "IN_PROGRESS", 37 | slots: { 38 | [options.slotName]: { 39 | resolutions: { 40 | status: "ER_SUCCESS_MATCH", 41 | values: [{ 42 | name: "Value 1", 43 | }, 44 | { 45 | name: "Value 2", 46 | }] 47 | } 48 | } 49 | } 50 | }); 51 | const response = await skill(request); 52 | expect(response).toMatchObject(ssml(/Which would you like:/gi)); 53 | }); 54 | 55 | it("Completed, chaos only price", async () => { 56 | const request = CreateIntentRequest({ 57 | name: name, 58 | locale: locale, 59 | dialogState: "COMPLETED", 60 | slots: { 61 | [options.slotName]: { 62 | resolutions: { 63 | status: "ER_SUCCESS_MATCH", 64 | values: [{ 65 | name: "Value 2", 66 | }] 67 | } 68 | } 69 | } 70 | }); 71 | const response = await skill(request); 72 | expect(response).toMatchObject(ssml(/The price of 1 '.+' in Incursion league is 123.5 Chaos Orbs/gi)); 73 | }); 74 | 75 | it("Completed, chaos and exalt price and league", async () => { 76 | const request = CreateIntentRequest({ 77 | name: name, 78 | locale: locale, 79 | dialogState: "COMPLETED", 80 | slots: { 81 | [options.slotName]: { 82 | resolutions: { 83 | status: "ER_SUCCESS_MATCH", 84 | values: [{ 85 | name: "Value 1", 86 | }] 87 | } 88 | }, 89 | [SlotTypes.League]: { 90 | resolutions: { 91 | status: "ER_SUCCESS_MATCH", 92 | values: [{ 93 | name: "Standard", 94 | id: LeagueTypes.Standard 95 | }] 96 | } 97 | }, 98 | } 99 | }); 100 | const response = await skill(request); 101 | expect(response).toMatchObject(ssml(/The price of 1 '.+' in Standard league is 12.3 Exalted Orbs or 123.5 Chaos Orbs/gi)); 102 | }); 103 | 104 | it("Completed, not found", async () => { 105 | const request = CreateIntentRequest({ 106 | name: name, 107 | locale: locale, 108 | dialogState: "COMPLETED", 109 | slots: { 110 | [options.slotName]: { 111 | resolutions: { 112 | status: "ER_SUCCESS_MATCH", 113 | values: [{ 114 | name: "Value 3", 115 | }] 116 | } 117 | }, 118 | } 119 | }); 120 | const response = await skill(request); 121 | expect(response).toMatchObject(ssml(/Sorry, I couldn't find the price of the item you requested/gi)); 122 | }); 123 | 124 | it("Multiple resolution authorities", async () => { 125 | const request = partial({ 126 | context: { 127 | System: {} 128 | }, 129 | request: { 130 | type: "IntentRequest", 131 | locale: locale, 132 | dialogState: "COMPLETED", 133 | intent: { 134 | name: name, 135 | slots: { 136 | [options.slotName]: { 137 | name: options.slotName, 138 | resolutions: { 139 | resolutionsPerAuthority: [{ 140 | status: { 141 | code: "ER_SUCCESS_NO_MATCH" 142 | }, 143 | }, 144 | { 145 | status: { 146 | code: "ER_SUCCESS_MATCH" 147 | }, 148 | values: [{ 149 | value: { 150 | name: "Value 2" 151 | } 152 | }] 153 | }] 154 | } 155 | } 156 | } 157 | } 158 | } 159 | }); 160 | const response = await skill(request); 161 | // console.log(JSON.stringify(response, null, 2)); 162 | expect(response).toMatchObject(ssml(/The price of 1 '.+' in Incursion league is 123.5 Chaos Orbs/gi)); 163 | }); 164 | } 165 | 166 | /** 167 | * Runs tests for items with links, e.g. armour and weapons. 168 | * 169 | * @param options 170 | */ 171 | export function LinkedItemTest(options: { 172 | intentName: IntentTypes; 173 | slotName: SlotTypes; 174 | }) { 175 | const name = options.intentName; 176 | const locale = LocaleTypes.enUS; 177 | 178 | it("Completed, linked", async () => { 179 | const request = CreateIntentRequest({ 180 | name: name, 181 | locale: locale, 182 | dialogState: "COMPLETED", 183 | slots: { 184 | [options.slotName]: { 185 | resolutions: { 186 | status: "ER_SUCCESS_MATCH", 187 | values: [{ 188 | name: "Value 1", 189 | }] 190 | } 191 | }, 192 | [SlotTypes.Links]: { 193 | value: "6" 194 | }, 195 | [SlotTypes.League]: { 196 | resolutions: { 197 | status: "ER_SUCCESS_MATCH", 198 | values: [{ 199 | name: "Standard", 200 | id: LeagueTypes.Standard 201 | }] 202 | } 203 | }, 204 | } 205 | }); 206 | const response = await skill(request); 207 | expect(response).toMatchObject(ssml(/The price of a 6 linked '.+' in Standard league is 12.3 Exalted Orbs or 123.5 Chaos Orbs/gi)); 208 | }); 209 | } 210 | 211 | /** 212 | * Runs tests for currency items, e.g. currency and fragments. 213 | * 214 | * @param options 215 | */ 216 | export function CurrencyItemTest(options: { 217 | intentName: IntentTypes; 218 | slotName: SlotTypes; 219 | }) { 220 | const name = options.intentName; 221 | const locale = LocaleTypes.enUS; 222 | 223 | it("InProgress", async () => { 224 | const request = CreateIntentRequest({ 225 | name: name, 226 | locale: locale, 227 | dialogState: "IN_PROGRESS", 228 | }); 229 | const response = await skill(request); 230 | expect(response).toMatchObject(inProgressDelegate(name)); 231 | }); 232 | 233 | it("InProgress ambiguous", async () => { 234 | const request = CreateIntentRequest({ 235 | name: name, 236 | locale: locale, 237 | dialogState: "IN_PROGRESS", 238 | slots: { 239 | [options.slotName]: { 240 | resolutions: { 241 | status: "ER_SUCCESS_MATCH", 242 | values: [{ 243 | name: "Value 1", 244 | }, 245 | { 246 | name: "Value 2", 247 | }] 248 | } 249 | } 250 | } 251 | }); 252 | const response = await skill(request); 253 | expect(response).toMatchObject(ssml(/Which would you like:/gi)); 254 | }); 255 | 256 | it("Completed, just currency", async () => { 257 | const request = CreateIntentRequest({ 258 | name: name, 259 | locale: locale, 260 | dialogState: "COMPLETED", 261 | slots: { 262 | [options.slotName]: { 263 | resolutions: { 264 | status: "ER_SUCCESS_MATCH", 265 | values: [{ 266 | name: "Value 1", 267 | }] 268 | } 269 | } 270 | } 271 | }); 272 | const response = await skill(request); 273 | expect(response).toMatchObject(ssml(/The price of 1 '.+' in Incursion league is 123.5 Chaos Orbs/gi)); 274 | }); 275 | 276 | it("Completed, currency with quantity and league", async () => { 277 | const request = CreateIntentRequest({ 278 | name: name, 279 | locale: locale, 280 | dialogState: "COMPLETED", 281 | slots: { 282 | [options.slotName]: { 283 | resolutions: { 284 | status: "ER_SUCCESS_MATCH", 285 | values: [{ 286 | name: "Value 1", 287 | }] 288 | } 289 | }, 290 | [SlotTypes.Quantity]: { 291 | value: "25", 292 | }, 293 | [SlotTypes.League]: { 294 | resolutions: { 295 | status: "ER_SUCCESS_MATCH", 296 | values: [{ 297 | name: "Standard", 298 | id: LeagueTypes.Standard 299 | }] 300 | } 301 | }, 302 | } 303 | }); 304 | const response = await skill(request); 305 | // 123.45 * 25 = 3086.25 => rounds to 3086.3 306 | expect(response).toMatchObject(ssml(/The price of 25 '.+' in Standard league is 3086.3 Chaos Orbs/gi)); 307 | }); 308 | 309 | it("Completed, fragment with quantity and league", async () => { 310 | const request = CreateIntentRequest({ 311 | name: name, 312 | locale: locale, 313 | dialogState: "COMPLETED", 314 | slots: { 315 | [options.slotName]: { 316 | resolutions: { 317 | status: "ER_SUCCESS_MATCH", 318 | values: [{ 319 | name: "Value 1", 320 | }] 321 | } 322 | }, 323 | [SlotTypes.Quantity]: { 324 | value: "25", 325 | }, 326 | [SlotTypes.League]: { 327 | resolutions: { 328 | status: "ER_SUCCESS_MATCH", 329 | values: [{ 330 | name: "Standard", 331 | id: LeagueTypes.Standard 332 | }] 333 | } 334 | }, 335 | } 336 | }); 337 | const response = await skill(request); 338 | // 123.45 * 25 = 3086.25 => rounds to 3086.3 339 | expect(response).toMatchObject(ssml(/The price of 25 '.+' in Standard league is 3086.3 Chaos Orbs/gi)); 340 | }); 341 | 342 | it("Completed, not found", async () => { 343 | const request = CreateIntentRequest({ 344 | name: name, 345 | locale: locale, 346 | dialogState: "COMPLETED", 347 | slots: { 348 | [options.slotName]: { 349 | resolutions: { 350 | status: "ER_SUCCESS_MATCH", 351 | values: [{ 352 | name: "Value 3", 353 | }] 354 | } 355 | }, 356 | } 357 | }); 358 | const response = await skill(request); 359 | expect(response).toMatchObject(ssml(/Sorry, I couldn't find the exchange for the currency you requested/gi)); 360 | }); 361 | } 362 | --------------------------------------------------------------------------------