Brad McAllister
Created November 18, 2016

Voice enable Wunderlist with Task Master!

If you love tracking your to do lists with Wunderlist, you are going to love this skill! Interact with your lists via Alexa!

154

Things used in this project

Hardware components

Amazon Echo
Amazon Alexa Amazon Echo
×1

Software apps and online services

Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
AWS Lambda
Amazon Web Services AWS Lambda
Wunderlist API

Story

Read more

Schematics

Task Master VUI

This illustrates the different voice interactions available.

Code

index.js

JavaScript
This contains all of the TaskMaster Intent methods
/**
 * App ID for the skill
 */
var APP_ID = undefined; //replace with "amzn1.echo-sdk-ams.app.[your-unique-value-here]";

/**
 * The AlexaSkill prototype and helper functions
 */
var AlexaSkill = require('./AlexaSkill');
var request = require('request');
var moment = require('moment');
var Promise = require('bluebird');
var WunderlistSDK = require('wunderlist');
var linkingCard = "You must link Task Master to your Wunderlist account. From the Alexa app, go to Skills -> Your Skills -> Task Master -> Link Account";
var linkingSpeech = "You must use the Alexa app to link task master to your Wunderlist account before this skill will work";
var linkingTitle = "Account Linking Required";

/**
 * TaskMaster.is a child of AlexaSkill.
 * To read more about inheritance in JavaScript, see the link below.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript#Inheritance
 */
var TaskMaster = function () {
    AlexaSkill.call(this, APP_ID);
};

// Extend AlexaSkill
TaskMaster.prototype = Object.create(AlexaSkill.prototype);
TaskMaster.prototype.constructor = TaskMaster;

TaskMaster.prototype.eventHandlers.onSessionStarted = function (sessionStartedRequest, session) {
    console.log("TaskMaster.onSessionStarted requestId: " + sessionStartedRequest.requestId + ", sessionId: " + session.sessionId);
    // any initialization logic goes here
};

TaskMaster.prototype.eventHandlers.onLaunch = function (launchRequest, session, response) {
    console.log("TaskMaster.onLaunch requestId: " + launchRequest.requestId + ", sessionId: " + session.sessionId);
    if(typeof session.user.accessToken !== 'undefined'){
      var speechOutput = "Welcome to Task Master. You can say, Whats due today?, Review my inbox, add pickup dry cleaning to inbox.";
      var repromptText = "You can say, Whats due today?, Review my inbox, add pickup dry cleaning to inbox.";
      response.ask(speechOutput, repromptText);
    }else{
      response.linkAccountCard(linkingSpeech, linkingTitle, linkingCard);
    }
};

TaskMaster.prototype.eventHandlers.onSessionEnded = function (sessionEndedRequest, session) {
    console.log("TaskMaster.onSessionEnded requestId: " + sessionEndedRequest.requestId+ ", sessionId: " + session.sessionId);
    // any cleanup logic goes here
};

TaskMaster.prototype.intentHandlers = {
    // register custom intent handlers
    "AddTaskIntent": function (intent, session, response) {
      if(typeof session.user.accessToken !== 'undefined'){
        // If list name is not provided, task is added to the inbox
        wunder.getListId(session.user.accessToken, intent.slots.ListName.value || null)
        .then(function(listId){
          return wunder.addTask(session.user.accessToken, intent.slots.TaskName.value, listId);
        })
        .catch(function(errorResponse){
          response.tell(errorResponse);
        })   
        .then(function(){
          response.tellWithCard("I've added "+ intent.slots.TaskName.value +" to your Wunderlist!", "Item added to Wunderlist", "I've added "+ intent.slots.TaskName.value +" to your Wunderlist!");
        })
        .catch(function(){
          response.tell("I had a problem saving your task");
        });
      }else{
        response.linkAccountCard(linkingSpeech, linkingTitle, linkingCard);
      }
    },
    "CheckListIntent": function (intent, session, response) {
      if(typeof session.user.accessToken !== 'undefined'){
        // Capture listName 
        var sessionAttributes = {};
        var listName = intent.slots.ListName.value;
        // determine what the listId is
        wunder.getListId(session.user.accessToken, intent.slots.ListName.value || null)
        .then(function(listId){
          console.log("listId: ", listId);
          // return all open tasks for list
          return wunder.inbox(session.user.accessToken, listId);
        })  
        .then(function(result){
            console.log("Wunderlist Response: ", result);
            var tasks = result.length;
            console.log("Length of response: ", tasks);
            sessionAttributes.inbox = result;
            session.attributes = sessionAttributes;
            switch(tasks){
              case 0:
                response.tell("The "+listName+" list is empty.");
              break;
              case 1:
                response.ask("You have "+tasks+" item in your "+listName+" list, Would you like to hear the details?");
              break;
              default:
                response.ask("You have "+tasks+" items in your "+listName+" list, Would you like to hear the details?");
            }
            
        })
        .catch(function(failResponse){
          response.tell(failResponse);
        });
      }else{
        response.linkAccountCard(linkingSpeech, linkingTitle, linkingCard);
      }
    },
    "DueIntent": function(intent, session, response){
      if(typeof session.user.accessToken !== 'undefined'){
        console.log(intent.slots.date);
        var sessionAttributes = {};
        var listName;
        console.log(typeof intent.slots.ListName.value);
        if(typeof intent.slots.ListName.value !== 'undefined'){
          console.log('defined!');
          listName = intent.slots.ListName.value;
        }else{
          console.log(intent.slots);
          listName = 'inbox';
        }
        
        wunder.getListId(session.user.accessToken, listName)
        .then(function(listId){
          return  wunder.due(session.user.accessToken, intent.slots.date.value, listId);
        })
        .then(function(due){
            var taskCount = due.length;
            sessionAttributes.due = due;
            session.attributes = sessionAttributes;
            switch(taskCount){
              case 0:
                response.tell("none of your tasks are due for "+ listName);
              break;
              case 1:
                response.ask("You have "+taskCount+" task in your "+listName+" list that is due, Would you like to hear the details?");
              break;
              default:
                response.ask("You have "+taskCount+" tasks in your "+listName+" list that are due, Would you like to hear the details?");
            }
        })
        .catch(function(failResponse){
          response.tell(failResponse);
        });
      }else{
        response.linkAccountCard(linkingSpeech, linkingTitle, linkingCard);
      }
    },
    "AMAZON.YesIntent": function(intent, session, response){
      if(typeof session.user.accessToken !== 'undefined'){
        console.log("session: ", session.attributes);
        var sessionAttributes = session.attributes;
        var i;
        var speechOutput = "";
        var repromptText = "";
        if(typeof sessionAttributes.due !== "undefined"){
            var due = sessionAttributes.due;
            for(i = 0; i < due.length; i++){
                speechOutput += "Task "+(i+1)+", "+due[i].title+", ";
            }
            response.tell(speechOutput);
        }else if(typeof sessionAttributes.inbox !== "undefined"){
            var inbox = sessionAttributes.inbox;
            for(i = 0; i < inbox.length; i++){
                speechOutput += "Task "+(i+1)+", "+inbox[i].title+", ";
            }
            response.tell(speechOutput);
        }else{
            response.tell("Sorry something went wrong");
        }
      }else{
        response.linkAccountCard(linkingSpeech, linkingTitle, linkingCard);
      }
    },
    "AMAZON.HelpIntent": function (intent, session, response) {
      response.ask("You can say review my inbox. You can say what is due in my inbox today, this week, or this month. You can say add task pickup dry cleaning.");
    },
    "AMAZON.StopIntent": function (intent, session, response) {
      response.tell("Goodbye");
    }
};

