Skip to content

Commit 3fce245

Browse files
authored
Merge pull request #810 from JonnyWong16/feature/web_url
Add methods to retrieve the Plex Web URL
2 parents 26648f6 + 516c8ed commit 3fce245

File tree

13 files changed

+247
-0
lines changed

13 files changed

+247
-0
lines changed

plexapi/audio.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,7 @@ def trackNumber(self):
422422
def _defaultSyncTitle(self):
423423
""" Returns str, default title for a new syncItem. """
424424
return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title)
425+
426+
def _getWebURL(self, base=None):
427+
""" Get the Plex Web URL with the correct parameters. """
428+
return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey)

plexapi/base.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,12 +579,28 @@ def delete(self):
579579

580580
def history(self, maxresults=9999999, mindate=None):
581581
""" Get Play History for a media item.
582+
582583
Parameters:
583584
maxresults (int): Only return the specified number of results (optional).
584585
mindate (datetime): Min datetime to return results from.
585586
"""
586587
return self._server.history(maxresults=maxresults, mindate=mindate, ratingKey=self.ratingKey)
587588

589+
def _getWebURL(self, base=None):
590+
""" Get the Plex Web URL with the correct parameters.
591+
Private method to allow overriding parameters from subclasses.
592+
"""
593+
return self._server._buildWebURL(base=base, endpoint='details', key=self.key)
594+
595+
def getWebURL(self, base=None):
596+
""" Returns the Plex Web URL for a media item.
597+
598+
Parameters:
599+
base (str): The base URL before the fragment (``#!``).
600+
Default is https://app.plex.tv/desktop.
601+
"""
602+
return self._getWebURL(base=base)
603+
588604

