From 7e0967267df624d9aab8b7c2cac4f9704b325c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Nyi=CC=81ri?= Date: Thu, 10 Jun 2021 23:01:30 +0200 Subject: [PATCH] Add: Deck, Continue Watching, Watch Next and Recently Added special libraries with support for older plex servers --- README.md | 13 ++-- dist/plex-meets-homeassistant.js | 100 +++++++++++++++++++++++++++++-- src/modules/Plex.ts | 53 +++++++++++++++- src/modules/utils.ts | 7 ++- src/plex-meets-homeassistant.ts | 54 +++++++++++++++-- 5 files changed, 210 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index fb5de59..fdcd43a 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,14 @@ More images [at the end of the readme](https://github.com/JurajNyiri/PlexMeetsHo **libraryName**: Name of the library you wish to render. -Available special libraries: - -_Deck_: Shows Continue Watching - -_Recently Added_: Shows recently added episodes of TV Shows. Tip: For movies, just put in your library name and use sort. +_Available special libraries:_ + +| Special Library | Description | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------- | +| Watch Next | Shows Continue Watching feed just like in your new plex interface. **Does not work with old servers.** | +| Recently Added | Shows recently added tv show episodes, might use old Plex API. For recently added movies use sort with your movies library. | +| Continue Watching | Shows movies and tv shows in progress, uses old Plex API. | +| Deck | Shows tv shows on deck, uses old Plex API. | **protocol**: _Optional_ Protocol to use for Plex. Defaults to "http". diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index c765fd6..a81f44c 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -18729,13 +18729,31 @@ class Plex { }); return this.exportSectionsData(await Promise.all(sectionsRequests)); }; - this.getRecentyAdded = async () => { + this.getRecentyAdded = async (useHub = false) => { + if (useHub) { + const hubs = await this.getHubs(); + let recentlyAddedData = {}; + // eslint-disable-next-line consistent-return + lodash.forEach(hubs.Hub, hub => { + if (lodash.isEqual(hub.key, '/hubs/home/recentlyAdded?type=2')) { + recentlyAddedData = hub; + return false; + } + }); + return recentlyAddedData; + } const url = this.authorizeURL(`${this.getBasicURL()}/hubs/home/recentlyAdded?type=2&X-Plex-Container-Start=0&X-Plex-Container-Size=50`); return (await axios.get(url, { timeout: this.requestTimeout })).data.MediaContainer; }; - this.getContinueWatching = async () => { + this.getHubs = async () => { + const url = this.authorizeURL(`${this.getBasicURL()}/hubs?includeEmpty=1&count=50&includeFeaturedTags=1&includeTypeFirst=1&includeStations=1&includeExternalMetadata=1&excludePlaylists=1`); + return (await axios.get(url, { + timeout: this.requestTimeout + })).data.MediaContainer; + }; + this.getWatchNext = async () => { const sections = await this.getSections(); let sectionsString = ''; lodash.forEach(sections, section => { @@ -18747,6 +18765,30 @@ class Plex { timeout: this.requestTimeout })).data.MediaContainer; }; + this.getContinueWatching = async () => { + const hubs = await this.getHubs(); + let continueWatchingData = {}; + // eslint-disable-next-line consistent-return + lodash.forEach(hubs.Hub, hub => { + if (lodash.isEqual(hub.key, '/hubs/home/continueWatching')) { + continueWatchingData = hub; + return false; + } + }); + return continueWatchingData; + }; + this.getOnDeck = async () => { + const hubs = await this.getHubs(); + let onDeckData = {}; + // eslint-disable-next-line consistent-return + lodash.forEach(hubs.Hub, hub => { + if (lodash.isEqual(hub.key, '/hubs/home/onDeck')) { + onDeckData = hub; + return false; + } + }); + return onDeckData; + }; this.getBasicURL = () => { return `${this.protocol}://${this.ip}:${this.port}`; }; @@ -19094,6 +19136,9 @@ const isVideoFullScreen = (_this) => { return ((video.offsetWidth === body.offsetHeight && video.offsetHeight === body.offsetHeight) || (_this.videoElem && _this.videoElem.classList.contains('simulatedFullScreen'))); }; +const getOldPlexServerErrorMessage = (libraryName) => { + return `PlexMeetsHomeAssistant: 404 Error requesting library feed for ${libraryName}. Plex API might have changed or using outdated server. Library ${libraryName} will not work.`; +}; const findTrailerURL = (movieData) => { let foundURL = ''; if (movieData.Extras && movieData.Extras.Metadata && movieData.Extras.Metadata.length > 0) { @@ -19874,15 +19919,62 @@ class PlexMeetsHomeAssistant extends HTMLElement { try { if (this.plex) { await this.plex.init(); + try { + const onDeck = await this.plex.getOnDeck(); + this.data.Deck = onDeck.Metadata; + } + catch (err) { + if (lodash.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Deck')); + } + else { + throw err; + } + } try { const continueWatching = await this.plex.getContinueWatching(); + this.data['Continue Watching'] = continueWatching.Metadata; + } + catch (err) { + if (lodash.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Continue Watching')); + } + else { + throw err; + } + } + try { + const watchNext = await this.plex.getWatchNext(); + this.data['Watch Next'] = watchNext.Metadata; + } + catch (err) { + if (lodash.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Watch Next')); + } + else { + throw err; + } + } + try { const recentlyAdded = await this.plex.getRecentyAdded(); - this.data.Deck = continueWatching.Metadata; this.data['Recently Added'] = recentlyAdded.Metadata; } catch (err) { if (lodash.includes(err.message, 'Request failed with status code 404')) { - console.warn('PlexMeetsHomeAssistant: You are using outdated Plex server. Recently added and continue watching is not available.'); + try { + console.warn('PlexMeetsHomeAssistant: Using old endpoint for recently added tv shows. Consider updating your Plex server.'); + const recentlyAdded = await this.plex.getRecentyAdded(true); + this.data['Recently Added'] = recentlyAdded.Metadata; + // eslint-disable-next-line no-shadow + } + catch (err) { + if (lodash.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Recently Added')); + } + else { + throw err; + } + } } else { throw err; diff --git a/src/modules/Plex.ts b/src/modules/Plex.ts index 4a07f54..4321194 100644 --- a/src/modules/Plex.ts +++ b/src/modules/Plex.ts @@ -94,7 +94,19 @@ class Plex { return this.exportSectionsData(await Promise.all(sectionsRequests)); }; - getRecentyAdded = async (): Promise => { + getRecentyAdded = async (useHub = false): Promise => { + if (useHub) { + const hubs = await this.getHubs(); + let recentlyAddedData: Record = {}; + // eslint-disable-next-line consistent-return + _.forEach(hubs.Hub, hub => { + if (_.isEqual(hub.key, '/hubs/home/recentlyAdded?type=2')) { + recentlyAddedData = hub; + return false; + } + }); + return recentlyAddedData; + } const url = this.authorizeURL( `${this.getBasicURL()}/hubs/home/recentlyAdded?type=2&X-Plex-Container-Start=0&X-Plex-Container-Size=50` ); @@ -105,7 +117,18 @@ class Plex { ).data.MediaContainer; }; - getContinueWatching = async (): Promise => { + private getHubs = async (): Promise => { + const url = this.authorizeURL( + `${this.getBasicURL()}/hubs?includeEmpty=1&count=50&includeFeaturedTags=1&includeTypeFirst=1&includeStations=1&includeExternalMetadata=1&excludePlaylists=1` + ); + return ( + await axios.get(url, { + timeout: this.requestTimeout + }) + ).data.MediaContainer; + }; + + getWatchNext = async (): Promise => { const sections = await this.getSections(); let sectionsString = ''; _.forEach(sections, section => { @@ -122,6 +145,32 @@ class Plex { ).data.MediaContainer; }; + getContinueWatching = async (): Promise => { + const hubs = await this.getHubs(); + let continueWatchingData: Record = {}; + // eslint-disable-next-line consistent-return + _.forEach(hubs.Hub, hub => { + if (_.isEqual(hub.key, '/hubs/home/continueWatching')) { + continueWatchingData = hub; + return false; + } + }); + return continueWatchingData; + }; + + getOnDeck = async (): Promise => { + const hubs = await this.getHubs(); + let onDeckData: Record = {}; + // eslint-disable-next-line consistent-return + _.forEach(hubs.Hub, hub => { + if (_.isEqual(hub.key, '/hubs/home/onDeck')) { + onDeckData = hub; + return false; + } + }); + return onDeckData; + }; + getBasicURL = (): string => { return `${this.protocol}://${this.ip}:${this.port}`; }; diff --git a/src/modules/utils.ts b/src/modules/utils.ts index 835c5f7..31ec8cb 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -65,6 +65,10 @@ const isVideoFullScreen = (_this: any): boolean => { ); }; +const getOldPlexServerErrorMessage = (libraryName: string): string => { + return `PlexMeetsHomeAssistant: 404 Error requesting library feed for ${libraryName}. Plex API might have changed or using outdated server. Library ${libraryName} will not work.`; +}; + const findTrailerURL = (movieData: Record): string => { let foundURL = ''; if (movieData.Extras && movieData.Extras.Metadata && movieData.Extras.Metadata.length > 0) { @@ -180,5 +184,6 @@ export { createEpisodesView, findTrailerURL, isVideoFullScreen, - hasEpisodes + hasEpisodes, + getOldPlexServerErrorMessage }; diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index b196c6c..6baca16 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -13,7 +13,8 @@ import { createEpisodesView, findTrailerURL, isVideoFullScreen, - hasEpisodes + hasEpisodes, + getOldPlexServerErrorMessage } from './modules/utils'; import style from './modules/style'; @@ -143,16 +144,59 @@ class PlexMeetsHomeAssistant extends HTMLElement { try { if (this.plex) { await this.plex.init(); + + try { + const onDeck = await this.plex.getOnDeck(); + this.data.Deck = onDeck.Metadata; + } catch (err) { + if (_.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Deck')); + } else { + throw err; + } + } + try { const continueWatching = await this.plex.getContinueWatching(); + this.data['Continue Watching'] = continueWatching.Metadata; + } catch (err) { + if (_.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Continue Watching')); + } else { + throw err; + } + } + + try { + const watchNext = await this.plex.getWatchNext(); + this.data['Watch Next'] = watchNext.Metadata; + } catch (err) { + if (_.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Watch Next')); + } else { + throw err; + } + } + + try { const recentlyAdded = await this.plex.getRecentyAdded(); - this.data.Deck = continueWatching.Metadata; this.data['Recently Added'] = recentlyAdded.Metadata; } catch (err) { if (_.includes(err.message, 'Request failed with status code 404')) { - console.warn( - 'PlexMeetsHomeAssistant: You are using outdated Plex server. Recently added and continue watching is not available.' - ); + try { + console.warn( + 'PlexMeetsHomeAssistant: Using old endpoint for recently added tv shows. Consider updating your Plex server.' + ); + const recentlyAdded = await this.plex.getRecentyAdded(true); + this.data['Recently Added'] = recentlyAdded.Metadata; + // eslint-disable-next-line no-shadow + } catch (err) { + if (_.includes(err.message, 'Request failed with status code 404')) { + console.warn(getOldPlexServerErrorMessage('Recently Added')); + } else { + throw err; + } + } } else { throw err; }