// Create the handler that responds to the Alexa Request.
exports.handler = function (event, context) {
    // Create an instance of the TaskMaster.skill.
    var taskMaster = new TaskMaster();
    taskMaster.execute(event, context);
};

// Object literral containing Wunderlist API calls
var wunder = {
    inbox: function(accessToken, listId){
      var wunderlistAPI = new WunderlistSDK({
        'accessToken': accessToken,
        'clientID': 'ee32a7e1b5c063b28e8f'
      });
      console.log("listID received: ", listId);

      return new Promise(function(resolve, reject){
        wunderlistAPI.http.tasks.forList(listId)
        .done(function (taskData, statusCode) {
          console.log("listData: ", taskData);
          resolve(taskData);
        })
        .fail(function(){
          reject();
        });
      });
    },
    due: function(accessToken, date, listId){
      var wunderlistAPI = new WunderlistSDK({
        'accessToken': accessToken,
        'clientID': 'ee32a7e1b5c063b28e8f'
      });

      // determine date format
      var reDay = /^(\d+-\d+-\d+)$/;
      var reWeek = /^\d+-([W]\d+)$/;
      var reMonth = /^(\d+-\d+)$/;
      var startDate, endDate;
    
      if(reDay.test(date)){
        startDate = moment(date);
        endDate = moment(date);
      }else if(reWeek.test(date)){
        var weekNum = reWeek.exec(date);
        startDate = moment().week(weekNum[1]).startOf('week');
        endDate = moment().week(weekNum[1]).endOf('week');
      }else if(reMonth.test(date)){
        startDate = moment().month(date).startOf('month');
        endDate = moment().month(date).endOf('month');
      }
      
      var dueToday = [];
      var i;

      return new Promise(function(resolve, reject){
        wunderlistAPI.http.tasks.forList(listId)
        .done(function(tasksData, statusCode){
          var tasks = tasksData;
          for(var i = 0; i < tasks.length; i++){
            if(typeof tasks[i].due_date !== 'undefined' && moment(tasks[i].due_date).isBetween(startDate, endDate, 'day')){
              dueToday.push(tasks[i]);
              console.log("matched task");
            }else if(typeof tasks[i].due_date !== 'undefined' && moment(tasks[i].due_date).isSame(startDate)){
              dueToday.push(tasks[i]);
              console.log("matched task");
            }else{
              console.log("no matches", tasks[i].due_date);
            }
          }
          resolve(dueToday);
        });
      });
    },
    getListId: function(accessToken, listName){
      console.log("listName:", listName);
      if(listName === null){
        listName = "inbox";
      }

      var wunderlistAPI = new WunderlistSDK({
        'accessToken': accessToken,
        'clientID': 'ee32a7e1b5c063b28e8f'
      });

      return new Promise(function(resolve, reject){
        wunderlistAPI.http.lists.all()
        .done(function (lists) {
          console.log("returned lists: ", lists);
          for(i = 0; i < lists.length; i++){
            var title = lists[i].title;
            if(title.toLowerCase() === listName.toLowerCase()){
                resolve(lists[i].id);
            }
          }
          reject("I can't find a list named "+ listName +", to add your task to. Please try again.");
        })
        .fail(function(){
          reject("I ran into a problem looking up your to do list");
        });
      });
    },
    addTask: function(accessToken, taskName, listId){
      var wunderlistAPI = new WunderlistSDK({
        'accessToken': accessToken,
        'clientID': 'ee32a7e1b5c063b28e8f'
      });

      return new Promise(function(resolve, reject){
        wunderlistAPI.http.tasks.create({'list_id': listId, 'title': taskName})
        .done(function(taskData, statusCode){
            resolve();
        })
        .fail(function(){
            reject();
        });
      });
    }
};

Credits

Brad McAllister

Brad McAllister

1 project • 0 followers

Comments