MetaView
Published © CC0

Kalender Ereignisse

Alexa skill which connects to (German) Wikipedia and returns an event which happend on the given day in year.

IntermediateShowcase (no instructions)2 hours159
Kalender Ereignisse

Things used in this project

Hardware components

Amazon Echo
Amazon Alexa Amazon Echo
×1

Story

Read more

Code

Ask German Wikipedia for events on a given date

JavaScript
AWS lambda function to provide a random event for a given day in year for an Alexa skill.
/* 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();
    */
};

Intent Schema

JSON
{
  "intents": [
    {
      "intent": "GetFirstEventIntent",
      "slots": [
        {
          "name": "day",
          "type": "AMAZON.DATE"
        }
      ]
    },
    {
      "intent": "AMAZON.HelpIntent"
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "intent": "AMAZON.CancelIntent"
    }
  ]
}

Sample Utterances

JSON
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}

Credits

MetaView

MetaView

7 projects • 10 followers

Comments