Things used in this project

Hardware components

Software apps and online services

Story

Description:

The Task Master skill allows you to access your Wunderlist via voice! Task Master is capable of reading the items in a particular list, telling you what items are due, and adding tasks. You can access the skill here!

Purpose:

I am a big fan of Wunderlist. I use it to keep track of tasks that I need to get done for work, around the house, and more. Alexa has the ability to add items to a list in the Alexa app, but I didn't want to have a separate list. This skill allows me to add items to my Wunderlist even when I don't have my phone handy.

Schematics

Task Master VUI

Code

index.js

JavaScript

This contains all of the TaskMaster Intent methods

/** * App ID for the skill */varAPP_ID=undefined;//replace with "amzn1.echo-sdk-ams.app.[your-unique-value-here]";/** * The AlexaSkill prototype and helper functions */varAlexaSkill=require('./AlexaSkill');varrequest=require('request');varmoment=require('moment');varPromise=require('bluebird');varWunderlistSDK=require('wunderlist');varlinkingCard="You must link Task Master to your Wunderlist account. From the Alexa app, go to Skills -> Your Skills -> Task Master -> Link Account";varlinkingSpeech="You must use the Alexa app to link task master to your Wunderlist account before this skill will work";varlinkingTitle="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 */varTaskMaster=function(){AlexaSkill.call(this,APP_ID);};// Extend AlexaSkillTaskMaster.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(typeofsession.user.accessToken!=='undefined'){varspeechOutput="Welcome to Task Master. You can say, Whats due today?, Review my inbox, add pickup dry cleaning to inbox.";varrepromptText="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(typeofsession.user.accessToken!=='undefined'){// If list name is not provided, task is added to the inboxwunder.getListId(session.user.accessToken,intent.slots.ListName.value||null).then(function(listId){returnwunder.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(typeofsession.user.accessToken!=='undefined'){// Capture listName varsessionAttributes={};varlistName=intent.slots.ListName.value;// determine what the listId iswunder.getListId(session.user.accessToken,intent.slots.ListName.value||null).then(function(listId){console.log("listId: ",listId);// return all open tasks for listreturnwunder.inbox(session.user.accessToken,listId);}).then(function(result){console.log("Wunderlist Response: ",result);vartasks=result.length;console.log("Length of response: ",tasks);sessionAttributes.inbox=result;session.attributes=sessionAttributes;switch(tasks){case0:response.tell("The "+listName+" list is empty.");break;case1: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(typeofsession.user.accessToken!=='undefined'){console.log(intent.slots.date);varsessionAttributes={};varlistName;console.log(typeofintent.slots.ListName.value);if(typeofintent.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){returnwunder.due(session.user.accessToken,intent.slots.date.value,listId);}).then(function(due){vartaskCount=due.length;sessionAttributes.due=due;session.attributes=sessionAttributes;switch(taskCount){case0:response.tell("none of your tasks are due for "+listName);break;case1: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(typeofsession.user.accessToken!=='undefined'){console.log("session: ",session.attributes);varsessionAttributes=session.attributes;vari;varspeechOutput="";varrepromptText="";if(typeofsessionAttributes.due!=="undefined"){vardue=sessionAttributes.due;for(i=0;i<due.length;i++){speechOutput+="Task "+(i+1)+", "+due[i].title+", ";}response.tell(speechOutput);}elseif(typeofsessionAttributes.inbox!=="undefined"){varinbox=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.vartaskMaster=newTaskMaster();taskMaster.execute(event,context);};// Object literral containing Wunderlist API callsvarwunder={inbox:function(accessToken,listId){varwunderlistAPI=newWunderlistSDK({'accessToken':accessToken,'clientID':'ee32a7e1b5c063b28e8f'});console.log("listID received: ",listId);returnnewPromise(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){varwunderlistAPI=newWunderlistSDK({'accessToken':accessToken,'clientID':'ee32a7e1b5c063b28e8f'});// determine date formatvarreDay=/^(\d+-\d+-\d+)$/;varreWeek=/^\d+-([W]\d+)$/;varreMonth=/^(\d+-\d+)$/;varstartDate,endDate;if(reDay.test(date)){startDate=moment(date);endDate=moment(date);}elseif(reWeek.test(date)){varweekNum=reWeek.exec(date);startDate=moment().week(weekNum[1]).startOf('week');endDate=moment().week(weekNum[1]).endOf('week');}elseif(reMonth.test(date)){startDate=moment().month(date).startOf('month');endDate=moment().month(date).endOf('month');}vardueToday=[];vari;returnnewPromise(function(resolve,reject){wunderlistAPI.http.tasks.forList(listId).done(function(tasksData,statusCode){vartasks=tasksData;for(vari=0;i<tasks.length;i++){if(typeoftasks[i].due_date!=='undefined'&&moment(tasks[i].due_date).isBetween(startDate,endDate,'day')){dueToday.push(tasks[i]);console.log("matched task");}elseif(typeoftasks[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";}varwunderlistAPI=newWunderlistSDK({'accessToken':accessToken,'clientID':'ee32a7e1b5c063b28e8f'});returnnewPromise(function(resolve,reject){wunderlistAPI.http.lists.all().done(function(lists){console.log("returned lists: ",lists);for(i=0;i<lists.length;i++){vartitle=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){varwunderlistAPI=newWunderlistSDK({'accessToken':accessToken,'clientID':'ee32a7e1b5c063b28e8f'});returnnewPromise(function(resolve,reject){wunderlistAPI.http.tasks.create({'list_id':listId,'title':taskName}).done(function(taskData,statusCode){resolve();}).fail(function(){reject();});});}};