Source code for tidalapi.page

# -*- coding: utf-8 -*-

# Copyright (C) 2021-2022 morguldir
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
Module for parsing TIDAL's pages format found at https://listen.tidal.com/v1/pages
"""

import copy

[docs]class Page(object): """ A page from the https://listen.tidal.com/view/pages/ endpoint The :class:`categories` field will the most complete information However it is an iterable that goes through all the visible items on the page as well, in the natural reading order """ title = "" categories = None _categories_iter = None def __init__(self, session, title): self.request = session.request self.categories = None self.title = title self.page_category = PageCategory(session) def __iter__(self): self._categories_iter = iter(self.categories) self._category = next(self._categories_iter) self._items_iter = iter(self._category.items) return self def __next__(self): if self._category == StopIteration: return StopIteration try: item = next(self._items_iter) except StopIteration: self._category = next(self._categories_iter) self._items_iter = iter(self._category.items) return self.__next__() return item
[docs] def next(self): return self.__next__()
[docs] def parse(self, json_obj): """ Goes through everything in the page, and gets the title and adds all the rows to the categories field :param json_obj: The json to be parsed :return: A copy of the Page that you can use to browse all the items """ self.title = json_obj['title'] self.categories = [] for row in json_obj['rows']: page_item = self.page_category.parse(row['modules'][0]) self.categories.append(page_item) return copy.copy(self)
[docs] def get(self, endpoint, params=None): """ Retrieve a page from the specified endpoint, overwrites the calling page :param params: Parameter to retrieve the page with :param endpoint: The endpoint you want to retrieve :return: A copy of the new :class:`.Page` at the requested endpoint """ url = endpoint if params is None: params = {} if "deviceType" not in params: params["deviceType"] = "BROWSER" json_obj = self.request.request('GET', url, params=params).json() return self.parse(json_obj)
[docs]class PageCategory(object): type = None title = None description = "" session = None requests = None _more = None def __init__(self, session): self.session = session self.request = session.request self.item_types = { 'ALBUM_LIST': self.session.parse_album, 'ARTIST_LIST': self.session.parse_artist, 'TRACK_LIST': self.session.parse_track, 'PLAYLIST_LIST': self.session.parse_playlist, 'VIDEO_LIST': self.session.parse_video, 'MIX_LIST': self.session.parse_mix, }
[docs] def parse(self, json_obj): result = None category_type = json_obj['type'] if category_type in ('PAGE_LINKS_CLOUD', 'PAGE_LINKS'): category = PageLinks(self.session) elif category_type in ('FEATURED_PROMOTIONS', 'MULTIPLE_TOP_PROMOTIONS'): category = FeaturedItems(self.session) elif category_type in self.item_types.keys(): category = ItemList(self.session) elif category_type == 'MIX_HEADER': result = self.session.parse_mix(json_obj['mix']) elif category_type == 'ARTIST_HEADER': result = self.session.parse_artist(json_obj['artist']) result.bio = json_obj['bio'] elif category_type == 'ALBUM_HEADER': result = self.session.parse_album(json_obj['album']) elif category_type == 'HIGHLIGHT_MODULE': category = ItemList(self.session) elif category_type == 'MIXED_TYPES_LIST': category = ItemList(self.session) elif category_type == 'TEXT_BLOCK': category = TextBlock(self.session) elif category_type in ('ITEM_LIST_WITH_ROLES', 'ALBUM_ITEMS'): category = ItemList(self.session) elif category_type == 'ARTICLE_LIST': json_obj['items'] = json_obj['pagedList']['items'] category = LinkList(self.session) elif category_type == 'SOCIAL': json_obj['items'] = json_obj['socialProfiles'] category = LinkList(self.session) else: raise NotImplementedError('PageType {} not implemented'.format(category_type)) if result: return result return category.parse(json_obj)
[docs] def show_more(self): """ Get the full list of items on their own :class:`.Page` from a :class:`.PageCategory` :return: A :class:`.Page` more of the items in the category, None if there aren't any """ return Page(self.session, self._more['title']).get(self._more['apiPath']) if self._more else None
[docs]class FeaturedItems(PageCategory): """ Items that have been featured by TIDAL """ items = None def __init__(self, session): super(FeaturedItems, self).__init__(session)
[docs] def parse(self, json_obj): self.items = [] self.title = json_obj['title'] self.description = json_obj['description'] for item in json_obj['items']: self.items.append(PageItem(self.session, item)) return self
[docs]class ItemList(PageCategory): """ A list of items from TIDAL, can be a list of mixes, for example, or a list of playlists and mixes in some cases """ items = None
[docs] def parse(self, json_obj): """ Parse a list of items on TIDAL from the pages endpoints. :param json_obj: The json from TIDAL to be parsed :return: A copy of the ItemList with a list of items """ self._more = json_obj.get('showMore') self.title = json_obj['title'] item_type = json_obj['type'] list_key = 'pagedList' session = None parse = None if item_type in self.item_types.keys(): parse = self.item_types[item_type] elif item_type == 'HIGHLIGHT_MODULE': session = self.session # Unwrap subtitle, maybe add a field for it later json_obj[list_key] = {'items': [x['item'] for x in json_obj['highlights']]} elif item_type in ('MIXED_TYPES_LIST', 'ALBUM_ITEMS'): session = self.session elif item_type == 'ITEM_LIST_WITH_ROLES': for item in json_obj[list_key]['items']: item['item']['artistRoles'] = item['roles'] session = self.session else: raise NotImplementedError("PageType {} not implemented".format(item_type)) self.items = self.request.map_json(json_obj[list_key], parse, session) return copy.copy(self)
[docs]class PageItem(object): """ An Item from a :class:`.PageCategory` from the /pages endpoint, call get() to retrieve the actual item """ header = "" short_header = "" short_sub_header = "" image_id = "" type = "" artifact_id = "" text = "" featured = False def __init__(self, session, json_obj): self.session = session self.request = session.request self.header = json_obj['header'] self.short_header = json_obj['shortHeader'] self.short_sub_header = json_obj['shortSubHeader'] self.image_id = json_obj['imageId'] self.type = json_obj['type'] self.artifact_id = json_obj['artifactId'] self.text = json_obj['text'] self.featured = bool(json_obj['featured'])
[docs] def get(self): """ Retrieve the PageItem with the artifact_id matching the type :return: The fully parsed item, e.g. :class:`.Playlist`, :class:`.Video`, :class:`.Track` """ if self.type == 'PLAYLIST': result = self.session.playlist(self.artifact_id) elif self.type == 'VIDEO': result = self.session.video(self.artifact_id) elif self.type == 'TRACK': result = self.session.track(self.artifact_id) elif self.type == 'ARTIST': result = self.session.artist(self.artifact_id) else: raise NotImplementedError("PageItem type %s not implemented" % self.type) return result
class TextBlock(object): """ A block of text, with a named icon, which seems to be left up to the application """ text = "" icon = "" items = None def __init__(self, session): self.session = session def parse(self, json_obj): self.text = json_obj['text'] self.icon = json_obj['icon'] self.items = [self.text] return copy.copy(self) class LinkList(PageCategory): """ A list of items containing links, e.g. social links or articles """ items = None title = None description = None def parse(self, json_obj): self.items = json_obj['items'] self.title = json_obj['title'] self.description = json_obj['description'] return copy.copy(self)