diff --git a/ask_configuration/LIBRARYTYPE.txt b/ask_configuration/LIBRARYTYPE.txt new file mode 100644 index 00000000..bd857adf --- /dev/null +++ b/ask_configuration/LIBRARYTYPE.txt @@ -0,0 +1,4 @@ +TV Shows +Shows +Movies +Films \ No newline at end of file diff --git a/ask_configuration/intent-schema.json b/ask_configuration/intent-schema.json index 34126a59..a6a246a6 100644 --- a/ask_configuration/intent-schema.json +++ b/ask_configuration/intent-schema.json @@ -28,6 +28,15 @@ "intent": "OnDeckIntent", "slots": [] }, + { + "intent": "RecentlyAddedIntent", + "slots": [ + { + "name": "libraryType", + "type": "LIBRARYTYPE" + } + ] + }, { "intent": "AMAZON.YesIntent" }, diff --git a/ask_configuration/sample-utterances.txt b/ask_configuration/sample-utterances.txt index 1a48a4ee..6cc754b4 100644 --- a/ask_configuration/sample-utterances.txt +++ b/ask_configuration/sample-utterances.txt @@ -6,8 +6,18 @@ AuthorizeMeIntent Authorize me AuthorizeMeIntent Sign in to Plex AuthorizeMeIntent Link my account WhatsNewIntent Release notes -OnDeckIntent What's new -OnDeckIntent What is new +RecentlyAddedIntent What's new +RecentlyAddedIntent What is new +RecentlyAddedIntent What's new in {libraryType} +RecentlyAddedIntent What is new in {libraryType} +RecentlyAddedIntent What are my new {libraryType} +RecentlyAddedIntent What are my recently added {libraryType} +RecentlyAddedIntent What are the new {libraryType} +RecentlyAddedIntent Latest {libraryType} +RecentlyAddedIntent About the latest {libraryType} +RecentlyAddedIntent Recently added {libraryType} +RecentlyAddedIntent About the recently added {libraryType} +RecentlyAddedIntent About recently added {libraryType} OnDeckIntent What is on deck OnDeckIntent What's on deck StartShowIntent Put on {showName} diff --git a/lib/plexutils.js b/lib/plexutils.js index ec7ed031..0a5b55ac 100644 --- a/lib/plexutils.js +++ b/lib/plexutils.js @@ -75,6 +75,20 @@ var getOnDeck = function(app, library) { } }; +/** + * Gets a list of items that have been recently added, either for all library types or a specific one. + * @param {module:App~App} app + * @param {?Object} library - Optional library object to ask for only items in that library + * @returns {Object} JSON object of shows/movies/etc recently added + */ +var getRecentlyAdded = function(app, library) { + if (library) { + return query(app.plex.pms, library.uri + '/recentlyAdded'); + } else { + return query(app.plex.pms, '/library/recentlyAdded'); + } +}; + /** * @typedef {Object} PlexAPI.Directory */ @@ -340,7 +354,9 @@ var getShowNamesFromList = function(apiResult) { for(i = 0; i < apiResult._children.length && i < 6; i++) { if(apiResult._children[i].type == 'episode') { shows.push(apiResult._children[i].grandparentTitle); - } else if (apiResult._children[i].type == 'movie') { + } else if (apiResult._children[i].type == 'season') { + shows.push(apiResult._children[i].parentTitle); + }else if (apiResult._children[i].type == 'movie') { shows.push(apiResult._children[i].title); } } @@ -359,6 +375,7 @@ module.exports = { getPlayers: getPlayers, startShow: startShow, getOnDeck: getOnDeck, + getRecentlyAdded: getRecentlyAdded, getListOfTVShows: getListOfTVShows, getAllEpisodesOfShow: getAllEpisodesOfShow, playMedia: playMedia, diff --git a/lib/states/authed.js b/lib/states/authed.js index 6cb11fcf..3aa861a8 100644 --- a/lib/states/authed.js +++ b/lib/states/authed.js @@ -54,8 +54,41 @@ var onDeckIntent = function(request, response) { .card("TV Shows Ready to Watch", "On Deck in your TV library: \n\n" + showListCard) .send(); }).catch(function(err) { - app.skill.error(err, request, response); - }); + app.skill.error(err, request, response); + }); + + return false; +}; + +var recentlyAddedIntent = function (request, response) { + var app = request.data._plex_app; + var libraryType = request.slot('libraryType', null); + var library; + + if (libraryType === 'tv shows' || libraryType === 'shows') { + library = app.user.TVLibrary; + } else if (libraryType === 'movies' || libraryType === 'films') { + library = app.user.MovieLibrary; + } + + plexutils.getRecentlyAdded(app, library) + .then(plexutils.getShowNamesFromList) + .then(function (mediaList) { + if(mediaList.length === 0) { + return response.say("You have not added any media recently!").send(); + } + + var showListCard = mediaList.join('\n'); + var showSpokenListHyphenated = utils.buildNaturalLangList(mediaList, 'and', true); + var libraryTypeTitle = ("Recently Added " + (utils.toTitleCase(libraryType) || '')).trim(); + + return response.say(libraryTypeTitle + ": " + showSpokenListHyphenated + '.') + .card(libraryTypeTitle, showListCard) + .send(); + }) + .catch(function(err) { + app.skill.error(err, request, response); + }); return false; }; @@ -220,6 +253,7 @@ var noIntent = function(request,response) { module.exports = { intents: { 'OnDeckIntent': onDeckIntent, + 'RecentlyAddedIntent': recentlyAddedIntent, 'StartShowIntent': startShowIntent, 'StartRandomShowIntent': startRandomShowIntent, 'StartSpecificEpisodeIntent': startSpecificEpisodeIntent, diff --git a/lib/user.js b/lib/user.js index 1c08932e..4ca1a2c4 100644 --- a/lib/user.js +++ b/lib/user.js @@ -37,6 +37,25 @@ var User = function(app, dbobject) { } }); + Object.defineProperty(this, 'MovieLibrary', { + get: function() { + if (!context.dbobject.libraries) { + throw new Error("Trying to get MovieLibrary with no libraries on the user record"); + } + + var libraries = context.dbobject.libraries.filter(function(library) { + return library.type == "movie"; + }); + + // For now we're going to sort by key, which more or less gives us a sort by creation date. + libraries.sort(function(a, b) { + return Number(a.key) - Number(b.key); + }); + + return libraries[0]; + } + }); + Object.defineProperty(this, 'TVLibrary', { get: function() { if (!context.dbobject.libraries) { diff --git a/lib/utils.js b/lib/utils.js index 08d6fca7..79b57d32 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -55,4 +55,23 @@ module.exports.buildNaturalLangList = function(items, finalWord, hyphenize) { module.exports.randomInt = function(low, high) { return Math.floor(Math.random() * (high - low) + low); +}; + +/** + * Super simple title-casing, does not handle lots of things well. Best to just use with simple + * complete words, no acronyms or anything complicated; + * @param {String} string -- String to change to title case + * @return {String} Title-cased string + */ +module.exports.toTitleCase = function(string) { + var exceptions = ['a', 'an', 'the', 'at', 'by', 'for', 'in', 'of', 'on', 'to', 'up', 'and', 'as', 'but', 'or', 'nor']; + if (string) { + string = string.split(' ').map(function(word) { + return exceptions.find(function(compare) { + return compare === word.toLowerCase(); + }) ? word : word.charAt(0).toUpperCase() + word.slice(1); + }).join(' '); + } + + return string; }; \ No newline at end of file