How to set output context for list options in dialogflow? - node.js

How do I give an output context to each item of a list ?
I am having trouble with handling the list options because the output context is not there.
For example in this code :
const meditatetrackList = () => {
const list = new List({
title: 'Choose a track.',
items: {
'healing water': {
title: 'Healing Water',
synonyms: ['healing water'],
image: new Image({
url: 'http://www.vstplanet.com/News/2016/Nature-sounds/Relaxing-nature-sounds.jpg',
alt: 'healing water',
}),
},
'elven forest': {
title: 'Elven Forest',
synonyms: ['elven' , 'forest' , 'elven forest'],
image: new Image({
url: 'https://scx2.b-cdn.net/gfx/news/2018/europeslostf.jpg',
alt: 'elven forest',
}),
},
'warm light' : {
title : 'Warm Light',
synonyms: ['warm','light','warm light'],
image: new Image({
url: 'https://www.socwall.com/images/wallpapers/37753-2000x1300.jpg',
alt: 'warm light',
}),
}
}
});
return list;
};

Okay, I am late here but I recently did this.
In this example code, I am returning a dropoff list of items
Util.js
getDropOffListCard: function(nearestDropOffResponse, agent) {
let objItem = {};
for (let index = 0; index < nearestDropOffResponse.length; index++) {
const element = nearestDropOffResponse[index];
let key = element.name.toUpperCase().replace(/ /g, "_");
let each = {
synonyms: [
'synonym 1',
'synonym 2',
'synonym 3',
],
title: element.name,
description: element.address + '\n' + element.distance + ' ' + element.unit,
image: new Image({
url: url + 'info-icon.png',
alt: 'Image alternate text',
})
};
objItem[key] = each;
}
agent.context.set({ // here set the context
name: 'global_context',
lifespan: 2,
parameters: {
dropLocationList: objItem
}
});
return new List({
title: 'Nearest drop off location',
items: objItem
});
}
app.js
intentMap.set("Nearest Drop Off Intent - yes", function(agent){
const conv = agent.conv();
conv.ask('Here are nearest drop off location. Where you can drop or pickup your parcel.');
conv.ask(trackingUtil.getDropOffListCard(nearestDropOffResponse, agent));
agent.add(conv);
});
Create another intent to handle this and add the event Google Assistant Option this event do the trick, it will send the selected option to your intent handler, where you can access the selected option as well as a list that we set in context.
intentMap.set("Nearest Drop Off Intent - yes - selected", function(agent){
const option_intent = agent.context.get('actions_intent_option');
const option_key = option_intent.parameters.OPTION;
const global_context = agent.context.get('global_context');
const selection = global_context.parameters.dropLocationList[option_key]
agent.add(selection.title);
agent.add(selection.description);
agent.add(trackingUtil.dropOffDetailCard(selection))
});

Related

how to extract parameters from the context for list response using dialogflow fulfillment

