Hardware components | ||||||
| × | 1 |
Alexa Skill ID: amzn1.ask.skill.9ab99233-ce51-42c7-9966-e32417d2fb30
Here comes another Alexa skill from the German Alexa skill store. The skill is already available in the German skill store. The skill connects the Wikipedia API with Alexa to present the user with a random event for a given day in year. As a template for this skill, I used an example which already connected to Wikipedia but was targeting the US Wikipedia.
Some changes I made were setting the correct endpoint, altering the json
parser to trigger on the correct German words, filtering senseless lines with captions and stuff and finally dealing with unicode
encoded umlauts
.
The original example was also presenting more than one event on a time. I changed this, because I rather expect the user to like to have short answers. If the user wants to have another event, he can just repeat its question. The skill was made in the usual way: on the Amazon developer account, you click on Alexa -> Alexa Skills Kit. Then you add a new skill and define Skill Information, Interaction Model (which you usually copy from your example and then adjust to your own like). On Configuration, you enter the ID of your AWS lambda function which you need to create before.
Adding the Lambda functionThere we go to the AWS dashboard and add a new lambda function. The example will give us an idea what it should look like. In this case, we had something more complicated then the simple skill templates. For easier editing (I really like to edit the code in the online editor), I copy all necessary code files into one.
The Amazon developer page will have a Test menu item, which we then use for testing. Console.log does work also in our AWS lambda function; to access the logs you need to go to Cloudwatch in your AWS account.
The development cycle then looks like: alter the code in AWS online editor, save, go to Amazon developer account's test page, try it out, check the logs in AWS Cloudwatch and return to the online editor for further development.
At the end, fill in the missing publishing information and submit for certification.
About the code flow:- User triggers your skill either with or without a date.
- If there is no date given, skill asks for the date and waits for answer.
- If the date is given, Wikipedia API is called and the result is then parsed and each event is added to an array.
- A random index between 0 and the size of the array is created and used to extract one of the events.
- The event is read out loud to the user and the session ends.
Ask German Wikipedia for events on a given date
JavaScript/* eslint-disable func-names */
/* eslint quote-props: ["error", "consistent"]*/
/**
* This sample demonstrates a simple skill built with the Amazon Alexa Skills
* nodejs skill development kit.
* This sample supports multiple lauguages. (en-US, en-GB, de-DE).
* The Intent Schema, Custom Slots and Sample Utterances for this skill, as well
* as testing instructions are located at https://github.com/alexa/skill-sample-nodejs-fact
**/
'use strict';
const https = require('https');
const Alexa = require('alexa-sdk');
const APP_ID = undefined; // TODO replace with your app ID (OPTIONAL).
/**
* URL prefix to download history content from Wikipedia
*/
const urlPrefix = 'https://de.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&explaintext=&exsectionformat=plain&redirects=&titles=';
/**
* Variable defining number of events to be read at one time
*/
var paginationSize = 1;
/**
* Variable defining the length of the delimiter between events
*/
var delimiterSize = 2;
const languageStrings = {
HELP_MESSAGE: 'Du kannst zum Beispiel fragen, „Was geschah heute“, oder du kannst „Beenden“ sagen... Wie kann ich dir helfen?',
HELP_REPROMPT: 'Wie kann ich dir helfen?',
STOP_MESSAGE: 'Auf Wiedersehen!',
};
function AlexaSkill(appId) {
this._appId = appId;
}
AlexaSkill.speechOutputType = {
PLAIN_TEXT: 'PlainText',
SSML: 'SSML'
}
AlexaSkill.prototype.requestHandlers = {
LaunchRequest: function (event, context, response) {
this.eventHandlers.onLaunch.call(this, event.request, event.session, response);
},
IntentRequest: function (event, context, response) {
this.eventHandlers.onIntent.call(this, event.request, event.session, response);
},
SessionEndedRequest: function (event, context) {
this.eventHandlers.onSessionEnded(event.request, event.session);
context.succeed();
}
};
/**
* Override any of the eventHandlers as needed
*/
AlexaSkill.prototype.eventHandlers = {
/**
* Called when the session starts.
* Subclasses could have overriden this function to open any necessary resources.
*/
onSessionStarted: function (sessionStartedRequest, session) {
},
/**
* Called when the user invokes the skill without specifying what they want.
* The subclass must override this function and provide feedback to the user.
*/
onLaunch: function (launchRequest, session, response) {
throw "onLaunch should be overriden by subclass";
},
/**
* Called when the user specifies an intent.
*/
onIntent: function (intentRequest, session, response) {
var intent = intentRequest.intent,
intentName = intentRequest.intent.name,
intentHandler = this.intentHandlers[intentName];
if (intentHandler) {
console.log('dispatch intent = ' + intentName);
intentHandler.call(this, intent, session, response);
} else {
throw 'Unsupported intent = ' + intentName;
}
},
/**
* Called when the user ends the session.
* Subclasses could have overriden this function to close any open resources.
*/
onSessionEnded: function (sessionEndedRequest, session) {
}
};
/**
* Subclasses should override the intentHandlers with the functions to handle specific intents.
*/
AlexaSkill.prototype.intentHandlers = {};
AlexaSkill.prototype.execute = function (event, context) {
try {
console.log("session applicationId: " + event.session.application.applicationId);
// Validate that this request originated from authorized source.
if (this._appId && event.session.application.applicationId !== this._appId) {
console.log("The applicationIds don't match : " + event.session.application.applicationId + " and "
+ this._appId);
throw "Invalid applicationId";
}
if (!event.session.attributes) {
event.session.attributes = {};
}
if (event.session.new) {
this.eventHandlers.onSessionStarted(event.request, event.session);
}
// Route the request to the proper handler which may have been overriden.
var requestHandler = this.requestHandlers[event.request.type];
requestHandler.call(this, event, context, new Response(context, event.session));
} catch (e) {
console.log("Unexpected exception " + e);
context.fail(e);
}
};
var Response = function (context, session) {
this._context = context;
this._session = session;
};
function createSpeechObject(optionsParam) {
if (optionsParam && optionsParam.type === 'SSML') {
return {
type: optionsParam.type,
ssml: optionsParam.speech
};
} else {
return {
type: optionsParam.type || 'PlainText',
text: optionsParam.speech || optionsParam
}
}
}
Response.prototype = (function () {
var buildSpeechletResponse = function (options) {
var alexaResponse = {
outputSpeech: createSpeechObject(options.output),
shouldEndSession: options.shouldEndSession
};
if (options.reprompt) {
alexaResponse.reprompt = {
outputSpeech: createSpeechObject(options.reprompt)
};
}
if (options.cardTitle && options.cardContent) {
alexaResponse.card = {
type: "Simple",
title: options.cardTitle,
content: options.cardContent
};
}
var returnResult = {
version: '1.0',
response: alexaResponse
};
if (options.session && options.session.attributes) {
returnResult.sessionAttributes = options.session.attributes;
}
return returnResult;
};
return {
tell: function (speechOutput) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
shouldEndSession: true
}));
},
tellWithCard: function (speechOutput, cardTitle, cardContent) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
cardTitle: cardTitle,
cardContent: cardContent,
shouldEndSession: true
}));
},
ask: function (speechOutput, repromptSpeech) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
reprompt: repromptSpeech,
shouldEndSession: false
}));
},
askWithCard: function (speechOutput, repromptSpeech, cardTitle, cardContent) {
this._context.succeed(buildSpeechletResponse({
session: this._session,
output: speechOutput,
reprompt: repromptSpeech,
cardTitle: cardTitle,
cardContent: cardContent,
shouldEndSession: false
}));
}
};
})();
var HistoryBuffSkill = function() {
AlexaSkill.call(this, APP_ID);
};
// Extend AlexaSkill
HistoryBuffSkill.prototype = Object.create(AlexaSkill.prototype);
HistoryBuffSkill.prototype.constructor = HistoryBuffSkill;
HistoryBuffSkill.prototype.eventHandlers.onSessionStarted = function (sessionStartedRequest, session) {
console.log("HistoryBuffSkill onSessionStarted requestId: " + sessionStartedRequest.requestId
+ ", sessionId: " + session.sessionId);
// any session init logic would go here
};
HistoryBuffSkill.prototype.eventHandlers.onLaunch = function (launchRequest, session, response) {
console.log("HistoryBuffSkill onLaunch requestId: " + launchRequest.requestId + ", sessionId: " + session.sessionId);
getWelcomeResponse(response);
};
HistoryBuffSkill.prototype.eventHandlers.onSessionEnded = function (sessionEndedRequest, session) {
console.log("onSessionEnded requestId: " + sessionEndedRequest.requestId
+ ", sessionId: " + session.sessionId);
// any session cleanup logic would go here
};
HistoryBuffSkill.prototype.intentHandlers = {
"GetFirstEventIntent": function (intent, session, response) {
handleFirstEventRequest(intent, session, response);
},
"AMAZON.HelpIntent": function (intent, session, response) {
const speechOutput = languageStrings['HELP_MESSAGE'];
const repromptOutput = languageStrings['HELP_MESSAGE'];
response.ask(speechOutput, repromptOutput);
},
'AMAZON.CancelIntent': function (intent, session, response) {
response.tell(languageStrings['STOP_MESSAGE']);
},
'AMAZON.StopIntent': function (intent, session, response) {
response.tell(languageStrings['STOP_MESSAGE']);
},
'SessionEndedRequest': function (intent, session, response) {
response.tell(languageStrings['STOP_MESSAGE']);
},
};
/**
* Function to handle the onLaunch skill behavior
*/
function getWelcomeResponse(response) {
// If we wanted to initialize the session to have some attributes we could add those here.
var cardTitle = "Kalender Ereignisse";
var repromptText = "Mit Kalender Ereignisse kannst Du geschichtliche Ereignisse für jeden Tag des Jahres abfragen. Zum Beispiel kannst Du sagen, was geschah heute oder was geschah am dritten Mai. Welcher Tag soll es sein?";
var speechText = "<p>Kalender Ereignisse.</p> <p>Für welchen Tag möchtest Du ein Ereignis hören?</p>";
var cardOutput = "Kalender Ereignisse. Für welchen Tag möchtest Du ein Ereignis hören?";
// If the user either does not reply to the welcome message or says something that is not
// understood, they will be prompted again with this text.
var speechOutput = {
speech: "<speak>" + speechText + "</speak>",
type: AlexaSkill.speechOutputType.SSML
};
var repromptOutput = {
speech: repromptText,
type: AlexaSkill.speechOutputType.PLAIN_TEXT
};
response.askWithCard(speechOutput, repromptOutput, cardTitle, cardOutput);
}
/**
* Gets a poster prepares the speech to reply to the user.
*/
function handleFirstEventRequest(intent, session, response) {
var daySlot = intent.slots.day;
var repromptText = "Mit Kalender Ereignisse kannst Du geschichtliche Ereignisse für jeden Tag des Jahres abfragen. Zum Beispiel kannst Du sagen, was geschah heute oder was geschah am dritten Mai. Also, welcher Tag soll es sein?";
var monthNames = ["Januar", "Februar", "März", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember"
];
var sessionAttributes = {};
// Read the first 3 events, then set the count to 3
sessionAttributes.index = paginationSize;
var date = "";
// If the user provides a date, then use that, otherwise use today
// The date is in server time, not in the user's time zone. So "today" for the user may actually be tomorrow
if (daySlot && daySlot.value) {
date = new Date(daySlot.value);
} else {
//date = new Date();
response.ask("Für welchen Tag möchtest Du ein Ereignis hören? Sage zum Beispiel heute oder dritter April.");
return;
}
var prefixContent = "<p>Ereignis für den " + date.getDate() + ". " + monthNames[date.getMonth()] + ", </p>";
var cardContent = "Ereignis für den " + date.getDate() + ". " + monthNames[date.getMonth()] + ", ";
var cardTitle = "Ereignisse am " + date.getDate() + ". " + monthNames[date.getMonth()];
getJsonEventsFromWikipedia(monthNames[date.getMonth()], date.getDate(), function (events) {
try {
var speechText = "";
sessionAttributes.text = events;
session.attributes = sessionAttributes;
console.log("events.length ", events.length);
if (events.length == 0) {
speechText = "Ich habe gerade ein Problem, mich mit der Wikipedia zu verbinden. Probiere es bitte später noch einmal.";
cardContent = speechText;
var speechOutput = {
speech: "<speak>" + prefixContent + speechText + "</speak>",
type: AlexaSkill.speechOutputType.SSML
};
response.tellWithCard(speechOutput, cardTitle, cardContent);
} else {
var i = Math.floor(Math.random() * events.length);
cardContent = cardContent + events[i] + " ";
speechText = "<p>" + speechText + events[i] + "</p> ";
var speechOutput = {
speech: "<speak>" + prefixContent + speechText + "</speak>",
type: AlexaSkill.speechOutputType.SSML
};
var repromptOutput = {
speech: repromptText,
type: AlexaSkill.speechOutputType.PLAIN_TEXT
};
console.log(speechOutput);
console.log(cardTitle);
console.log(cardContent);
response.tellWithCard(speechOutput, cardTitle, cardContent);
}
} catch (e) {
console.log("Unexpected exception " + e);
}
});
}
function getJsonEventsFromWikipedia(month, day, eventCallback) {
var url = urlPrefix + day + '._' + month;
console.log("url: ", url);
https.get(url, function(res) {
var body = '';
res.on('data', function (chunk) {
body += chunk;
});
res.on('end', function () {
console.log("Antwort erhalten ", body.length);
var stringResult = parseJson(body);
eventCallback(stringResult);
});
}).on('error', function (e) {
console.log("Got error: ", e);
});
}
function unencodeUnicode(inputText) {
var x = inputText;
var r = /\\u([\d\w]{4})/gi;
x = x.replace(r, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
} );
x = unescape(x);
return x;
}
function parseJson(inputText) {
// sizeOf (/nEvents/n) is 10
var text = inputText.substring(inputText.indexOf("\\nEreignisse\\n\\n\\n")+14, inputText.indexOf("\\n\\n\\nGeboren")),
retArr = [],
retString = "",
endIndex,
startIndex = 0;
console.log("text.length ", text.length);
if (text.length == 0) {
return retArr;
}
while(true) {
endIndex = text.indexOf("\\n", startIndex);
var eventText = (endIndex == -1 ? text.substring(startIndex) : text.substring(startIndex, endIndex));
//console.log("eventText: ", eventText);
// has to start with a number...
if ((eventText.charAt(0) == "0") || (eventText.charAt(0) == "1") || (eventText.charAt(0) == "2")) {
eventText = unencodeUnicode(eventText);
// replace dashes returned in text from Wikipedia's API
eventText = eventText.replace(/\\u2013\s*/g, '');
// add comma after year so Alexa pauses before continuing with the sentence
eventText = eventText.replace(/(^\d+)/,'$1,');
eventText = 'Im Jahr ' + eventText;
retArr.push(eventText);
}
if (endIndex == -1) {
break;
}
startIndex = endIndex + delimiterSize;
}
if (retString != "") {
retArr.push(retString);
}
retArr.reverse();
return retArr;
}
exports.handler = function (event, context) {
// Create an instance of the HistoryBuff Skill.
var skill = new HistoryBuffSkill();
skill.execute(event, context);
/*
const alexa = Alexa.handler(event, context);
alexa.APP_ID = APP_ID;
// To enable string internationalization (i18n) features, set a resources object.
alexa.resources = languageStrings;
alexa.registerHandlers(handlers);
alexa.execute();
*/
};
{
"intents": [
{
"intent": "GetFirstEventIntent",
"slots": [
{
"name": "day",
"type": "AMAZON.DATE"
}
]
},
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
}
]
}
GetFirstEventIntent was geschah am {day}
GetFirstEventIntent was passierte am {day}
GetFirstEventIntent was war am {day}
GetFirstEventIntent was geschah {day}
GetFirstEventIntent was passierte {day}
GetFirstEventIntent was war {day}
GetFirstEventIntent am {day}
GetFirstEventIntent {day}
GetFirstEventIntent was am {day} geschah
GetFirstEventIntent was am {day} passierte
GetFirstEventIntent was am {day} war
GetFirstEventIntent was ist am {day} geschehen
GetFirstEventIntent was am {day} geschehen ist
GetFirstEventIntent was ist am {day} passiert
GetFirstEventIntent was am {day} passiert ist
GetFirstEventIntent was ist am {day} gewesen
GetFirstEventIntent was am {day} gewesen ist
GetFirstEventIntent was {day} geschah
GetFirstEventIntent was {day} passierte
GetFirstEventIntent was {day} war
GetFirstEventIntent Ereignis am {day}
GetFirstEventIntent Ereignisse am {day}
GetFirstEventIntent Ereignis für {day}
GetFirstEventIntent Ereignisse für {day}
GetFirstEventIntent Ereignis für den {day}
GetFirstEventIntent Ereignisse für den {day}
GetFirstEventIntent Ereignis {day}
GetFirstEventIntent Ereignisse {day}
Comments