Merge pull request #810 from JonnyWong16/feature/web_url

Add methods to retrieve the Plex Web URL
This commit is contained in:
JonnyWong16 2021-10-04 12:38:24 -07:00 committed by GitHub
commit 3fce24587d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 247 additions and 0 deletions

View file

@ -422,3 +422,7 @@ 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):
""" Get the Plex Web URL with the correct parameters. """
return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey)

View file

@ -579,12 +579,28 @@ 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 _getWebURL(self, base=None):
""" Get the Plex Web URL with the correct parameters.
Private method to allow overriding parameters from subclasses.
"""
return self._server._buildWebURL(base=base, endpoint='details', key=self.key)
def getWebURL(self, base=None):
""" 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._getWebURL(base=base)
class Playable(object):
""" This is a general place to store functions specific to media that is Playable.

View file

@ -1456,6 +1456,23 @@ 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.
"""
params = {'source': self.key}
if tab is not None:
params['pivot'] = tab
if key is not None:
params['key'] = key
params['pageType'] = 'list'
return self._server._buildWebURL(base=base, **params)
class MovieSection(LibrarySection):
""" Represents a :class:`~plexapi.library.LibrarySection` section containing movies.
@ -1857,6 +1874,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 +1886,13 @@ 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
class HubMediaTag(PlexObject):
""" Base class of hub media tag search results.

View file

@ -137,6 +137,10 @@ class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin, RatingMixin):
filepaths.append(filepath)
return filepaths
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
class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, TagMixin):
@ -301,3 +305,7 @@ class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixi
if filepath:
filepaths.append(filepath)
return filepaths
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)

View file

@ -464,3 +464,7 @@ 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):
""" Get the Plex Web URL with the correct parameters. """
return self._server._buildWebURL(base=base, endpoint='playlist', key=self.key)

View file

@ -890,6 +890,42 @@ class PlexServer(PlexObject):
key = '/statistics/resources?timespan=6'
return self.fetchItems(key, StatisticsResources)
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.
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/'
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)
)
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):
""" Contains the locally cached MyPlex account information. The properties provided don't

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from urllib.parse import quote_plus
from . import conftest as utils
from . import test_media, test_mixins
@ -105,6 +107,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:
@ -200,6 +210,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)
@ -341,6 +359,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()

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from urllib.parse import quote_plus
import pytest
from plexapi.exceptions import BadRequest, NotFound
@ -285,3 +287,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

View file

@ -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.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
assert quote_plus(hub.key) in url
def test_library_ShowSection_all(tvshows):
assert len(tvshows.all(title__iexact="The 100"))

View file

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from urllib.parse import quote_plus
from . import test_media, test_mixins
@ -24,6 +26,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)
@ -35,3 +46,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

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import time
from urllib.parse import quote_plus
import pytest
from plexapi.exceptions import BadRequest, NotFound, Unsupported
@ -258,6 +259,20 @@ def test_Playlist_exceptions(plex, movies, movie, artist):
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()
def test_Playlist_mixins_images(playlist):
# test_mixins.lock_art(playlist)
test_mixins.lock_poster(playlist)

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import re
import time
from urllib.parse import quote_plus
import pytest
from datetime import datetime
@ -512,3 +513,24 @@ 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_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.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

View file

@ -610,6 +610,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)
@ -805,6 +816,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
@ -918,6 +937,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.
@ -1127,6 +1154,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]