From 0811e5334cd0a152a09537d87c87ff85305cd5ee Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 2 Aug 2021 20:37:17 -0700 Subject: [PATCH 1/7] Add method to get the Plex Web URL for media items --- plexapi/base.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/plexapi/base.py b/plexapi/base.py index bb41f11f..22cde202 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -575,12 +575,45 @@ class PlexPartialObject(PlexObject): def history(self, maxresults=9999999, mindate=None): """ Get Play History for a media item. + Parameters: maxresults (int): Only return the specified number of results (optional). mindate (datetime): Min datetime to return results from. """ return self._server.history(maxresults=maxresults, mindate=mindate, ratingKey=self.ratingKey) + def _buildWebURL(self, base=None, endpoint='details', key='', legacy=False): + """ Build the Plex Web URL for the item. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + endpoint (str): The Plex Web URL endpoint. + 'playlist' for playlists, 'details' for all other media types. + key (str): The Plex API URL for the item (/library/metadata/). + legacy (bool): True or False to use the legacy URL. + Photoalbum and Photo use the legacy URL. + """ + if base is None: + base = 'https://app.plex.tv/desktop' + + params = {'key': key or self.key} + if legacy: + params['legacy'] = 1 + + return '%s#!/server/%s/%s%s' % ( + base, self._server.machineIdentifier, endpoint, utils.joinArgs(params) + ) + + def getWebURL(self, base=None): + """ Returns the Plex Web URL for the item. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + """ + return self._buildWebURL(base=base) + class Playable(object): """ This is a general place to store functions specific to media that is Playable. From 9483e4d9c9072af7cc5f8e5eeb51804d0ce9b00e Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Mon, 2 Aug 2021 20:37:29 -0700 Subject: [PATCH 2/7] Add custom Plex Web URL override for track, photo, and playlist --- plexapi/audio.py | 9 +++++++++ plexapi/photo.py | 18 ++++++++++++++++++ plexapi/playlist.py | 9 +++++++++ 3 files changed, 36 insertions(+) diff --git a/plexapi/audio.py b/plexapi/audio.py index bb1dee22..f6701cc5 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -415,3 +415,12 @@ class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, def _defaultSyncTitle(self): """ Returns str, default title for a new syncItem. """ return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title) + + def getWebURL(self, base=None): + """ Returns the Plex Web URL for the track's album. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + """ + return self._buildWebURL(base=base, key=self.parentKey) diff --git a/plexapi/photo.py b/plexapi/photo.py index f3196663..008a97e1 100644 --- a/plexapi/photo.py +++ b/plexapi/photo.py @@ -137,6 +137,15 @@ class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin, RatingMixin): filepaths.append(filepath) return filepaths + def getWebURL(self, base=None): + """ Returns the Plex Web URL for the photoalbum. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + """ + return self._buildWebURL(base=base, legacy=True) + @utils.registerPlexObject class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, TagMixin): @@ -301,3 +310,12 @@ class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixi if filepath: filepaths.append(filepath) return filepaths + + def getWebURL(self, base=None): + """ Returns the Plex Web URL for the photo's photoalbum. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + """ + return self._buildWebURL(base=base, key=self.parentKey, legacy=True) diff --git a/plexapi/playlist.py b/plexapi/playlist.py index bac46965..3bb18913 100644 --- a/plexapi/playlist.py +++ b/plexapi/playlist.py @@ -459,3 +459,12 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi raise Unsupported('Unsupported playlist content') return myplex.sync(sync_item, client=client, clientId=clientId) + + def getWebURL(self, base=None): + """ Returns the Plex Web URL for the playlist. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + """ + return self._buildWebURL(base=base, endpoint='playlist') From 17ab4ce252738bc11856817aef78359fbf99315a Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 4 Aug 2021 13:11:28 -0700 Subject: [PATCH 3/7] Add getWebURL for LibrarySection and library hubs --- plexapi/base.py | 2 +- plexapi/library.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/plexapi/base.py b/plexapi/base.py index 22cde202..9a8e2e54 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -595,7 +595,7 @@ class PlexPartialObject(PlexObject): Photoalbum and Photo use the legacy URL. """ if base is None: - base = 'https://app.plex.tv/desktop' + base = 'https://app.plex.tv/desktop/' params = {'key': key or self.key} if legacy: diff --git a/plexapi/library.py b/plexapi/library.py index d98f684d..94bef71b 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1456,6 +1456,29 @@ class LibrarySection(PlexObject): def listChoices(self, category, libtype=None, **kwargs): return self.listFilterChoices(field=category, libtype=libtype) + def getWebURL(self, base=None, tab=None, key=None): + """ Returns the Plex Web URL for the library. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + tab (str): The library tab (recommended, library, collections, playlists, timeline). + key (str): A hub key. + """ + if base is None: + base = 'https://app.plex.tv/desktop/' + + params = {'source': self.key} + if tab is not None: + params['pivot'] = tab + if key is not None: + params['key'] = key + params['pageType'] = 'list' + + return '%s#!/media/%s/com.plexapp.plugins.library%s' % ( + base, self._server.machineIdentifier, utils.joinArgs(params) + ) + class MovieSection(LibrarySection): """ Represents a :class:`~plexapi.library.LibrarySection` section containing movies. @@ -1857,6 +1880,7 @@ class Hub(PlexObject): self.style = data.attrib.get('style') self.title = data.attrib.get('title') self.type = data.attrib.get('type') + self._section = None # cache for self.section def __len__(self): return self.size @@ -1868,6 +1892,22 @@ class Hub(PlexObject): self.more = False self.size = len(self.items) + def section(self): + """ Returns the :class:`~plexapi.library.LibrarySection` this hub belongs to. + """ + if self._section is None: + self._section = self._server.library.sectionByID(self.librarySectionID) + return self._section + + def getWebURL(self, base=None): + """ Returns the Plex Web URL for the library. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + """ + return self.section().getWebURL(base=base, key=self.key) + class HubMediaTag(PlexObject): """ Base class of hub media tag search results. From e10f96a52959c8ab78640bc284157172cf08bd54 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 4 Aug 2021 13:12:33 -0700 Subject: [PATCH 4/7] Add getPlaylistsWebURL for the server playlists page --- plexapi/server.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plexapi/server.py b/plexapi/server.py index c31c9a69..b673c2f3 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -890,6 +890,25 @@ class PlexServer(PlexObject): key = '/statistics/resources?timespan=6' return self.fetchItems(key, StatisticsResources) + def getPlaylistsWebURL(self, base=None, tab=None): + """ Returns the Plex Web URL for the server playlists page. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + tab (str): The playlist tab (audio, video, photo). + """ + if base is None: + base = 'https://app.plex.tv/desktop/' + + params = {'source': 'playlists'} + if tab is not None: + params['pivot'] = 'playlists.%s' % tab + + return '%s#!/media/%s/com.plexapp.plugins.library%s' % ( + base, self._server.machineIdentifier, utils.joinArgs(params) + ) + class Account(PlexObject): """ Contains the locally cached MyPlex account information. The properties provided don't From 3bebeaacd53663f0caf6a31cad03d362cdabb463 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 4 Aug 2021 14:02:01 -0700 Subject: [PATCH 5/7] Add tests for Plex Web URLs --- tests/test_audio.py | 26 ++++++++++++++++++++++++++ tests/test_collection.py | 10 ++++++++++ tests/test_library.py | 26 ++++++++++++++++++++++++++ tests/test_photo.py | 20 ++++++++++++++++++++ tests/test_playlist.py | 15 +++++++++++++++ tests/test_server.py | 13 +++++++++++++ tests/test_video.py | 35 +++++++++++++++++++++++++++++++++++ 7 files changed, 145 insertions(+) diff --git a/tests/test_audio.py b/tests/test_audio.py index 33325a4c..94c76fc3 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from urllib.parse import quote_plus + from . import conftest as utils from . import test_media, test_mixins @@ -103,6 +105,14 @@ def test_audio_Artist_media_tags(artist): test_media.tag_style(artist) +def test_video_Artist_PlexWebURL(plex, artist): + url = artist.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(artist.key) in url + + def test_audio_Album_attrs(album): assert utils.is_datetime(album.addedAt) if album.art: @@ -196,6 +206,14 @@ def test_audio_Album_media_tags(album): test_media.tag_style(album) +def test_video_Album_PlexWebURL(plex, album): + url = album.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(album.key) in url + + def test_audio_Track_attrs(album): track = album.get("As Colourful As Ever").reload() assert utils.is_datetime(track.addedAt) @@ -337,6 +355,14 @@ def test_audio_Track_media_tags(track): test_media.tag_mood(track) +def test_video_Track_PlexWebURL(plex, track): + url = track.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(track.parentKey) in url + + def test_audio_Audio_section(artist, album, track): assert artist.section() assert album.section() diff --git a/tests/test_collection.py b/tests/test_collection.py index 92490359..52ae5f1a 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from urllib.parse import quote_plus + import pytest from plexapi.exceptions import BadRequest, NotFound @@ -283,3 +285,11 @@ def test_Collection_mixins_rating(collection): def test_Collection_mixins_tags(collection): test_mixins.edit_label(collection) + + +def test_Collection_PlexWebURL(plex, collection): + url = collection.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(collection.key) in url diff --git a/tests/test_library.py b/tests/test_library.py index 66a1df3f..3a6442ec 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from collections import namedtuple from datetime import datetime, timedelta +from urllib.parse import quote_plus + import pytest from plexapi.exceptions import BadRequest, NotFound @@ -212,6 +214,30 @@ def test_library_MovieSection_collection_exception(movies): movies.collection("Does Not Exists") +def test_library_MovieSection_PlexWebURL(plex, movies): + tab = 'library' + url = movies.getWebURL(tab=tab) + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'source=%s' % movies.key in url + assert 'pivot=%s' % tab in url + # Test a different base + base = 'https://doesnotexist.com/plex' + url = movies.getWebURL(base=base) + assert url.startswith(base) + + +def test_library_MovieSection_PlexWebURL_hub(plex, movies): + hubs = movies.hubs() + hub = next(iter(hubs), None) + assert hub is not None + url = hub.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'source=%s' % movies.key in url + assert quote_plus(hub.key) in url + + def test_library_ShowSection_all(tvshows): assert len(tvshows.all(title__iexact="The 100")) diff --git a/tests/test_photo.py b/tests/test_photo.py index 8d282f81..499948c8 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from urllib.parse import quote_plus + from . import test_media, test_mixins @@ -22,6 +24,15 @@ def test_photo_Photoalbum_mixins_rating(photoalbum): test_mixins.edit_rating(photoalbum) +def test_video_Photoalbum_PlexWebURL(plex, photoalbum): + url = photoalbum.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(photoalbum.key) in url + assert 'legacy=1' in url + + def test_photo_Photo_mixins_rating(photo): test_mixins.edit_rating(photo) @@ -33,3 +44,12 @@ def test_photo_Photo_mixins_tags(photo): def test_photo_Photo_media_tags(photo): photo.reload() test_media.tag_tag(photo) + + +def test_video_Photo_PlexWebURL(plex, photo): + url = photo.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(photo.parentKey) in url + assert 'legacy=1' in url diff --git a/tests/test_playlist.py b/tests/test_playlist.py index 92b1204a..d571c089 100644 --- a/tests/test_playlist.py +++ b/tests/test_playlist.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import time +from urllib.parse import quote_plus import pytest from plexapi.exceptions import BadRequest, NotFound, Unsupported @@ -255,3 +256,17 @@ def test_Playlist_exceptions(plex, movies, movie, artist): playlist.moveItem(movie) finally: playlist.delete() + + +def test_Playlist_PlexWebURL(plex, show): + title = 'test_playlist_plexweburl' + episodes = show.episodes() + playlist = plex.createPlaylist(title, items=episodes[:3]) + try: + url = playlist.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'playlist' in url + assert quote_plus(playlist.key) in url + finally: + playlist.delete() diff --git a/tests/test_server.py b/tests/test_server.py index 4d7eaa17..c0f8afd9 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -512,3 +512,16 @@ def test_server_transcode_sessions(plex, requests_mock): assert session.videoCodec in utils.CODECS assert session.videoDecision == "transcode" assert utils.is_int(session.width, gte=852) + + +def test_server_PlaylistsPlexWebURL(plex): + tab = 'audio' + url = plex.getPlaylistsWebURL(tab=tab) + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'source=playlists' in url + assert 'pivot=playlists.%s' % tab in url + # Test a different base + base = 'https://doesnotexist.com/plex' + url = plex.getPlaylistsWebURL(base=base) + assert url.startswith(base) diff --git a/tests/test_video.py b/tests/test_video.py index d919b6b1..b4829d09 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -608,6 +608,17 @@ def test_video_Movie_extras(movies): assert extra.type == 'clip' assert extra.section() == movies +def test_video_Movie_PlexWebURL(plex, movie): + url = movie.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(movie.key) in url + # Test a different base + base = 'https://doesnotexist.com/plex' + url = movie.getWebURL(base=base) + assert url.startswith(base) + def test_video_Show_attrs(show): assert utils.is_datetime(show.addedAt) @@ -801,6 +812,14 @@ def test_video_Show_media_tags(show): test_media.tag_similar(show) +def test_video_Show_PlexWebURL(plex, show): + url = show.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(show.key) in url + + def test_video_Season(show): seasons = show.seasons() assert len(seasons) == 2 @@ -912,6 +931,14 @@ def test_video_Season_mixins_tags(show): test_mixins.edit_collection(season) +def test_video_Season_PlexWebURL(plex, season): + url = season.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(season.key) in url + + def test_video_Episode_updateProgress(episode, patched_http_call): episode.updateProgress(2 * 60 * 1000) # 2 minutes. @@ -1119,6 +1146,14 @@ def test_video_Episode_media_tags(episode): test_media.tag_writer(episode) +def test_video_Episode_PlexWebURL(plex, episode): + url = episode.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert 'details' in url + assert quote_plus(episode.key) in url + + def test_that_reload_return_the_same_object(plex): # we want to check this that all the urls are correct movie_library_search = plex.library.section("Movies").search("Elephants Dream")[0] From 49ce2f9bb4ae3f63c856ea7f690f128a4052e320 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Sep 2021 15:23:09 -0700 Subject: [PATCH 6/7] Refactor getWebURL --- plexapi/audio.py | 11 +++-------- plexapi/base.py | 29 ++++++----------------------- plexapi/library.py | 17 +---------------- plexapi/photo.py | 22 ++++++---------------- plexapi/playlist.py | 11 +++-------- plexapi/server.py | 35 ++++++++++++++++++++++++++--------- 6 files changed, 45 insertions(+), 80 deletions(-) diff --git a/plexapi/audio.py b/plexapi/audio.py index f6701cc5..7761d6db 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -416,11 +416,6 @@ class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, """ Returns str, default title for a new syncItem. """ return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title) - def getWebURL(self, base=None): - """ Returns the Plex Web URL for the track's album. - - Parameters: - base (str): The base URL before the fragment (``#!``). - Default is https://app.plex.tv/desktop. - """ - return self._buildWebURL(base=base, key=self.parentKey) + def _getWebURL(self, base=None): + """ Get the Plex Web URL with the correct parameters. """ + return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey) diff --git a/plexapi/base.py b/plexapi/base.py index 9a8e2e54..b6f0af6a 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -582,37 +582,20 @@ class PlexPartialObject(PlexObject): """ return self._server.history(maxresults=maxresults, mindate=mindate, ratingKey=self.ratingKey) - def _buildWebURL(self, base=None, endpoint='details', key='', legacy=False): - """ Build the Plex Web URL for the item. - - Parameters: - base (str): The base URL before the fragment (``#!``). - Default is https://app.plex.tv/desktop. - endpoint (str): The Plex Web URL endpoint. - 'playlist' for playlists, 'details' for all other media types. - key (str): The Plex API URL for the item (/library/metadata/). - legacy (bool): True or False to use the legacy URL. - Photoalbum and Photo use the legacy URL. + def _getWebURL(self, base=None): + """ Get the Plex Web URL with the correct parameters. + Private method to allow overriding parameters from subclasses. """ - if base is None: - base = 'https://app.plex.tv/desktop/' - - params = {'key': key or self.key} - if legacy: - params['legacy'] = 1 - - return '%s#!/server/%s/%s%s' % ( - base, self._server.machineIdentifier, endpoint, utils.joinArgs(params) - ) + return self._server._buildWebURL(base=base, endpoint='details', key=self.key) def getWebURL(self, base=None): - """ Returns the Plex Web URL for the item. + """ Returns the Plex Web URL for a media item. Parameters: base (str): The base URL before the fragment (``#!``). Default is https://app.plex.tv/desktop. """ - return self._buildWebURL(base=base) + return self._getWebURL(base=base) class Playable(object): diff --git a/plexapi/library.py b/plexapi/library.py index 94bef71b..92887784 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1465,19 +1465,13 @@ class LibrarySection(PlexObject): tab (str): The library tab (recommended, library, collections, playlists, timeline). key (str): A hub key. """ - if base is None: - base = 'https://app.plex.tv/desktop/' - params = {'source': self.key} if tab is not None: params['pivot'] = tab if key is not None: params['key'] = key params['pageType'] = 'list' - - return '%s#!/media/%s/com.plexapp.plugins.library%s' % ( - base, self._server.machineIdentifier, utils.joinArgs(params) - ) + return self._server._buildWebURL(base=base, **params) class MovieSection(LibrarySection): @@ -1899,15 +1893,6 @@ class Hub(PlexObject): self._section = self._server.library.sectionByID(self.librarySectionID) return self._section - def getWebURL(self, base=None): - """ Returns the Plex Web URL for the library. - - Parameters: - base (str): The base URL before the fragment (``#!``). - Default is https://app.plex.tv/desktop. - """ - return self.section().getWebURL(base=base, key=self.key) - class HubMediaTag(PlexObject): """ Base class of hub media tag search results. diff --git a/plexapi/photo.py b/plexapi/photo.py index 008a97e1..6a60db81 100644 --- a/plexapi/photo.py +++ b/plexapi/photo.py @@ -137,14 +137,9 @@ class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin, RatingMixin): filepaths.append(filepath) return filepaths - def getWebURL(self, base=None): - """ Returns the Plex Web URL for the photoalbum. - - Parameters: - base (str): The base URL before the fragment (``#!``). - Default is https://app.plex.tv/desktop. - """ - return self._buildWebURL(base=base, legacy=True) + def _getWebURL(self, base=None): + """ Get the Plex Web URL with the correct parameters. """ + return self._server._buildWebURL(base=base, endpoint='details', key=self.key, legacy=1) @utils.registerPlexObject @@ -311,11 +306,6 @@ class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixi filepaths.append(filepath) return filepaths - def getWebURL(self, base=None): - """ Returns the Plex Web URL for the photo's photoalbum. - - Parameters: - base (str): The base URL before the fragment (``#!``). - Default is https://app.plex.tv/desktop. - """ - return self._buildWebURL(base=base, key=self.parentKey, legacy=True) + def _getWebURL(self, base=None): + """ Get the Plex Web URL with the correct parameters. """ + return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey, legacy=1) diff --git a/plexapi/playlist.py b/plexapi/playlist.py index 3bb18913..c0510e5e 100644 --- a/plexapi/playlist.py +++ b/plexapi/playlist.py @@ -460,11 +460,6 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi return myplex.sync(sync_item, client=client, clientId=clientId) - def getWebURL(self, base=None): - """ Returns the Plex Web URL for the playlist. - - Parameters: - base (str): The base URL before the fragment (``#!``). - Default is https://app.plex.tv/desktop. - """ - return self._buildWebURL(base=base, endpoint='playlist') + def _getWebURL(self, base=None): + """ Get the Plex Web URL with the correct parameters. """ + return self._server._buildWebURL(base=base, endpoint='playlist', key=self.key) diff --git a/plexapi/server.py b/plexapi/server.py index b673c2f3..5c0beaed 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -890,24 +890,41 @@ class PlexServer(PlexObject): key = '/statistics/resources?timespan=6' return self.fetchItems(key, StatisticsResources) - def getPlaylistsWebURL(self, base=None, tab=None): - """ Returns the Plex Web URL for the server playlists page. + def _buildWebURL(self, base=None, endpoint=None, **kwargs): + """ Build the Plex Web URL for the object. Parameters: base (str): The base URL before the fragment (``#!``). Default is https://app.plex.tv/desktop. - tab (str): The playlist tab (audio, video, photo). + endpoint (str): The Plex Web URL endpoint. + None for server, 'playlist' for playlists, 'details' for all other media types. + **kwargs (dict): Dictionary of URL parameters. """ if base is None: base = 'https://app.plex.tv/desktop/' - params = {'source': 'playlists'} - if tab is not None: - params['pivot'] = 'playlists.%s' % tab + if endpoint: + return '%s#!/server/%s/%s%s' % ( + base, self.machineIdentifier, endpoint, utils.joinArgs(kwargs) + ) + else: + return '%s#!/media/%s/com.plexapp.plugins.library%s' % ( + base, self.machineIdentifier, utils.joinArgs(kwargs) + ) - return '%s#!/media/%s/com.plexapp.plugins.library%s' % ( - base, self._server.machineIdentifier, utils.joinArgs(params) - ) + def getWebURL(self, base=None, playlistTab=None): + """ Returns the Plex Web URL for the server. + + Parameters: + base (str): The base URL before the fragment (``#!``). + Default is https://app.plex.tv/desktop. + playlistTab (str): The playlist tab (audio, video, photo). Only used for the playlist URL. + """ + if playlistTab is not None: + params = {'source': 'playlists', 'pivot': 'playlists.%s' % playlistTab} + else: + params = {'key': '/hubs', 'pageType': 'hub'} + return self._buildWebURL(base=base, **params) class Account(PlexObject): From 516c8ed02fad607157a5a3026820fb66c92d53da Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Sun, 26 Sep 2021 15:23:18 -0700 Subject: [PATCH 7/7] Update Plex Web URL tests --- tests/test_library.py | 2 +- tests/test_server.py | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/test_library.py b/tests/test_library.py index 3a6442ec..92205007 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -231,7 +231,7 @@ def test_library_MovieSection_PlexWebURL_hub(plex, movies): hubs = movies.hubs() hub = next(iter(hubs), None) assert hub is not None - url = hub.getWebURL() + url = hub.section().getWebURL(key=hub.key) assert url.startswith('https://app.plex.tv/desktop') assert plex.machineIdentifier in url assert 'source=%s' % movies.key in url diff --git a/tests/test_server.py b/tests/test_server.py index c0f8afd9..a6b36496 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import re import time +from urllib.parse import quote_plus import pytest from datetime import datetime @@ -514,14 +515,22 @@ def test_server_transcode_sessions(plex, requests_mock): assert utils.is_int(session.width, gte=852) -def test_server_PlaylistsPlexWebURL(plex): +def test_server_PlexWebURL(plex): + url = plex.getWebURL() + assert url.startswith('https://app.plex.tv/desktop') + assert plex.machineIdentifier in url + assert quote_plus('/hubs') in url + assert 'pageType=hub' in url + # Test a different base + base = 'https://doesnotexist.com/plex' + url = plex.getWebURL(base=base) + assert url.startswith(base) + + +def test_server_PlexWebURL_playlists(plex): tab = 'audio' - url = plex.getPlaylistsWebURL(tab=tab) + url = plex.getWebURL(playlistTab=tab) assert url.startswith('https://app.plex.tv/desktop') assert plex.machineIdentifier in url assert 'source=playlists' in url assert 'pivot=playlists.%s' % tab in url - # Test a different base - base = 'https://doesnotexist.com/plex' - url = plex.getPlaylistsWebURL(base=base) - assert url.startswith(base)