-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
dexa.ts
343 lines (303 loc) · 11.1 KB
/
dexa.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
import { toTitleCase } from '@klasa/utils';
import { app as AlexaApp, request as Request, response as Response } from 'alexa-app';
import ApolloClient, { ApolloQueryResult } from 'apollo-boost';
import { remove as removeDiacritics } from 'confusables';
import 'cross-fetch/polyfill';
import gql from 'graphql-tag';
import * as c from './constants';
const enum SLOTS {
POKEMON = 'POKEMON',
ITEM = 'ITEM',
ABILITY = 'ABILITY',
MOVE = 'MOVE'
}
const enum INTENT_NAMES {
DEX_INTENT = 'DexIntent',
MOVE_INTENT = 'MoveIntent',
ITEM_INTENT = 'ItemIntent',
ABILITY_INTENT = 'AbilityIntent'
}
export default class extends AlexaApp {
private apollo: ApolloClient<unknown>;
private DEV = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
private dFilter = this.removeNullAndUndefined.bind(this);
private dElectricOrIce = /(electric|ice)/i;
public constructor() {
super('dexa');
this.apollo = new ApolloClient({
uri: this.DEV ? 'http://localhost:4000' : 'https://favware.tech/api'
});
this.init();
}
public init() {
this.launchIntent();
this.stopIntent();
this.cancelIntent();
this.helpIntent();
this.errorHandler();
this.intent(
INTENT_NAMES.DEX_INTENT,
{
slots: { [SLOTS.POKEMON]: SLOTS.POKEMON },
utterances: [`data on {-|${SLOTS.POKEMON}}`, `pokemon data for {-|${SLOTS.POKEMON}}`, `pokemon data {-|${SLOTS.POKEMON}}`]
},
(req, res) => {
const pokemon = removeDiacritics(req.slot(SLOTS.POKEMON));
return this.DexIntent(res, req, pokemon);
}
);
this.intent(
INTENT_NAMES.MOVE_INTENT,
{
slots: { [SLOTS.MOVE]: SLOTS.MOVE },
utterances: [`move data for {-|${SLOTS.MOVE}}`, `move data {-|${SLOTS.MOVE}}`]
},
(req, res) => {
const move = removeDiacritics(req.slot(SLOTS.MOVE));
return this.MoveIntent(res, req, move);
}
);
this.intent(
INTENT_NAMES.ITEM_INTENT,
{
slots: { [SLOTS.ITEM]: SLOTS.ITEM },
utterances: [`item data {-|${SLOTS.ITEM}}`, `item data for {-|${SLOTS.ITEM}}`]
},
(req, res) => {
const item = removeDiacritics(req.slot(SLOTS.ITEM));
return this.ItemIntent(res, req, item);
}
);
this.intent(
INTENT_NAMES.ABILITY_INTENT,
{
slots: { [SLOTS.ABILITY]: SLOTS.ABILITY },
utterances: [`ability data for {-|${SLOTS.ABILITY}}`, `ability data {-|${SLOTS.ABILITY}}`]
},
(req, res) => {
const ability = removeDiacritics(req.slot(SLOTS.ABILITY));
return this.AbilityIntent(res, req, ability);
}
);
}
public async DexIntent(res: Response, req: Request, pokemon: string) {
try {
const { data }: ApolloQueryResult<c.GraphQLPokemonResponse<'getPokemonDetailsByFuzzy'>> = await this.apollo.query({
query: gql`
${c.getPokemonDetailsByFuzzy(pokemon)}
`
});
const { getPokemonDetailsByFuzzy: pokeData } = data;
const titleCaseName = toTitleCase(pokeData.species);
const prevos = c.parsePrevos(pokeData);
const evos = c.parseEvos(pokeData);
const text = [
`${titleCaseName}, number ${pokeData.num}, ${pokeData.flavorTexts[0].flavor}`,
`It is ${pokeData.types.join(' ')} type.`,
prevos.length ? `Its pre-evolution${prevos.length >= 2 ? 's are' : ' is'} ${prevos.join(' and ')}.` : null,
evos.length ? `It evolves into ${evos.join(' and ')}.` : null,
`${titleCaseName} is typically ${pokeData.height} meters tall and weighs about ${pokeData.weight} kilograms.`,
`${c.parseGenderRatio(pokeData.gender)}.`
]
.filter(this.dFilter)
.join(' ');
return res
.say(text)
.card({
type: 'Simple',
title: `Dex Pokémon Data for ${titleCaseName}`,
content: text
})
.shouldEndSession(false);
} catch {
return this.throwQueryErr(res, req, SLOTS.POKEMON);
}
}
public async MoveIntent(res: Response, req: Request, move: string) {
try {
const { data }: ApolloQueryResult<c.GraphQLPokemonResponse<'getMoveDetailsByFuzzy'>> = await this.apollo.query({
query: gql`
${c.getMoveDetailsByFuzzy(move)}
`
});
const { getMoveDetailsByFuzzy: moveData } = data;
const titleCaseName = toTitleCase(moveData.name);
const text = [
`${titleCaseName}, ${moveData.desc || moveData.shortDesc}`,
`${titleCaseName} is a${this.dElectricOrIce.test(moveData.type) ? 'n' : ''} ${moveData.type} type move.`,
`${titleCaseName} ${c.parseMoveBasePower(moveData.basePower, moveData.category)} and it has ${moveData.pp} pp.`,
`Under normal conditions this move will have a priority of ${moveData.priority} and an accuracy of ${moveData.accuracy}%.`,
`In battles with multiple Pokémon on each side it will have an effect on ${c.parseMoveTarget(moveData.target)}.`,
moveData.isZ ? `This move is a Z Move and requires the Z-Crystal ${moveData.isZ}.` : null,
moveData.isGMax ? `This move is a G MAX move and can only be used by G Max ${moveData.isGMax}.` : null,
moveData.isNonstandard !== 'Past' ? `${titleCaseName} is available in the generation 8 games.` : null
]
.filter(this.dFilter)
.join(' ');
return res
.say(text)
.card({
type: 'Simple',
title: `Dex Move Data for ${titleCaseName}`,
content: text
})
.shouldEndSession(false);
} catch {
return this.throwQueryErr(res, req, SLOTS.MOVE);
}
}
private async ItemIntent(res: Response, req: Request, item: string) {
try {
const { data }: ApolloQueryResult<c.GraphQLPokemonResponse<'getItemDetailsByFuzzy'>> = await this.apollo.query({
query: gql`
${c.getItemDetailsByFuzzy(item)}
`
});
const { getItemDetailsByFuzzy: itemData } = data;
const titleCaseName = toTitleCase(itemData.name);
const text = [
`${titleCaseName}, ${itemData.desc}`,
`It was introduced in generation ${itemData.generationIntroduced}.`,
`${titleCaseName} is ${itemData.isNonstandard === 'Past' ? 'not ' : ''}available in Generation 8.`
]
.join(' ')
.replace(/([0-9]{1}(\.[0-9]){0,1})x/gm, '$1 times');
return res
.say(text)
.card({
type: 'Simple',
title: `Dex Item Data for ${titleCaseName}`,
content: text
})
.shouldEndSession(false);
} catch {
return this.throwQueryErr(res, req, SLOTS.ITEM);
}
}
private async AbilityIntent(res: Response, req: Request, ability: string) {
try {
const { data }: ApolloQueryResult<c.GraphQLPokemonResponse<'getAbilityDetailsByFuzzy'>> = await this.apollo.query({
query: gql`
${c.getAbilityDetailsByFuzzy(ability)}
`
});
const { getAbilityDetailsByFuzzy: abilityData } = data;
const titleCaseName = toTitleCase(abilityData.name);
const text = `${titleCaseName}, ${abilityData.desc || abilityData.shortDesc}`;
return res
.say(text)
.card({
type: 'Simple',
title: `Dex Ability Data for ${titleCaseName}`,
content: text
})
.shouldEndSession(false);
} catch {
return this.throwQueryErr(res, req, SLOTS.ABILITY);
}
}
private errorHandler() {
this.error = (_exc: Error, req, res) => {
try {
switch (req.data.request.intent.name) {
case INTENT_NAMES.DEX_INTENT:
return res.say(`I couldn't find a Pokémon for ${req.slot(SLOTS.POKEMON)}. Are you sure you spelled that correctly?`);
case INTENT_NAMES.ABILITY_INTENT:
return res.say(`I couldn't find an Ability for ${req.slot(SLOTS.ABILITY)}. Are you sure you spelled that correctly?`);
case INTENT_NAMES.MOVE_INTENT:
return res.say(`I couldn't find an Move for ${req.slot(SLOTS.MOVE)}. I only support moves that have are used inside battles`);
case INTENT_NAMES.ITEM_INTENT:
return res.say(`I couldn't find an Item for ${req.slot(SLOTS.ITEM)}. Is that really an item that can be used in battle?`);
default:
return this.throwSystemErr(res);
}
} catch (err) {
return this.throwSystemErr(res);
}
};
}
private helpIntent() {
return this.intent(
'AMAZON.HelpIntent',
{
slots: {},
utterances: ['what are your commands', 'for help', 'help']
},
(_req, res) => {
const helpOutput = [
'Dexa provides many sources of information, Pokémon, Items, Abilities and Moves. Respectively these can be invoked with.',
'1: Ask Dexa Browser pokémon data.',
'2: Ask Dexa Browser item data.',
'3: Ask Dexa Browser ability data.',
'4: Ask Dexa Browser move data.',
'',
'You can always stop or cancel anything I am saying by saying "Alexa Stop" or "Alexa Cancel".',
'If you want to start browsing you can request something now.'
].join('\n');
res
.say(helpOutput)
.reprompt('I did not quite catch that, could you repeat it?')
.shouldEndSession(false);
}
);
}
private cancelIntent() {
return this.intent(
'AMAZON.CancelIntent',
{
slots: {},
utterances: ['cancel', 'quit']
},
(_req, res) => {
const cancelOutput = 'No problem. Request cancelled.';
res.say(cancelOutput).shouldEndSession(true);
}
);
}
private stopIntent() {
return this.intent(
'AMAZON.StopIntent',
{
slots: {},
utterances: ['stop', 'end']
},
(_req, res) => {
const stopOutput = "Don't you worry, I'll be back";
res.say(stopOutput).shouldEndSession(true);
}
);
}
private launchIntent() {
return this.launch((_req, res) => {
const prompt = [
'Welcome to Dexa, your one stop place for PokéDex information.',
'You can start browsing right away by giving me a command, or respond with "help" to learn all my commands.',
'If you want to stop Dexa, then respond with "Alexa Stop".'
].join(' ');
const reprompt = 'I did not quite catch that, could you repeat it?';
res
.say(prompt)
.reprompt(reprompt)
.shouldEndSession(false);
});
}
private removeNullAndUndefined<TValue>(value: TValue | null | undefined): value is TValue {
return value !== null && value !== undefined;
}
private throwSystemErr(res: Response) {
return res
.say('Something went awfully wrong browsing my dataset. Please use "Alexa ask Dexa Browser for help" if you are unsure how to use Dexa')
.shouldEndSession(false);
}
private throwQueryErr(res: Response, req: Request, slot: SLOTS) {
const prompt = [
'I am sorry but I could not resolve that query.',
req.slot(slot) ? `I think you said ${removeDiacritics(req.slot(slot))}? ` : '',
'Maybe try again, or respond with "Alexa Cancel" if you want to stop.'
].join(' ');
return res
.say(prompt)
.reprompt(prompt)
.shouldEndSession(false);
}
}