589605
class Playable(object):
590606
""" This is a general place to store functions specific to media that is Playable.

plexapi/library.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,23 @@ def filterFields(self, mediaType=None):
14561456
def listChoices(self, category, libtype=None, **kwargs):
14571457
return self.listFilterChoices(field=category, libtype=libtype)
14581458

1459+
def getWebURL(self, base=None, tab=None, key=None):
1460+
""" Returns the Plex Web URL for the library.
1461+
1462+
Parameters:
1463+
base (str): The base URL before the fragment (``#!``).
1464+
Default is https://app.plex.tv/desktop.
1465+
tab (str): The library tab (recommended, library, collections, playlists, timeline).
1466+
key (str): A hub key.
1467+
"""
1468+
params = {'source': self.key}
1469+
if tab is not None:
1470+
params['pivot'] = tab
1471+
if key is not None:
1472+
params['key'] = key
1473+
params['pageType'] = 'list'
1474+
return self._server._buildWebURL(base=base, **params)
1475+
14591476

14601477
class MovieSection(LibrarySection):
14611478
""" Represents a :class:`~plexapi.library.LibrarySection` section containing movies.
@@ -1857,6 +1874,7 @@ def _loadData(self, data):
18571874
self.style = data.attrib.get('style')
18581875
self.title = data.attrib.get('title')
18591876
self.type = data.attrib.get('type')
1877+
self._section = None # cache for self.section
18601878

18611879
def __len__(self):
18621880
return self.size
@@ -1868,6 +1886,13 @@ def reload(self):
18681886
self.more = False
18691887
self.size = len(self.items)
18701888

1889+
def section(self):
1890+
""" Returns the :class:`~plexapi.library.LibrarySection` this hub belongs to.
1891+
"""
1892+
if self._section is None:
1893+
self._section = self._server.library.sectionByID(self.librarySectionID)
1894+
return self._section
1895+
18711896

18721897
class HubMediaTag(PlexObject):
18731898
""" Base class of hub media tag search results.

plexapi/photo.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ def download(self, savepath=None, keep_original_name=False, showstatus=False):
137137
filepaths.append(filepath)
138138
return filepaths
139139

140+
def _getWebURL(self, base=None):
141+
""" Get the Plex Web URL with the correct parameters. """
142+
return self._server._buildWebURL(base=base, endpoint='details', key=self.key, legacy=1)
143+
140144

141145
@utils.registerPlexObject
142146
class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, TagMixin):
@@ -301,3 +305,7 @@ def download(self, savepath=None, keep_original_name=False, showstatus=False):
301305
if filepath:
302306
filepaths.append(filepath)
303307
return filepaths
308+
309+
def _getWebURL(self, base=None):
310+
""" Get the Plex Web URL with the correct parameters. """
311+
return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey, legacy=1)

plexapi/playlist.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,3 +464,7 @@ def sync(self, videoQuality=None, photoResolution=None, audioBitrate=None, clien
464464
raise Unsupported('Unsupported playlist content')
465465

466466
return myplex.sync(sync_item, client=client, clientId=clientId)
467+
468+
def _getWebURL(self, base=None):
469+
""" Get the Plex Web URL with the correct parameters. """
470+
return self._server._buildWebURL(base=base, endpoint='playlist', key=self.key)

plexapi/server.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,42 @@ def resources(self):
890890
key = '/statistics/resources?timespan=6'
891891
return self.fetchItems(key, StatisticsResources)
892892

893+
def _buildWebURL(self, base=None, endpoint=None, **kwargs):
894+
""" Build the Plex Web URL for the object.
895+
896+
Parameters:
897+
base (str): The base URL before the fragment (``#!``).
898+
Default is https://app.plex.tv/desktop.
899+
endpoint (str): The Plex Web URL endpoint.
900+
None for server, 'playlist' for playlists, 'details' for all other media types.
901+
**kwargs (dict): Dictionary of URL parameters.
902+
"""
903+
if base is None:
904+
base = 'https://app.plex.tv/desktop/'
905+
906+
if endpoint:
907+
return '%s#!/server/%s/%s%s' % (
908+
base, self.machineIdentifier, endpoint, utils.joinArgs(kwargs)
909+
)
910+
else:
911+
return '%s#!/media/%s/com.plexapp.plugins.library%s' % (
912+
base, self.machineIdentifier, utils.joinArgs(kwargs)
913+
)
914+
915+
def getWebURL(self, base=None, playlistTab=None):
916+
""" Returns the Plex Web URL for the server.
917+
918+
Parameters:
919+
base (str): The base URL before the fragment (``#!``).
920+
Default is https://app.plex.tv/desktop.
921+
playlistTab (str): The playlist tab (audio, video, photo). Only used for the playlist URL.
922+
"""
923+
if playlistTab is not None:
924+
params = {'source': 'playlists', 'pivot': 'playlists.%s' % playlistTab}
925+
else:
926+
params = {'key': '/hubs', 'pageType': 'hub'}
927+
return self._buildWebURL(base=base, **params)
928+
893929

894930
class Account(PlexObject):
895931
""" Contains the locally cached MyPlex account information. The properties provided don't

tests/test_audio.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
2+
from urllib.parse import quote_plus
3+
24
from . import conftest as utils
35
from . import test_media, test_mixins
46

@@ -105,6 +107,14 @@ def test_audio_Artist_media_tags(artist):
105107
test_media.tag_style(artist)
106108

107109

110+
def test_video_Artist_PlexWebURL(plex, artist):
111+
url = artist.getWebURL()
112+
assert url.startswith('https://app.plex.tv/desktop')
113+
assert plex.machineIdentifier in url
114+
assert 'details' in url
115+
assert quote_plus(artist.key) in url
116+
117+
108118
def test_audio_Album_attrs(album):
109119
assert utils.is_datetime(album.addedAt)
110120
if album.art:
@@ -200,6 +210,14 @@ def test_audio_Album_media_tags(album):
200210
test_media.tag_style(album)
201211

202212

213+
def test_video_Album_PlexWebURL(plex, album):
214+
url = album.getWebURL()
215+
assert url.startswith('https://app.plex.tv/desktop')
216+
assert plex.machineIdentifier in url
217+
assert 'details' in url
218+
assert quote_plus(album.key) in url
219+
220+
203221
def test_audio_Track_attrs(album):
204222
track = album.get("As Colourful As Ever").reload()
205223
assert utils.is_datetime(track.addedAt)
@@ -341,6 +359,14 @@ def test_audio_Track_media_tags(track):
341359
test_media.tag_mood(track)
342360

343361

362+
def test_video_Track_PlexWebURL(plex, track):
363+
url = track.getWebURL()
364+
assert url.startswith('https://app.plex.tv/desktop')
365+
assert plex.machineIdentifier in url
366+
assert 'details' in url
367+
assert quote_plus(track.parentKey) in url
368+
369+
344370
def test_audio_Audio_section(artist, album, track):
345371
assert artist.section()
346372
assert album.section()

tests/test_collection.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
2+
from urllib.parse import quote_plus
3+
24
import pytest
35
from plexapi.exceptions import BadRequest, NotFound
46

@@ -285,3 +287,11 @@ def test_Collection_mixins_rating(collection):
285287

286288
def test_Collection_mixins_tags(collection):
287289
test_mixins.edit_label(collection)
290+
291+
292+
def test_Collection_PlexWebURL(plex, collection):
293+
url = collection.getWebURL()
294+
assert url.startswith('https://app.plex.tv/desktop')
295+
assert plex.machineIdentifier in url
296+
assert 'details' in url
297+
assert quote_plus(collection.key) in url

tests/test_library.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# -*- coding: utf-8 -*-
22
from collections import namedtuple
33
from datetime import datetime, timedelta
4+
from urllib.parse import quote_plus
5+
46
import pytest
57
from plexapi.exceptions import BadRequest, NotFound
68

@@ -212,6 +214,30 @@ def test_library_MovieSection_collection_exception(movies):
212214
movies.collection("Does Not Exists")
213215

214216

217+
def test_library_MovieSection_PlexWebURL(plex, movies):
218+
tab = 'library'
219+
url = movies.getWebURL(tab=tab)
220+
assert url.startswith('https://app.plex.tv/desktop')
221+
assert plex.machineIdentifier in url
222+
assert 'source=%s' % movies.key in url
223+
assert 'pivot=%s' % tab in url
224+
# Test a different base
225+
base = 'https://doesnotexist.com/plex'
226+
url = movies.getWebURL(base=base)
227+
assert url.startswith(base)
228+
229+
230+
def test_library_MovieSection_PlexWebURL_hub(plex, movies):
231+
hubs = movies.hubs()
232+
hub = next(iter(hubs), None)
233+
assert hub is not None
234+
url = hub.section().getWebURL(key=hub.key)
235+
assert url.startswith('https://app.plex.tv/desktop')
236+
assert plex.machineIdentifier in url
237+
assert 'source=%s' % movies.key in url
238+
assert quote_plus(hub.key) in url
239+
240+
215241
def test_library_ShowSection_all(tvshows):
216242
assert len(tvshows.all(title__iexact="The 100"))
217243

tests/test_photo.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
2+
from urllib.parse import quote_plus
3+
24
from . import test_media, test_mixins
35

46

@@ -24,6 +26,15 @@ def test_photo_Photoalbum_mixins_rating(photoalbum):
2426
test_mixins.edit_rating(photoalbum)
2527

2628

29+
def test_video_Photoalbum_PlexWebURL(plex, photoalbum):
30+
url = photoalbum.getWebURL()
31+
assert url.startswith('https://app.plex.tv/desktop')
32+
assert plex.machineIdentifier in url
33+
assert 'details' in url
34+
assert quote_plus(photoalbum.key) in url
35+
assert 'legacy=1' in url
36+
37+
2738
def test_photo_Photo_mixins_rating(photo):
2839
test_mixins.edit_rating(photo)
2940

@@ -35,3 +46,12 @@ def test_photo_Photo_mixins_tags(photo):
3546
def test_photo_Photo_media_tags(photo):
3647
photo.reload()
3748
test_media.tag_tag(photo)
49+
50+
51+
def test_video_Photo_PlexWebURL(plex, photo):
52+
url = photo.getWebURL()
53+
assert url.startswith('https://app.plex.tv/desktop')
54+
assert plex.machineIdentifier in url
55+
assert 'details' in url
56+
assert quote_plus(photo.parentKey) in url
57+
assert 'legacy=1' in url

0 commit comments

Comments
 (0)