mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 11:43:13 +00:00
Merge pull request #810 from JonnyWong16/feature/web_url
Add methods to retrieve the Plex Web URL
This commit is contained in:
commit
3fce24587d
13 changed files with 247 additions and 0 deletions
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in a new issue