Is it possible to pass the value of selected list item to any other intent. in my case I am passing a list and user select an item from the list, now I want to show this selected item name in another intent but I can't do that.
is anything wrong with my code
code runs properly only problem is instead of "selected item name" it returns "undefined".
this is my code
'use strict';
const functions = require('firebase-functions');
const {dialogflow, SimpleResponse} = require ('actions-on-google');
const {Suggestions, List, Image, BasicCard} = require ('actions-on-google');
const SHOW_PHONE_INTENT = 'Default Welcome Intent';
const FALLBACK_INTENT = 'Default Fallback Intent';
const SELECTED_PHONE_INTENT = 'SelectedPhoneIntent';
const ADD_TO_CART_INTENT = 'AddToCartIntent';
const AppContexts = {AWAITING_PHONE: 'awaiting-phone'};
const AppContexts1 = {AWAITING_REPLY: 'awaiting-reply'};
const app = dialogflow();
const PhoneDetail = {
'Phone1': {
text: `screen size = 5 inches \n
price = $100`,
subtitle: 'This is phone1',
title: 'Phone1 Details',
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'pic1',
}),
display: 'WHITE',
};
'Phone2': {
text: `screen size = 5.5 inches \n
price = $150`,
subtitle: 'This is phone2',
title: 'Phone2 Details',
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'pic2',
}),
display: 'WHITE',
};
'Phone3': {
text: `screen size = 6 inches \n
price = $200`,
subtitle: 'This is phone3',
title: 'Phone3 Details',
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'pic3',
}),
display: 'WHITE',
};
};
app.intent(FALLBACK_INTENT, (conv) => {
conv.ask("Sorry! Could you please repeat that?");
});
app.intent(SHOW_PHONE_INTENT, (conv) => {
conv.contexts.set(AppContexts.AWAITING_PHONE, 1);
conv.ask("Here's the list of phone's.");
conv.ask(new List({
title: "Select a phone to see details.",
items: {
"Phone1": {
title: "phone1",
description: "Click here to check phone1 details.",
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'p1',
}),
},
"Phone2": {
title: "phone2",
description: "Click here to check phone2 details.",
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'plc',
}),
},
"Phone3": {
title: "phone3",
description: "Click here to check phone3 details.",
image: new Image({
url: 'https://img.icons8.com/plasticine/2x/name.png',
alt: 'obj',
}),
},
},
}));
});
app.intent(SELECTED_PHONE_INTENT, (conv, input, option) => {
const context = conv.contexts.get(AppContexts.AWAITING_PHONE);
if (option) {
conv.ask(`${option} Details`);
conv.ask(new BasicCard(PhoneDetail[option]));
conv.ask(new Suggestions(['Show List', 'Add to Cart']));
} else {
conv.close('Sorry! there might be some issue, please contact support.');
}
conv.contexts.set(AppContexts1.AWAITING_REPLY, 1);
});
app.intent(ADD_TO_CART_INTENT, (conv, parameters) => {
const context1 = conv.contexts.get(AppContexts1.AWAITING_REPLY);
const selectedPhone = context1.parameters;
const qty = context1.parameters.qty;
if ('Add to Cart'){
let missingSlots = [];
if (!qty) { missingSlots.push('qty'); }
if (missingSlots.length === 1){
conv.ask(`How many phone's do you need?`);
} else {
conv.ask(`You have ordered ${qty} ${selectedPhone}. `);
conv.close("Thanks for shopping with us.");
}
} else {
conv.close('Sorry! there might be some issue, please contact support.');
}
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
let suppose user selects phone1 and orders 2 qty
then my response comes as "You have ordered 2 undefined. Thanks for shopping with us."
need help in getting the selected item name instead of undefined.
Here is the Intent that handles selecting the item from the list:
The issue is that you're not actually setting the parameters in the Context, so no values are preserved between the calls to your webhook.
In the SELECTED_PHONE_INTENT handler, the line should be something more like
conv.contexts.set(AppContexts1.AWAITING_REPLY, 5, {
phone: option
});
while in the ADD_TO_CART_INTENT handler, you would get this information with a line such as
const selectedPhone = context1.parameters.phone;

Trying to edit a specific embed field on a user action

I'm trying to triggering an edit on my embedded (already sent) message while keeping all the other fields the same value
I have found this answer as an inspiration (which works with the example): Embed message doesn't update but that doesn't seem to get all the fields, only the first. There isn't much more on the subject to find (or i'm not good at Googling :)).
So the new embed is just the first field and not all the (not changed) fields.
activityMsg = new Discord.RichEmbed({
title: 'Some text',
description: 'Description',
color: 3447003,
footer: {
icon_url: image,
text: image
},
thumbnail: {
url: image
},
fields: [
{
name: 'Text',
value: 'Text2',
},
{
name: 'Date and time',
value: '2pm',
},
{
name: 'Participants',
value: '#User',
},
{
name: 'Waiting list',
value: '#user2',
},
{
name: 'Max players',
value: '22',
}
]
});
const reactionFilterPlus = (reaction, user) => reaction.emoji.name === emoji_plus;
if(typeof title != undefined && title != null && data.length == 4 && error == ''){
var title = title[0].replace('[','').replace(']','');
// add reaction emoji to message
msg.channel.send(activityMsg)
.then(msg => msg.react(constants.emoji_plus))
.then(mReaction => {
// createReactionCollector - responds on each react, AND again at the end.
const collector = mReaction.message
.createReactionCollector(reactionFilterPlus, {
time: 15000
});
// set collector events
collector.on('collect', r => {
// immutably copy embed's Like field to new obj
let embedLikeField = Object.assign({}, activityMsg.fields[0]);
// update 'field' with new value
embedLikeField.value = `${user} <3`;
// create new embed with old title & description, new field
const newEmbed = new Discord.RichEmbed({
title: activityMsg.title,
description: activityMsg.description,
fields: [embedLikeField]
});
// edit message with new embed
// NOTE: can only edit messages you author
r.message.edit(newEmbed)
.catch(console.log);
});
})
.catch(console.log);
}
I expected this line to get all the fields, but that isn't the case.
// immutably copy embed's Like field to new obj
let embedLikeField = Object.assign({}, activityMsg.fields[0]);
I have tried let embedLikeField = Object.assign({}, activityMsg.fields[0] === 'Participants') but then I get the following error about a fieldname not present.
{ DiscordAPIError: Invalid Form Body
embed.fields[0].name: This field is required
at item.request.gen.end (/usr/src/app/node_modules/discord.js/src/client/rest/RequestHandlers/Sequential.js:79:15)
at then (/usr/src/app/node_modules/snekfetch/src/index.js:215:21)
at process._tickCallback (internal/process/next_tick.js:68:7)
English isn't my native language and I'm stilling learning nodejs sorry sorry in advance about these points.
Object.assign() performes a shallow clone on the source, Are you trying to clone your entire embed or only its first field?
activityMsg.fields[0] refers to the first element in the list called fields within your activityMsg object. Try calling your assign() with activityMsg as source.

Microsoft Bot Framework (v4): How to make hero card list as prompt choice

I'm trying to create a prompt dialog with hero card list as the choices.
I've created a function that will return the herocard list and use it as the dialog prompt choice.
How can i achieved this? or there is a better way to implement it.
Note: I need to put it in the dialog prompt because I need to implement a sequential conversation. I also put the herocard list in a separate function because I will use it in other dialog prompt.
async selectProduct(stepContext){
return await stepContext.prompt(CHOICE_PROMPT, {
prompt: 'Select Product:',
choices: this.productChoices()
});
}
productChoices(){
const productSeriesOptions = [
CardFactory.heroCard(
'Title 1',
CardFactory.images(['image URL 1']),
CardFactory.actions([
{
type: ActionTypes.ImBack,
title: 'Title 1',
value: 'Value 1'
}
])
),
CardFactory.heroCard(
'Title 2',
CardFactory.images(['image URL 2']),
CardFactory.actions([
{
type: ActionTypes.ImBack,
title: 'Title 2',
value: 'Value 2'
}
])
)
];
return productSeriesOptions;
}
I've included a sample Dialog that demonstrates presenting a carousel of HeroCards to the user (below). The HeroCard has a single button that when clicked results in the next Waterfall step being run.
I originally pulled this dialog from the 'using-cards' sample. So if you wanted to give it a run, you could replace the mainDialog.js in that project and run it in the emulator.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { ActionTypes, AttachmentLayoutTypes, CardFactory } = require('botbuilder');
const { ChoicePrompt, ComponentDialog, DialogSet, DialogTurnStatus, WaterfallDialog, ChoiceFactory } = require('botbuilder-dialogs');
const MAIN_WATERFALL_DIALOG = 'mainWaterfallDialog';
class MainDialog extends ComponentDialog {
constructor() {
super('MainDialog');
// Define the main dialog and its related components.
this.addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
this.showProductChoicesStep.bind(this),
this.showCardSelectionStep.bind(this)
]));
// The initial child Dialog to run.
this.initialDialogId = MAIN_WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {*} turnContext
* #param {*} accessor
*/
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
/**
* Send a carousel of HeroCards for the user to pick from.
* #param {WaterfallStepContext} stepContext
*/
async showProductChoicesStep(stepContext) {
console.log('MainDialog.showProductChoicesStep');
await stepContext.context.sendActivity({
attachments: this.productChoices(),
attachmentLayout: AttachmentLayoutTypes.Carousel
});
return { status: DialogTurnStatus.waiting };
}
async showCardSelectionStep(stepContext) {
console.log('MainDialog.showCardSelectionStep');
await stepContext.context.sendActivity('You picked ' + stepContext.context.activity.value);
// Give the user instructions about what to do next
await stepContext.context.sendActivity('Type anything to see another card.');
return await stepContext.endDialog();
}
// ======================================
// Helper functions used to create cards.
// ======================================
productChoices(){
const productSeriesOptions = [
CardFactory.heroCard(
'Product 1',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product1'
}
])
),
CardFactory.heroCard(
'Product 2',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product2'
}
])
),
CardFactory.heroCard(
'Product 3',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product3'
}
])
),
CardFactory.heroCard(
'Product 4',
CardFactory.images(['https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg']),
CardFactory.actions([
{
type: 'messageBack',
title: 'Pick Me',
value: 'product4'
}
])
)
];
return productSeriesOptions;
}
}
module.exports.MainDialog = MainDialog;

