From c66554618537fd96fd020cde6d351781c3fde14e Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 22 Jun 2020 15:02:48 -0400 Subject: [PATCH 01/33] create library.Station class for stations found in music libraries --- plexapi/library.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 397f5c2ef..26f7d3d5a 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1083,6 +1083,36 @@ def __len__(self): return self.size +@utils.registerPlexObject +class Station(PlexObject): + """ Represents a single Hub (or category) in the PlexServer search. + + Attributes: + TAG (str): 'Hub' + hubIdentifier (str): Unknown. + size (int): Number of items found. + title (str): Title of this Hub. + type (str): Type of items in the Hub. + items (str): List of items in the Hub. + """ + TITLE = 'Stations' + TYPE = 'station' + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self._data = data + self.hubIdentifier = data.attrib.get('hubIdentifier') + self.size = utils.cast(int, data.attrib.get('size')) + self.title = data.attrib.get('title') + self.type = data.attrib.get('type') + self.more = data.attrib.get('more') + self.style = data.attrib.get('style') + self.items = self.findItems(data) + + def __len__(self): + return self.size + + @utils.registerPlexObject class Collections(PlexObject): From bd536db7ac2c672cdf2fdea246b6a8f853a6a867 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 22 Jun 2020 15:03:28 -0400 Subject: [PATCH 02/33] add stations method to library.MusicSection --- plexapi/library.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 26f7d3d5a..a15c3010d 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -915,6 +915,11 @@ def albums(self): key = '/library/sections/%s/albums' % self.key return self.fetchItems(key) + def stations(self): + """ Returns a list of :class:`~plexapi.audio.Album` objects in this section. """ + key = '/hubs/sections/%s?includeStations=1' % self.key + return self.fetchItems(key, cls=Station) + def searchArtists(self, **kwargs): """ Search for an artist. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ return self.search(libtype='artist', **kwargs) From ee9cb7dc85afb22205b3f1ed6f00b4be0997c0ba Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 22 Jun 2020 15:04:33 -0400 Subject: [PATCH 03/33] add hubs method to library.LibrarySection --- plexapi/library.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index a15c3010d..c37fd32aa 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -455,6 +455,12 @@ def all(self, sort=None, **kwargs): key = '/library/sections/%s/all%s' % (self.key, sortStr) return self.fetchItems(key, **kwargs) + def hubs(self): + """ Returns a list of available `:class:`~plexapi.library.Hub` for this library section. + """ + key = '/hubs/sections/%s' % self.key + return self.fetchItems(key) + def agents(self): """ Returns a list of available `:class:`~plexapi.media.Agent` for this library section. """ From c988f393a73598fa47b02ab9d6fd324d3690e61b Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 22 Jun 2020 15:27:45 -0400 Subject: [PATCH 04/33] library.Station docstring update. --- plexapi/library.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plexapi/library.py b/plexapi/library.py index c37fd32aa..7b57a409d 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1096,14 +1096,17 @@ def __len__(self): @utils.registerPlexObject class Station(PlexObject): - """ Represents a single Hub (or category) in the PlexServer search. + """ Represents the Station area in the MusicSection. Attributes: - TAG (str): 'Hub' + TITLE (str): 'Stations' + TYPE (str): 'station' hubIdentifier (str): Unknown. size (int): Number of items found. title (str): Title of this Hub. type (str): Type of items in the Hub. + more (str): Unknown. + style (str): Unknown items (str): List of items in the Hub. """ TITLE = 'Stations' From ea88be39a0871c673f3b83bfe14e8b7897d09b5a Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 22 Jun 2020 15:43:40 -0400 Subject: [PATCH 05/33] add Filter class to library referencing #209 --- plexapi/library.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 7b57a409d..17b49b26f 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1066,6 +1066,25 @@ def _loadData(self, data): self.type = data.attrib.get('type') +class Filter(PlexObject): + """ Represents a single Filter. + + Attributes: + TAG (str): 'Directory' + TYPE (str): 'filter' + """ + TAG = 'Directory' + TYPE = 'filter' + + def _loadData(self, data): + self._data = data + self.filter = data.attrib.get('filter') + self.filterType = data.attrib.get('filterType') + self.key = data.attrib.get('key') + self.title = data.attrib.get('title') + self.type = data.attrib.get('type') + + @utils.registerPlexObject class Hub(PlexObject): """ Represents a single Hub (or category) in the PlexServer search. From 6829bb2759c41670b855a997d5bbc875f7450e9a Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 22 Jun 2020 15:44:20 -0400 Subject: [PATCH 06/33] add _filter method to library.LibrarySection referencing #209 --- plexapi/library.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 17b49b26f..d323246c9 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -461,6 +461,11 @@ def hubs(self): key = '/hubs/sections/%s' % self.key return self.fetchItems(key) + def _filters(self): + """ Returns a list of :class:`~plexapi.library.Filter` from this library section. """ + key = '/library/sections/%s/filters' % self.key + return self.fetchItems(key, cls=Filter) + def agents(self): """ Returns a list of available `:class:`~plexapi.media.Agent` for this library section. """ From 3746b4ecb58a8e555ab852514609ae4ad8e3ee8b Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 29 Jun 2020 13:39:48 -0400 Subject: [PATCH 07/33] removal of registerPlexObject as the only usage calls this class directly. --- plexapi/media.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plexapi/media.py b/plexapi/media.py index 65895c3e3..d0ab94423 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -715,7 +715,6 @@ def _loadData(self, data): self.end = cast(int, data.attrib.get('endTimeOffset')) -@utils.registerPlexObject class Field(PlexObject): """ Represents a single Field. From 847d21f79dcc7d181511d26c7497eee0ee2239c2 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 08:59:30 -0400 Subject: [PATCH 08/33] add FirstCharacter class --- plexapi/library.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index d323246c9..1b0f71136 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1151,6 +1151,15 @@ def __len__(self): return self.size +class FirstCharacter(PlexObject): + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self._data = data + self.key = data.attrib.get('key') + self.size = data.attrib.get('size') + self.title = data.attrib.get('title') + + @utils.registerPlexObject class Collections(PlexObject): From c1cef679cc6da7abe6fa48f00fd295c535a5f5c2 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:00:50 -0400 Subject: [PATCH 09/33] add firstCharacter method to library.LibrarySection --- plexapi/library.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 1b0f71136..d85ff1020 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -490,6 +490,10 @@ def recentlyAdded(self, maxresults=50): """ return self.search(sort='addedAt:desc', maxresults=maxresults) + def firstCharacter(self): + key = '/library/sections/%s/firstCharacter' % self.key + return self.fetchItems(key, cls=FirstCharacter) + def analyze(self): """ Run an analysis on all of the items in this library section. See See :func:`~plexapi.base.PlexPartialObject.analyze` for more details. From e5d79c751812b4c64c0e0ffa5156e56bc2c2917d Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:06:26 -0400 Subject: [PATCH 10/33] add Sort class --- plexapi/library.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index d85ff1020..76cbd6029 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1155,6 +1155,21 @@ def __len__(self): return self.size +class Sort(PlexObject): + """ Represents a + + """ + TAG = 'Sort' + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self._data = data + self.defaultDirection = data.attrib.get('defaultDirection') + self.descKey = data.attrib.get('descKey') + self.key = data.attrib.get('key') + self.title = data.attrib.get('title') + + class FirstCharacter(PlexObject): def _loadData(self, data): """ Load attribute values from Plex XML response. """ From 4a49ca97b0fbc914cbf20b1095ea30e403d5edeb Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:19:53 -0400 Subject: [PATCH 11/33] add _sorts method --- plexapi/library.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 76cbd6029..fa4a7bafb 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -466,6 +466,14 @@ def _filters(self): key = '/library/sections/%s/filters' % self.key return self.fetchItems(key, cls=Filter) + def _sorts(self, mediaType=None): + items = [] + for data in self.listChoices('sorts', mediaType): + sort = Sort(server=self._server, data=data._data) + sort._initpath = data._initpath + items.append(sort) + return items + def agents(self): """ Returns a list of available `:class:`~plexapi.media.Agent` for this library section. """ From d0943a369e138f72f80bcafb6557f6d6dbdb0bda Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:21:44 -0400 Subject: [PATCH 12/33] add firstCharacterKey attribute to library.Sort class --- plexapi/library.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plexapi/library.py b/plexapi/library.py index fa4a7bafb..0dcf05bee 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1176,6 +1176,7 @@ def _loadData(self, data): self.descKey = data.attrib.get('descKey') self.key = data.attrib.get('key') self.title = data.attrib.get('title') + self.firstCharacterKey = data.attrib.get('firstCharacterKey') class FirstCharacter(PlexObject): From 9936ee553d48dc66d5272e72a475687b58d1872b Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:23:18 -0400 Subject: [PATCH 13/33] create Field, Operator, and FieldType classes --- plexapi/library.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 0dcf05bee..01280846b 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1179,6 +1179,53 @@ def _loadData(self, data): self.firstCharacterKey = data.attrib.get('firstCharacterKey') +class Field(PlexObject): + """ Represents a + + """ + TAG = 'Field' + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self._data = data + self.key = data.attrib.get('key') + self.title = data.attrib.get('title') + self.type = data.attrib.get('type') + self.subType = data.attrib.get('subType') + self.operators = [] + + +@utils.registerPlexObject +class Operator(PlexObject): + """ Represents a + + """ + TAG = 'Operator' + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self.key = data.attrib.get('key') + self.title = data.attrib.get('title') + + +@utils.registerPlexObject +class FieldType(PlexObject): + """ Represents a + + """ + TAG = 'FieldType' + + def __repr__(self): + _type = self._clean(self.firstAttr('type')) + return '<%s>' % ':'.join([p for p in [self.__class__.__name__, _type] if p]) + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self._data = data + self.type = data.attrib.get('type') + self.operators = self.findItems(data, Operator) + + class FirstCharacter(PlexObject): def _loadData(self, data): """ Load attribute values from Plex XML response. """ From 9ec2399b21a0150a68a116c493073b4e3cf131d3 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:24:12 -0400 Subject: [PATCH 14/33] add filterFields method to library.LibrarySection --- plexapi/library.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 01280846b..3d9d8ac07 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -474,6 +474,30 @@ def _sorts(self, mediaType=None): items.append(sort) return items + def filterFields(self, mediaType=None): + items = [] + key = '/library/sections/%s/filters?includeMeta=1' % self.key + data = self._server.query(key) + for meta in data.iter('Meta'): + for metaType in meta.iter('Type'): + if mediaType and metaType.attrib.get('type') == mediaType: + fields = self.findItems(metaType, Field) + for field in fields: + field._initpath = metaType.attrib.get('key') + fieldType = [_ for _ in self.findItems(meta, FieldType) if _.type == field.type] + field.operators = fieldType[0].operators + items += fields + elif not mediaType: + fields = self.findItems(metaType, Field) + for field in fields: + field._initpath = metaType.attrib.get('key') + fieldType = [_ for _ in self.findItems(meta, FieldType) if _.type == field.type] + field.operators = fieldType[0].operators + items += fields + if not items and mediaType: + raise BadRequest('mediaType (%s) not found.' % mediaType) + return items + def agents(self): """ Returns a list of available `:class:`~plexapi.media.Agent` for this library section. """ From a665d6f3eedda41fcb22776cb1704d1baa2718a2 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:32:58 -0400 Subject: [PATCH 15/33] update library.LibrarySection._cleanSearchFilter method to pull categories from LibrarySection.filterFields() instead of using hardcoded ALLOWED_FILTERS or BOOLEAN_FILTERS. --- plexapi/library.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plexapi/library.py b/plexapi/library.py index 3d9d8ac07..c9fc2b593 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -668,12 +668,14 @@ def search(self, title=None, sort=None, maxresults=None, def _cleanSearchFilter(self, category, value, libtype=None): # check a few things before we begin + categories = [x.key for x in self.filterFields()] + booleanFilters = [x.key for x in self.filterFields() if x.type == 'boolean'] if category.endswith('!'): - if category[:-1] not in self.ALLOWED_FILTERS: + if category[:-1] not in categories: raise BadRequest('Unknown filter category: %s' % category[:-1]) - elif category not in self.ALLOWED_FILTERS: + elif category not in categories: raise BadRequest('Unknown filter category: %s' % category) - if category in self.BOOLEAN_FILTERS: + if category in booleanFilters: return '1' if value else '0' if not isinstance(value, (list, tuple)): value = [value] From 14c122c87b8796368db91872c0b4ba36df8fd139 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:33:53 -0400 Subject: [PATCH 16/33] update library.LibrarySection._cleanSearchSort method to pull sort keys from LibrarySection._sorts() instead of using hardcoded ALLOWED_SORT. --- plexapi/library.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plexapi/library.py b/plexapi/library.py index c9fc2b593..cef531f09 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -699,7 +699,8 @@ def _cleanSearchFilter(self, category, value, libtype=None): def _cleanSearchSort(self, sort): sort = '%s:asc' % sort if ':' not in sort else sort scol, sdir = sort.lower().split(':') - lookup = {s.lower(): s for s in self.ALLOWED_SORT} + allowedSort = [sort.key for sort in self._sorts()] + lookup = {s.lower(): s for s in allowedSort} if scol not in lookup: raise BadRequest('Unknown sort column: %s' % scol) if sdir not in ('asc', 'desc'): From ea6538f596b38fc6b96fec1758d9227cd5bcb91e Mon Sep 17 00:00:00 2001 From: blacktwin Date: Tue, 30 Jun 2020 09:39:16 -0400 Subject: [PATCH 17/33] removal of all ALLOWED_FILTERS, ALLOWED_SORT, BOOLEAN_FILTERS instances and docstring references. --- plexapi/library.py | 48 +--------------------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/plexapi/library.py b/plexapi/library.py index cef531f09..f9061bd14 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -312,9 +312,6 @@ class LibrarySection(PlexObject): """ Base class for a single library section. Attributes: - ALLOWED_FILTERS (tuple): () - ALLOWED_SORT (tuple): () - BOOLEAN_FILTERS (tuple): ('unwatched', 'duplicate') server (:class:`~plexapi.server.PlexServer`): Server this client is connected to. initpath (str): Path requested when building this object. agent (str): Unknown (com.plexapp.agents.imdb, etc) @@ -336,9 +333,6 @@ class LibrarySection(PlexObject): totalSize (int): Total number of item in the library """ - ALLOWED_FILTERS = () - ALLOWED_SORT = () - BOOLEAN_FILTERS = ('unwatched', 'duplicate') def _loadData(self, data): self._data = data @@ -796,21 +790,9 @@ class MovieSection(LibrarySection): """ Represents a :class:`~plexapi.library.LibrarySection` section containing movies. Attributes: - ALLOWED_FILTERS (list): List of allowed search filters. ('unwatched', - 'duplicate', 'year', 'decade', 'genre', 'contentRating', 'collection', - 'director', 'actor', 'country', 'studio', 'resolution', 'guid', 'label') - ALLOWED_SORT (list): List of allowed sorting keys. ('addedAt', - 'originallyAvailableAt', 'lastViewedAt', 'titleSort', 'rating', - 'mediaHeight', 'duration') TAG (str): 'Directory' TYPE (str): 'movie' """ - ALLOWED_FILTERS = ('unwatched', 'duplicate', 'year', 'decade', 'genre', 'contentRating', - 'collection', 'director', 'actor', 'country', 'studio', 'resolution', - 'guid', 'label', 'writer', 'producer', 'subtitleLanguage', 'audioLanguage', - 'lastViewedAt', 'viewCount', 'addedAt') - ALLOWED_SORT = ('addedAt', 'originallyAvailableAt', 'lastViewedAt', 'titleSort', 'rating', - 'mediaHeight', 'duration') TAG = 'Directory' TYPE = 'movie' METADATA_TYPE = 'movie' @@ -860,21 +842,10 @@ class ShowSection(LibrarySection): """ Represents a :class:`~plexapi.library.LibrarySection` section containing tv shows. Attributes: - ALLOWED_FILTERS (list): List of allowed search filters. ('unwatched', - 'year', 'genre', 'contentRating', 'network', 'collection', 'guid', 'label') - ALLOWED_SORT (list): List of allowed sorting keys. ('addedAt', 'lastViewedAt', - 'originallyAvailableAt', 'titleSort', 'rating', 'unwatched') TAG (str): 'Directory' TYPE (str): 'show' """ - ALLOWED_FILTERS = ('unwatched', 'year', 'genre', 'contentRating', 'network', 'collection', - 'guid', 'duplicate', 'label', 'show.title', 'show.year', 'show.userRating', - 'show.viewCount', 'show.lastViewedAt', 'show.actor', 'show.addedAt', 'episode.title', - 'episode.originallyAvailableAt', 'episode.resolution', 'episode.subtitleLanguage', - 'episode.unwatched', 'episode.addedAt', 'episode.userRating', 'episode.viewCount', - 'episode.lastViewedAt') - ALLOWED_SORT = ('addedAt', 'lastViewedAt', 'originallyAvailableAt', 'titleSort', - 'rating', 'unwatched') + TAG = 'Directory' TYPE = 'show' METADATA_TYPE = 'episode' @@ -940,20 +911,9 @@ class MusicSection(LibrarySection): """ Represents a :class:`~plexapi.library.LibrarySection` section containing music artists. Attributes: - ALLOWED_FILTERS (list): List of allowed search filters. ('genre', - 'country', 'collection') - ALLOWED_SORT (list): List of allowed sorting keys. ('addedAt', - 'lastViewedAt', 'viewCount', 'titleSort') TAG (str): 'Directory' TYPE (str): 'artist' """ - ALLOWED_FILTERS = ('genre', 'country', 'collection', 'mood', 'year', 'track.userRating', 'artist.title', - 'artist.userRating', 'artist.genre', 'artist.country', 'artist.collection', 'artist.addedAt', - 'album.title', 'album.userRating', 'album.genre', 'album.decade', 'album.collection', - 'album.viewCount', 'album.lastViewedAt', 'album.studio', 'album.addedAt', 'track.title', - 'track.userRating', 'track.viewCount', 'track.lastViewedAt', 'track.skipCount', - 'track.lastSkippedAt') - ALLOWED_SORT = ('addedAt', 'lastViewedAt', 'viewCount', 'titleSort', 'userRating') TAG = 'Directory' TYPE = 'artist' @@ -1025,15 +985,9 @@ class PhotoSection(LibrarySection): """ Represents a :class:`~plexapi.library.LibrarySection` section containing photos. Attributes: - ALLOWED_FILTERS (list): List of allowed search filters. ('all', 'iso', - 'make', 'lens', 'aperture', 'exposure', 'device', 'resolution') - ALLOWED_SORT (list): List of allowed sorting keys. ('addedAt') TAG (str): 'Directory' TYPE (str): 'photo' """ - ALLOWED_FILTERS = ('all', 'iso', 'make', 'lens', 'aperture', 'exposure', 'device', 'resolution', 'place', - 'originallyAvailableAt', 'addedAt', 'title', 'userRating', 'tag', 'year') - ALLOWED_SORT = ('addedAt',) TAG = 'Directory' TYPE = 'photo' CONTENT_TYPE = 'photo' From 23e641eaa38324a608030c5622d379e9cb5614b0 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Wed, 1 Jul 2020 22:28:25 -0400 Subject: [PATCH 18/33] change if to check both conditions at the same time. thanks @hellowlol --- plexapi/library.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plexapi/library.py b/plexapi/library.py index f9061bd14..11f05d7f0 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -474,14 +474,7 @@ def filterFields(self, mediaType=None): data = self._server.query(key) for meta in data.iter('Meta'): for metaType in meta.iter('Type'): - if mediaType and metaType.attrib.get('type') == mediaType: - fields = self.findItems(metaType, Field) - for field in fields: - field._initpath = metaType.attrib.get('key') - fieldType = [_ for _ in self.findItems(meta, FieldType) if _.type == field.type] - field.operators = fieldType[0].operators - items += fields - elif not mediaType: + if not mediaType or metaType.attrib.get('type') == mediaType: fields = self.findItems(metaType, Field) for field in fields: field._initpath = metaType.attrib.get('key') From 257b7ae61e27b382c1fe3250e60512826634254b Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:36:46 -0400 Subject: [PATCH 19/33] library.LibrarySection._sorts() docstring --- plexapi/library.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 11f05d7f0..dc9f89263 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -461,6 +461,8 @@ def _filters(self): return self.fetchItems(key, cls=Filter) def _sorts(self, mediaType=None): + """ Returns a list of available `:class:`~plexapi.library.Sort` for this library section. + """ items = [] for data in self.listChoices('sorts', mediaType): sort = Sort(server=self._server, data=data._data) From 8eadcb28aab457dc2cdebafb4bc5cca151bde37d Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:37:50 -0400 Subject: [PATCH 20/33] library.LibrarySection.filterFields() docstring --- plexapi/library.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index dc9f89263..b27bec0ea 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -471,6 +471,8 @@ def _sorts(self, mediaType=None): return items def filterFields(self, mediaType=None): + """ Returns a list of available `:class:`~plexapi.library.FilterField` for this library section. + """ items = [] key = '/library/sections/%s/filters?includeMeta=1' % self.key data = self._server.query(key) From 1e58e9c5fd365a3c52f6145e2991d1f3db18ee13 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:39:34 -0400 Subject: [PATCH 21/33] library.Field class rename to FilterField update library.LibrarySection.filterFields() usage --- plexapi/library.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plexapi/library.py b/plexapi/library.py index b27bec0ea..7f21c2b53 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -479,7 +479,7 @@ def filterFields(self, mediaType=None): for meta in data.iter('Meta'): for metaType in meta.iter('Type'): if not mediaType or metaType.attrib.get('type') == mediaType: - fields = self.findItems(metaType, Field) + fields = self.findItems(metaType, FilterField) for field in fields: field._initpath = metaType.attrib.get('key') fieldType = [_ for _ in self.findItems(meta, FieldType) if _.type == field.type] @@ -1157,8 +1157,8 @@ def _loadData(self, data): self.firstCharacterKey = data.attrib.get('firstCharacterKey') -class Field(PlexObject): - """ Represents a +class FilterField(PlexObject): + """ Represents a Filters Field element found in library. """ TAG = 'Field' From bf5d8b8ba37e6e2288573a93dd0d0355427de59d Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:40:12 -0400 Subject: [PATCH 22/33] library.Sort docstring --- plexapi/library.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plexapi/library.py b/plexapi/library.py index 7f21c2b53..ce55ba90c 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1142,8 +1142,15 @@ def __len__(self): class Sort(PlexObject): - """ Represents a + """ Represents a Sort element found in library. + Attributes: + TAG (str): 'Sort' + defaultDirection (str): Default sorting direction. + descKey (str): Url key for sorting with desc. + key (str): Url key for sorting, + title (str): Title of sorting, + firstCharacterKey (str): Url path for first character endpoint. """ TAG = 'Sort' From 8aa492f19545f3210c3bad14601d4228854c76ad Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:40:51 -0400 Subject: [PATCH 23/33] library.FilterField docstring --- plexapi/library.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index ce55ba90c..193111814 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1167,6 +1167,13 @@ def _loadData(self, data): class FilterField(PlexObject): """ Represents a Filters Field element found in library. + Attributes: + TAG (str): 'Field' + key (str): Url key for filter, + title (str): Title of filter. + type (str): Type of filter (string, boolean, integer, date, etc). + subType (str): Subtype of filter (decade, rating, etc). + operators (str): Operators available for this filter. """ TAG = 'Field' From 6cad8c1eb0c99e85a9e594330d1eed4099d9ec50 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:42:28 -0400 Subject: [PATCH 24/33] library.Operator docstring --- plexapi/library.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plexapi/library.py b/plexapi/library.py index 193111814..8f4a2ec42 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1189,8 +1189,12 @@ def _loadData(self, data): @utils.registerPlexObject class Operator(PlexObject): - """ Represents a + """ Represents an Operator available for filter. + Attributes: + TAG (str): 'Operator' + key (str): Url key for operator. + title (str): Title of operator. """ TAG = 'Operator' From 5c3695d223169b1d730fef8fbf78402b22433d1b Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:42:51 -0400 Subject: [PATCH 25/33] library.FieldType docstring --- plexapi/library.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plexapi/library.py b/plexapi/library.py index 8f4a2ec42..6ca16a00a 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1206,8 +1206,12 @@ def _loadData(self, data): @utils.registerPlexObject class FieldType(PlexObject): - """ Represents a + """ Represents a FieldType for filter. + Attributes: + TAG (str): 'Operator' + type (str): Type of filter (string, boolean, integer, date, etc), + operators (str): Operators available for this filter. """ TAG = 'FieldType' From f079f7d06133226c4aee73693208b6d67286f8ad Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 00:43:21 -0400 Subject: [PATCH 26/33] library.FirstCharacter docstring --- plexapi/library.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 6ca16a00a..42b7a5716 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1227,6 +1227,13 @@ def _loadData(self, data): class FirstCharacter(PlexObject): + """ Represents a First Character element from a library. + + Attributes: + key (str): Url key for character. + size (str): Total amount of library items starting with this character. + title (str): Character (#, !, A, B, C, ...). + """ def _loadData(self, data): """ Load attribute values from Plex XML response. """ self._data = data From a5b5438122158f89f5bffb7aeefd5e308c1b6d8c Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 01:33:18 -0400 Subject: [PATCH 27/33] create library.Folder class --- plexapi/library.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 42b7a5716..905e4d83f 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1204,6 +1204,29 @@ def _loadData(self, data): self.title = data.attrib.get('title') +class Folder(PlexObject): + """ Represents a Folder inside a library. + + Attributes: + key (str): Url key for folder. + title (str): Title of folder. + """ + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + self.key = data.attrib.get('key') + self.title = data.attrib.get('title') + + def subfolders(self): + """ Returns a list of available `:class:`~plexapi.library.Folder` for this folder. + Continue down subfolders until a mediaType is found. + """ + if self.key.startswith('/library/metadata'): + return self.fetchItems(self.key) + else: + return self.fetchItems(self.key, Folder) + + @utils.registerPlexObject class FieldType(PlexObject): """ Represents a FieldType for filter. From 2112f778dbe18d705548296ad07577fb6e1962b2 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 01:33:36 -0400 Subject: [PATCH 28/33] create library.folders method --- plexapi/library.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index 905e4d83f..e38d8972b 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -449,6 +449,12 @@ def all(self, sort=None, **kwargs): key = '/library/sections/%s/all%s' % (self.key, sortStr) return self.fetchItems(key, **kwargs) + def folders(self): + """ Returns a list of available `:class:`~plexapi.library.Folder` for this library section. + """ + key = '/library/sections/%s/folder' % self.key + return self.fetchItems(key, Folder) + def hubs(self): """ Returns a list of available `:class:`~plexapi.library.Hub` for this library section. """ From 83c4e64ed63ebec8e2110f8803211779bca162ce Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 2 Jul 2020 14:40:28 -0400 Subject: [PATCH 29/33] add library.Collection._preferences() method --- plexapi/library.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index e38d8972b..b558a8d4b 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1303,6 +1303,15 @@ def children(self): def __len__(self): return self.childCount + def _preferences(self): + """ Returns a list of :class:`~plexapi.settings.Preferences` objects. """ + items = [] + data = self._server.query(self._details_key) + for item in data.iter('Setting'): + items.append(Setting(data=item, server=self._server)) + + return items + def delete(self): part = '/library/metadata/%s' % self.ratingKey return self._server.query(part, method=self._server._session.delete) From 614108fb63c3cad1b3172df71dcab865bf552f29 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Thu, 30 Jul 2020 10:33:58 -0400 Subject: [PATCH 30/33] create allSubfolders method for library.Folder class --- plexapi/library.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plexapi/library.py b/plexapi/library.py index b558a8d4b..a1669bfb8 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1232,6 +1232,22 @@ def subfolders(self): else: return self.fetchItems(self.key, Folder) + def allSubfolders(self): + """ Returns a list of all available `:class:`~plexapi.library.Folder` for this folder. + Only returns `:class:`~plexapi.library.Folder`. + """ + folders =[] + for folder in self.subfolders(): + if not folder.key.startswith('/library/metadata'): + folders.append(folder) + while True: + for subfolder in folder.subfolders(): + if not subfolder.key.startswith('/library/metadata'): + folders.append(subfolder) + continue + break + return folders + @utils.registerPlexObject class FieldType(PlexObject): From c6f60d231c1d4b4c4b90a3e34159f14e694d949a Mon Sep 17 00:00:00 2001 From: blacktwin Date: Wed, 9 Sep 2020 15:23:56 -0400 Subject: [PATCH 31/33] flake fix --- plexapi/library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/library.py b/plexapi/library.py index fd0b577e5..856153798 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1258,7 +1258,7 @@ def allSubfolders(self): """ Returns a list of all available `:class:`~plexapi.library.Folder` for this folder. Only returns `:class:`~plexapi.library.Folder`. """ - folders =[] + folders = [] for folder in self.subfolders(): if not folder.key.startswith('/library/metadata'): folders.append(folder) From 58a432f176fbbc385c926ca3d3e27e94a8ccec2a Mon Sep 17 00:00:00 2001 From: blacktwin Date: Wed, 9 Sep 2020 15:25:38 -0400 Subject: [PATCH 32/33] flake fix --- plexapi/library.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plexapi/library.py b/plexapi/library.py index 856153798..61a75cc87 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1071,6 +1071,7 @@ def _loadData(self, data): self.title = data.attrib.get('title') self.type = data.attrib.get('type') + @utils.registerPlexObject class Location(PlexObject): """ Represents a single library Location. From 4f96838b3d4f3b7e56fee3a00e8266c4b41a3085 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Mon, 28 Sep 2020 13:51:25 -0400 Subject: [PATCH 33/33] update sort field in library.ShowSection.recentlyAdded method --- plexapi/library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/library.py b/plexapi/library.py index 7b9bd95e7..5376e6438 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -879,7 +879,7 @@ def recentlyAdded(self, libtype='episode', maxresults=50): Parameters: maxresults (int): Max number of items to return (default 50). """ - return self.search(sort='addedAt:desc', libtype=libtype, maxresults=maxresults) + return self.search(sort='episode.addedAt:desc', libtype=libtype, maxresults=maxresults) def collection(self, **kwargs): """ Returns a list of collections from this library section. """