Not getting the items in Carousel in Actions on Google

app.intent('Our Events', (conv) =>{
if (!conv.surface.capabilities.has('actions.capability.SCREEN_OUTPUT')) {
conv.ask('Sorry, try this on a screen device or select the ' +
'phone surface in the simulator.');
return;
}
conv.ask(getdata());
conv.ask('Events are here!');
});
function getdata()
{
let carouselItems ={};
var events = db.collection('events').get().then(snapshot =>{
snapshot.forEach(doc=>{
let itemdetails ={
title:doc.data().title,
description:doc.data().info,
image: new Image({
url:doc.data().image_url,
alt:'Image',
}),
};
carouselItems[doc.id]=itemdetails;
console.log(carouselItems[doc.id]);
});
});
return new Carousel({
title:'Our Events',
items: carouselItems,
});
}
Carousels are visual selection responses and they need to a unique key to be recognized. So you have to give selection key to all carousel item objects. In your case it seems you add objects without key to array.
Please check the sample below and try to create objects according to structure.
items: {
// Add the first item to the carousel
'SELECTION_KEY_ONE': {
synonyms: [
'synonym 1',
'synonym 2',
'synonym 3',
],
title: 'Title of First Carousel Item',
description: 'This is a description of a carousel item.',
image: new Image({
url: 'IMG_URL_AOG.com',
alt: 'Image alternate text',
}),
},
// Add the second item to the carousel
'SELECTION_KEY_GOOGLE_HOME': {
synonyms: [
'Google Home Assistant',
'Assistant on the Google Home',
],
title: 'Google Home',
description: 'Google Home is a voice-activated speaker powered by ' +
'the Google Assistant.',
image: new Image({
url: 'IMG_URL_GOOGLE_HOME.com',
alt: 'Google Home',
}),
},
// Add third item to the carousel
'SELECTION_KEY_GOOGLE_PIXEL': {
synonyms: [
'Google Pixel XL',
'Pixel',
'Pixel XL',
],
title: 'Google Pixel',
description: 'Pixel. Phone by Google.',
image: new Image({
url: 'IMG_URL_GOOGLE_PIXEL.com',
alt: 'Google Pixel',
}),
},
},
}));
Your function needs to be rewritten so that your intent handler knows the operation is asynchronous. Note the addition of async and await keywords which will inform the interpreter when a given command is complete.
app.intent('Our Events', async (conv) =>{
if (!conv.surface.capabilities.has('actions.capability.SCREEN_OUTPUT')) {
conv.ask('Sorry, try this on a screen device or select the ' +
'phone surface in the simulator.');
return;
}
conv.ask(await getdata());
conv.ask('Events are here!');
});
async function getdata() {
let carouselItems ={};
const snapshot = db.collection('events').get()
snapshot.forEach(doc=>{
let itemdetails ={
title:doc.data().title,
description:doc.data().info,
image: new Image({
url:doc.data().image_url,
alt:'Image',
}),
};
carouselItems[doc.id]=itemdetails;
console.log(carouselItems[doc.id]);
});
return new Carousel({
title:'Our Events',
items: carouselItems,
});
}

Q promises and mongo db q.all

I'm making calls to a mongodb database - pulling data out... reading it, and then making further requests based on that data. Once all the data has been received, I wish to process it.
I've been using Q.promises library but don't know what I'm doing. I thought that q.all would only trigger once everything has completed? However, my processPlaylist function runs twice. I've commented the code below:
Thanks,
Rob
var PlaylistCollection = require('./models/playlist');
var AssetCollection = require('./models/asset');
var screenID = '############';
var playerData = [];
// array continaing playlistys which have been synced
var alreadySynced = [];
// Process our playlist once downloaded
var processPlaylist = function (playerData) {
console.log('----Processing Playerlist-----')
console.log(playerData);
// DO STUFF
}
// Get playlist by id. Return playlist Data
var getSubLists = function (id) {
return PlaylistCollection.findById(id);
}
// Get sub-playlist function
function getSubListRecursive(id) {
return getSubLists(id).then(function (playlist) {
// store all our returned playlist data into a playlist array
playerData.push(playlist)
// an Array to keep tabs on what we've already pulled down
alreadySynced.push(playlist.id)
// get all our playlist.resources, and only return those which are unique
var playlistResources = _.uniq(playlist.resources, 'rid');
// console.log('Playlist Resources: ', playlistResources)
// console.log(alreadySynced);
var sublists = _.pluck(_.filter(playlistResources, { 'type': 'playlist' }), 'rid');
// remove playlists which have already been synced. We don't want to pull them down twice
sublists = _.difference(sublists, alreadySynced);
// console.log('sublists: ', sublists)
// Get the next playlist and so on...
var dbops = sublists.map(function (sublist) {
// console.log(sublist)
return getSubListRecursive(sublist)
});
q.all(dbops).then(function () {
console.log('All Done - so process the playlist')
return processPlaylist(playerData);
});
})
}
// Trigger the whole process..... grab our first playlist / ScreenID
getSubListRecursive(screenID);
and I get the following output:
----Processing Playerlist-----
[ { _id: 554d1df16ce4c438f8e2225b,
title: 'list 1',
__v: 29,
daily: true,
endTime: '',
startTime: '',
resources:
[ { rid: '55650cebef204ab70302a4d9',
title: 'list 4',
type: 'playlist' },
{ rid: '554d1df16ce4c438f8e2225b',
title: 'list 1',
type: 'playlist' } ] },
{ _id: 55650cebef204ab70302a4d9,
title: 'list 4',
__v: 1,
daily: false,
endTime: '',
startTime: '',
resources:
[ { rid: '55650647ef204ab70302a4d8',
title: 'list 3',
type: 'playlist' } ] } ]
All Done - so process the playlist
----Processing Playerlist-----
[ { _id: 554d1df16ce4c438f8e2225b,
title: 'list 1',
__v: 29,
daily: true,
endTime: '',
startTime: '',
resources:
[ { rid: '55650cebef204ab70302a4d9',
title: 'list 4',
type: 'playlist' },
{ rid: '554d1df16ce4c438f8e2225b',
title: 'list 1',
type: 'playlist' } ] },
{ _id: 55650cebef204ab70302a4d9,
title: 'list 4',
__v: 1,
daily: false,
endTime: '',
startTime: '',
resources:
[ { rid: '55650647ef204ab70302a4d8',
title: 'list 3',
type: 'playlist' } ] },
{ _id: 55650647ef204ab70302a4d8,
title: 'list 3',
__v: 5,
daily: false,
endTime: '',
startTime: '',
resources:
[ { rid: '55650637ef204ab70302a4d7',
title: 'list 2',
type: 'playlist' },
{ rid: '554d1df16ce4c438f8e2225b',
title: 'list 1',
type: 'playlist' },
{ rid: '55650cebef204ab70302a4d9',
title: 'list 4',
type: 'playlist' } ] } ]
EDIT
There were a number of things wrong with what I wrote. I discussed it with a buddy of mine - who pointed out that getSubListRecursive is being invoked recursively several times so the q.all statement is being executed several times...
So I refactored...
// Get sub-playlist function
function getSubListRecursive(id) {
console.log(id)
return getSubLists(id).then(function (playlist) {
if (playlist) {
// store all our returned playlist data into a playlist array
playerData.push(playlist)
// an Array to keep tabs on what we've already pulled down
alreadySynced.push(playlist.id)
// get all our playlist.resources, and only return those which are unique
var playlistResources = _.uniq(playlist.resources, 'rid');
// console.log('Playlist Resources: ', playlistResources)
// console.log(alreadySynced);
var sublists = _.pluck(_.filter(playlistResources, { 'type': 'playlist' }), 'rid');
// remove playlists which have already been synced. We don't want to pull them down twice
sublists = _.difference(sublists, alreadySynced);
// console.log('sublists: ', sublists)
return sublists.map(function (sublist) {
// console.log(sublist)
if (sublists.length > 0) {
return getSubListRecursive(sublist)
} else {
return processPlaylist(playerData);
}
});
} else {
return processPlaylist(playerData);
}
});
}
this works. I'm basically using promises to control the flow here - which probably isn't the best way of doing it? I no longer use an all statement, and ultimately end up with array populated with all the playlist data - which I can manipulate in my processPlaylist function.
However, I've not marked the question as solved, as I'd really like to know how I can do this with Q.all (properly use promises)
Thanks,
Rob
I think you were just confused about when the entire process was finished. You need to wait until the entire recursive promise chain has resolved. I think you could use the original code with a slight change to where processPlaylist() is called:
var PlaylistCollection = require('./models/playlist');
var AssetCollection = require('./models/asset');
var screenID = '############';
var playerData = [];
// array continaing playlistys which have been synced
var alreadySynced = [];
// Process our playlist once downloaded
var processPlaylist = function (playerData) {
console.log('----Processing Playerlist-----')
console.log(playerData);
// DO STUFF
}
// Get playlist by id. Return playlist Data
var getSubLists = function (id) {
return PlaylistCollection.findById(id);
}
// Get sub-playlist function
function getSubListRecursive(id) {
return getSubLists(id).then(function (playlist) {
// store all our returned playlist data into a playlist array
playerData.push(playlist)
// an Array to keep tabs on what we've already pulled down
alreadySynced.push(playlist.id)
// get all our playlist.resources, and only return those which are unique
var playlistResources = _.uniq(playlist.resources, 'rid');
// console.log('Playlist Resources: ', playlistResources)
// console.log(alreadySynced);
var sublists = _.pluck(_.filter(playlistResources, { 'type': 'playlist' }), 'rid');
// remove playlists which have already been synced. We don't want to pull them down twice
sublists = _.difference(sublists, alreadySynced);
// console.log('sublists: ', sublists)
// Get the next playlist and so on...
var dbops = sublists.map(function (sublist) {
// console.log(sublist)
return getSubListRecursive(sublist)
});
return q.all(dbops);
});
}
// Trigger the whole process..... grab our first playlist / ScreenID
getSubListRecursive(screenID).then(function() {
console.log('All Done - so process the playlist')
return processPlaylist(playerData);
});

Resources