From 7e8b43de25abf8db3b1a029e908aed71c440c33e Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Wed, 1 Sep 2021 20:29:22 +0200 Subject: [PATCH 01/18] Removed no longer supported API interactions, --- mopidy_radionet/radionet.py | 54 +++++++------------------------------ 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index f50dcea..d292abe 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -30,7 +30,6 @@ class RadioNetClient(object): base_url = 'https://radio.net/' session = requests.Session() - api_key = None api_prefix = None min_bitrate = 96 max_top_stations = 100 @@ -49,6 +48,8 @@ class RadioNetClient(object): proxy = httpclient.format_proxy(proxy_config) self.session.proxies.update({'http': proxy, 'https': proxy}) + if user_agent is None: + user_agent = "Mopidy Radio Client" full_user_agent = httpclient.format_user_agent(user_agent) self.session.headers.update({'user-agent': full_user_agent}) self.session.headers.update({'cache-control': 'no-cache'}) @@ -65,43 +66,16 @@ class RadioNetClient(object): self.local_stations = [] self.search_results = [] - def current_milli_time(self): - return int(round(time.time() * 1000)) - - def get_api_key(self): - if self.api_key is not None: + def set_api_prefix(self): + if self.api_prefix is not None: return tmp_str = self.session.get(self.base_url) - - # apiprefix_search = re.search('apiPrefix ?: ?\'(.*)\',?', tmp_str.content.decode()) - # self.api_prefix = apiprefix_search.group(1) lang = self.base_url.split('.')[-1].replace('/', '') self.api_prefix = "https://api.radio." + lang + "/info/v2" - apikey_search = re.search('apiKey ?: ?[\'|"](.*)[\'|"],?', tmp_str.content.decode()) - self.api_key = apikey_search.group(1) - - logger.info('Radio.net: APIPREFIX %s' % self.api_prefix) - logger.info('Radio.net: APIKEY %s' % self.api_key) - - def do_post(self, api_sufix, url_params=None, payload=None): - self.get_api_key() - - if 'apikey' in url_params.keys(): - url_params['apikey'] = self.api_key - - response = self.session.post(self.api_prefix + api_sufix, - params=url_params, data=payload) - - return response - def do_get(self, api_sufix, url_params=None): - self.get_api_key() - - if 'apikey' in url_params.keys(): - url_params['apikey'] = self.api_key - + self.set_api_prefix() response = self.session.get(self.api_prefix + api_sufix, params=url_params) @@ -112,8 +86,6 @@ class RadioNetClient(object): api_suffix = '/search/station' url_params = { - 'apikey': self.api_key, - '_': self.current_milli_time(), 'station': station_id, } @@ -149,13 +121,11 @@ class RadioNetClient(object): api_suffix = '/search/localstations' url_params = { - 'apikey': self.api_key, - '_': self.current_milli_time(), 'pageindex': 1, 'sizeperpage': 100, } - response = self.do_post(api_suffix, url_params) + response = self.do_get(api_suffix, url_params) if response.status_code is not 200: logger.error('Radio.net: Get local stations error. ' + @@ -181,13 +151,11 @@ class RadioNetClient(object): for station in self.favortes: api_suffix = '/search/stationsonly' url_params = { - 'apikey': self.api_key, - '_': self.current_milli_time(), 'query': station, 'pageindex': 1, } - response = self.do_post(api_suffix, url_params) + response = self.do_get(api_suffix, url_params) if response.status_code is not 200: logger.error('Radio.net: Search error ' + response.text) @@ -208,13 +176,11 @@ class RadioNetClient(object): api_suffix = '/search/topstations' url_params = { - 'apikey': self.api_key, - '_': self.current_milli_time(), 'pageindex': 1, 'sizeperpage': 100, } - response = self.do_post(api_suffix, url_params) + response = self.do_get(api_suffix, url_params) if response.status_code is not 200: logger.error('Radio.net: Get top stations error. ' + response.text) @@ -236,13 +202,11 @@ class RadioNetClient(object): api_suffix = '/search/stationsonly' url_params = { - 'apikey': self.api_key, - '_': self.current_milli_time(), 'query': query_string, 'pageindex': page_index, } - response = self.do_post(api_suffix, url_params) + response = self.do_get(api_suffix, url_params) if response.status_code is not 200: logger.error('Radio.net: Search error ' + response.text) From 2f659d8c60a2d5a73fd4d867d066bfeabcb4d29f Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Wed, 1 Sep 2021 21:04:40 +0200 Subject: [PATCH 02/18] Added a PlaybackProvider to mark tracks as live stream --- mopidy_radionet/backend.py | 18 ++++++++++++++++++ mopidy_radionet/library.py | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/mopidy_radionet/backend.py b/mopidy_radionet/backend.py index da365de..395fccd 100644 --- a/mopidy_radionet/backend.py +++ b/mopidy_radionet/backend.py @@ -5,6 +5,7 @@ import time from mopidy import backend import pykka +import re import mopidy_radionet @@ -24,6 +25,9 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend): mopidy_radionet.__version__)) self.library = RadioNetLibraryProvider(backend=self) + self.playback = RadioNetPlaybackProvider( + audio=audio, backend=self + ) self.uri_schemes = ['radionet'] @@ -46,3 +50,17 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend): self.radionet.get_local_stations() self.radionet.get_favorites() self.set_update_timeout() + +class RadioNetPlaybackProvider(backend.PlaybackProvider): + + def is_live(self, uri): + return True + + def translate_uri(self, uri): + identifier = re.findall(r'^radionet:track:?([a-z0-9]+|\d+)?$', uri) + if identifier: + radio_data = self.backend.radionet.get_station_by_id(identifier) + if radio_data: + return radio_data.stream_url + + return None \ No newline at end of file diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index 29f47cd..a2ed9e8 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -22,7 +22,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): variant, identifier = self.parse_uri(uri) - if variant == 'station': + if variant == 'station' or variant == 'track': identifier = int(identifier) radio_data = self.backend.radionet.get_station_by_id(identifier) @@ -40,7 +40,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): name=radio_data.name, genre=radio_data.genres, comment=radio_data.description, - uri=radio_data.stream_url) + uri='radionet:track:%s' % (identifier)) return [track] return [] From 63da37c012e2964bc17044e2da2a8f5c0b3308e8 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Wed, 8 Sep 2021 00:48:20 +0200 Subject: [PATCH 03/18] Refactored library and radionet class - Changes in radio.net API (added support for configurable apikey) - Added better caching - Added support for browsing additional categories --- README.rst | 5 +- mopidy_radionet/__init__.py | 19 +- mopidy_radionet/backend.py | 38 ++-- mopidy_radionet/ext.conf | 1 + mopidy_radionet/library.py | 253 +++++++++++++++++++------ mopidy_radionet/radionet.py | 361 +++++++++++++++++++++++------------- setup.cfg | 49 +++++ tests/conftest.py | 24 +++ tests/test_library.py | 142 ++++++++++++++ tests/test_radionet.py | 58 +++--- 10 files changed, 708 insertions(+), 242 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/test_library.py diff --git a/README.rst b/README.rst index 41525e2..ff823b8 100644 --- a/README.rst +++ b/README.rst @@ -37,6 +37,7 @@ Mopidy-RadioNet to your Mopidy configuration file:: enabled = true language = pl # or net, de, at, fr, pt, es, dk, se, it min_bitrate = 96 + api_key = valid_api_key favorite_stations = 'bbcradio1' 'bbcradio2' @@ -77,8 +78,8 @@ Mopidy-RadioNet to your Mopidy configuration file:: Project resources ================= -- `Source code `_ -- `Issue tracker `_ +- `Source code `_ +- `Issue tracker `_ Changelog diff --git a/mopidy_radionet/__init__.py b/mopidy_radionet/__init__.py index 7778a36..581dd09 100644 --- a/mopidy_radionet/__init__.py +++ b/mopidy_radionet/__init__.py @@ -5,8 +5,7 @@ import os from mopidy import config, ext - -__version__ = '0.2.2' +__version__ = "0.2.2" logger = logging.getLogger(__name__) @@ -14,21 +13,23 @@ logger = logging.getLogger(__name__) class Extension(ext.Extension): - dist_name = 'Mopidy-RadioNet' - ext_name = 'radionet' + dist_name = "Mopidy-RadioNet" + ext_name = "radionet" version = __version__ def get_default_config(self): - conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf') + conf_file = os.path.join(os.path.dirname(__file__), "ext.conf") return config.read(conf_file) def get_config_schema(self): schema = super(Extension, self).get_config_schema() - schema['language'] = config.String() - schema['min_bitrate'] = config.String() - schema['favorite_stations'] = config.List() + schema["language"] = config.String() + schema["min_bitrate"] = config.String() + schema["api_key"] = config.String() + schema["favorite_stations"] = config.List() return schema def setup(self, registry): from .backend import RadioNetBackend - registry.add('backend', RadioNetBackend) + + registry.add("backend", RadioNetBackend) diff --git a/mopidy_radionet/backend.py b/mopidy_radionet/backend.py index 395fccd..95dcb85 100644 --- a/mopidy_radionet/backend.py +++ b/mopidy_radionet/backend.py @@ -1,11 +1,10 @@ from __future__ import unicode_literals +import re import time -from mopidy import backend - import pykka -import re +from mopidy import backend import mopidy_radionet @@ -19,21 +18,24 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend): def __init__(self, config, audio): super(RadioNetBackend, self).__init__() self.radionet = RadioNetClient( - config['proxy'], - "%s/%s" % ( - mopidy_radionet.Extension.dist_name, - mopidy_radionet.__version__)) - - self.library = RadioNetLibraryProvider(backend=self) - self.playback = RadioNetPlaybackProvider( - audio=audio, backend=self + config["proxy"], + "%s/%s" + % (mopidy_radionet.Extension.dist_name, mopidy_radionet.__version__), ) - self.uri_schemes = ['radionet'] + self.library = RadioNetLibraryProvider(backend=self) + self.playback = RadioNetPlaybackProvider(audio=audio, backend=self) - self.radionet.min_bitrate = int(config['radionet']['min_bitrate']) - self.radionet.set_lang(str(config['radionet']['language'])) - self.radionet.set_favorites(tuple(file_ext.lower() for file_ext in config["radionet"]["favorite_stations"])) + self.uri_schemes = ["radionet"] + + self.radionet.min_bitrate = int(config["radionet"]["min_bitrate"]) + self.radionet.set_lang(str(config["radionet"]["language"])) + self.radionet.set_apikey(str(config["radionet"]["api_key"])) + self.radionet.set_favorites( + tuple( + file_ext.lower() for file_ext in config["radionet"]["favorite_stations"] + ) + ) def set_update_timeout(self, minutes=2): self.update_timeout = time.time() + 60 * minutes @@ -51,16 +53,16 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend): self.radionet.get_favorites() self.set_update_timeout() -class RadioNetPlaybackProvider(backend.PlaybackProvider): +class RadioNetPlaybackProvider(backend.PlaybackProvider): def is_live(self, uri): return True def translate_uri(self, uri): - identifier = re.findall(r'^radionet:track:?([a-z0-9]+|\d+)?$', uri) + identifier = re.findall(r"^radionet:track:?([a-z0-9]+|\d+)?$", uri) if identifier: radio_data = self.backend.radionet.get_station_by_id(identifier) if radio_data: return radio_data.stream_url - return None \ No newline at end of file + return None diff --git a/mopidy_radionet/ext.conf b/mopidy_radionet/ext.conf index b7fbfb3..3b276cd 100644 --- a/mopidy_radionet/ext.conf +++ b/mopidy_radionet/ext.conf @@ -2,4 +2,5 @@ enabled = true language = pl min_bitrate = 96 +api_key = something favorite_stations = \ No newline at end of file diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index a2ed9e8..d62788a 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -10,19 +10,19 @@ logger = logging.getLogger(__name__) class RadioNetLibraryProvider(backend.LibraryProvider): - root_directory = Ref.directory(uri='radionet:root', name='Radio.net') + root_directory = Ref.directory(uri="radionet:root", name="Radio.net") def __init__(self, backend): super().__init__(backend) def lookup(self, uri): - if not uri.startswith('radionet:'): + if not uri.startswith("radionet:"): return None - variant, identifier = self.parse_uri(uri) + variant, identifier, sorting, page = self.parse_uri(uri) - if variant == 'station' or variant == 'track': + if variant == "station" or variant == "track": identifier = int(identifier) radio_data = self.backend.radionet.get_station_by_id(identifier) @@ -30,9 +30,15 @@ class RadioNetLibraryProvider(backend.LibraryProvider): album = Album( artists=[artist], - name=radio_data.description + ' / ' + radio_data.continent + - ' / ' + radio_data.country + ' - ' + radio_data.city, - uri='radionet:station:%s' % (identifier)) + name=radio_data.description + + " / " + + radio_data.continent + + " / " + + radio_data.country + + " - " + + radio_data.city, + uri="radionet:station:%s" % (identifier), + ) track = Track( artists=[artist], @@ -40,81 +46,222 @@ class RadioNetLibraryProvider(backend.LibraryProvider): name=radio_data.name, genre=radio_data.genres, comment=radio_data.description, - uri='radionet:track:%s' % (identifier)) + uri="radionet:track:%s" % (identifier), + ) return [track] return [] def browse(self, uri): - self.backend.refresh() - directories = [] - tracks = [] - variant, identifier = self.parse_uri(uri) - if variant == 'root': - if self.backend.radionet.local_stations: - directories.append( - self.ref_directory( - "radionet:category:localstations", "Local stations") - ) - if self.backend.radionet.top_stations: - directories.append( - self.ref_directory( - "radionet:category:top100", "Top 100") - ) - if self.backend.radionet.favorite_stations: - directories.append( - self.ref_directory( - "radionet:category:favorites", "Favorites") - ) - return directories - elif variant == 'category' and identifier: - if identifier == "localstations": - for station in self.backend.radionet.local_stations: - tracks.append(self.station_to_ref(station)) - if identifier == "top100": - for station in self.backend.radionet.top_stations: - tracks.append(self.station_to_ref(station)) - if identifier == "favorites": - for station in self.backend.radionet.favorite_stations: - tracks.append(self.station_to_ref(station)) - tracks.sort(key=lambda ref: ref.name) - return tracks + category, page, value, sorting = self.parse_uri(uri) + + logger.error("Uri %s", uri) + + if category == "root": + return self._browse_root() + elif category in ["favorites", "topstations", "localstations"]: + return self._browse_category(category, page) + elif category in ["genres", "topics", "languages", "cities", "countries"]: + return self._browse_sorted_category(category, value, sorting, page) else: - logger.debug('Unknown URI: %s', uri) + logger.debug("Unknown URI: %s", uri) return [] + def _browse_root(self): + directories = [ + self.ref_directory("radionet:favorites", "Favorites"), + self.ref_directory("radionet:topstations", "Top stations"), + self.ref_directory("radionet:localstations", "Local stations"), + self.ref_directory("radionet:genres", "Genres"), + self.ref_directory("radionet:topics", "Topics"), + self.ref_directory("radionet:languages", "Languages"), + self.ref_directory("radionet:cities", "Cities"), + self.ref_directory("radionet:countries", "Countries"), + ] + return directories + + def _browse_category(self, category, page): + result = [] + if category == "favorites": + items = self._get_favorites() + if items: + for item in items: + result.append( + self.ref_track( + "radionet:station:{0}".format(str(item.id)), item.name + ) + ) + elif category == "topstations": + items = self._get_topstations() + if items: + for item in items: + result.append( + self.ref_track( + "radionet:station:{0}".format(item["id"]), + item["name"]["value"], + ) + ) + elif not page: + pages = self._get_category_pages(category) + for index in range(pages): + result.append( + self.ref_directory( + "radionet:{0}:{1}".format(category, str(index + 1)), + str(index + 1), + ) + ) + else: + items = self._get_category(category, page) + if items: + for item in items: + result.append( + self.ref_track( + "radionet:station:{0}".format(item["id"]), + item["name"]["value"], + ) + ) + return result + + def _browse_sorted_category(self, category, value, sorting, page): + result = [] + + if not value: + items = self.__getattribute__("_get_{0}".format(category))() + if items: + for item in items: + result.append( + self.ref_directory( + "radionet:{0}:{1}".format(category, item["systemEnglish"]), + item["localized"], + ) + ) + elif not sorting or sorting not in ["rank", "az"]: + result.append( + self.ref_directory( + "radionet:{0}:{1}:rank".format(category, value), "By rank" + ) + ) + result.append( + self.ref_directory( + "radionet:{0}:{1}:az".format(category, value), "Alphabetical" + ) + ) + elif not page: + pages = self._get_sorted_category_pages(category, value) + for index in range(pages): + result.append( + self.ref_directory( + "radionet:{0}:{1}:{2}:{3}".format( + category, value, sorting, str(index + 1) + ), + str(index + 1), + ) + ) + else: + items = self._get_sorted_category(category, value, sorting, page) + if items: + for item in items: + result.append( + self.ref_track( + "radionet:station:{0}".format(item["id"]), + item["name"]["value"], + ) + ) + return result + + def _get_genres(self): + return self.backend.radionet.get_genres() + + def _get_topics(self): + return self.backend.radionet.get_topics() + + def _get_languages(self): + return self.backend.radionet.get_languages() + + def _get_cities(self): + return self.backend.radionet.get_cities() + + def _get_countries(self): + return self.backend.radionet.get_countries() + + def _get_topstations(self): + return self.backend.radionet.get_category("topstations", 1) + + def _get_sorted_category(self, category, name, sorting, page): + return self.backend.radionet.get_sorted_category(category, name, sorting, page) + + def _get_sorted_category_pages(self, category, name): + return self.backend.radionet.get_sorted_category_pages(category, name) + + def _get_category(self, category, page): + return self.backend.radionet.get_category(category, page) + + def _get_category_pages(self, category): + return self.backend.radionet.get_category_pages(category) + + def _get_favorites(self): + return self.backend.radionet.get_favorites() + def search(self, query=None, uris=None, exact=False): - if 'any' not in query: + if "any" not in query: return None result = [] - self.backend.radionet.do_search(' '.join(query['any'])) + self.backend.radionet.do_search(" ".join(query["any"])) for station in self.backend.radionet.search_results: result.append(self.station_to_track(station)) - return SearchResult( - tracks=result - ) + return SearchResult(tracks=result) def station_to_ref(self, station): return Ref.track( - uri='radionet:station:%s' % (station.id), + uri="radionet:station:%s" % (station.id), name=station.name, ) def station_to_track(self, station): ref = self.station_to_ref(station) - return Track(uri=ref.uri, name=ref.name, album=Album(uri=ref.uri, name=ref.name), - artists=[Artist(uri=ref.uri, name=ref.name)]) + return Track( + uri=ref.uri, + name=ref.name, + album=Album(uri=ref.uri, name=ref.name), + artists=[Artist(uri=ref.uri, name=ref.name)], + ) def ref_directory(self, uri, name): return Ref.directory(uri=uri, name=name) + def ref_track(self, uri, name): + return Ref.track(uri=uri, name=name) + def parse_uri(self, uri): - result = re.findall(r'^radionet:([a-z]+):?([a-z0-9]+|\d+)?$', uri) + category = None + value = None + page = None + sorting = None + + result = re.findall( + r"^radionet:(genres|topics|languages|cities|countries)(:([^:]+)(:(rank|az)(:([0-9]+))?)?)?$", + uri, + ) + if result: - return result[0] - return None, None + category = result[0][0] + value = result[0][2] + sorting = result[0][4] + page = result[0][6] + + else: + result = re.findall( + r"^radionet:(root|favorites|topstations|localstations|station|track)(:([0-9]+))?$", + uri, + ) + + if result: + category = result[0][0] + page = result[0][2] + + return category, page, value, sorting diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index d292abe..54d6404 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -6,9 +6,8 @@ import logging import re import time -from mopidy import httpclient - import requests +from mopidy import httpclient logger = logging.getLogger(__name__) @@ -27,214 +26,324 @@ class Station(object): class RadioNetClient(object): - base_url = 'https://radio.net/' + base_url = "https://radio.net/" session = requests.Session() api_prefix = None min_bitrate = 96 max_top_stations = 100 station_bookmarks = None + api_key = None stations_images = [] - top_stations = [] - local_stations = [] search_results = [] + favorites = [] + cache = {} + + category_param_map = { + "genres": "genre", + "topics": "topic", + "languages": "language", + "cities": "city", + "countries": "country", + } def __init__(self, proxy_config=None, user_agent=None): super(RadioNetClient, self).__init__() self.session = requests.Session() + if proxy_config is not None: proxy = httpclient.format_proxy(proxy_config) - self.session.proxies.update({'http': proxy, 'https': proxy}) + self.session.proxies.update({"http": proxy, "https": proxy}) - if user_agent is None: - user_agent = "Mopidy Radio Client" full_user_agent = httpclient.format_user_agent(user_agent) - self.session.headers.update({'user-agent': full_user_agent}) - self.session.headers.update({'cache-control': 'no-cache'}) + self.session.headers.update({"user-agent": full_user_agent}) + self.session.headers.update({"cache-control": "no-cache"}) + + self.update_prefix() + + def __del__(self): + self.session.close() def set_lang(self, lang): - langs = ['net', 'de', 'at', 'fr', 'pt', 'es', 'dk', 'se', 'it', 'pl'] + langs = ["net", "de", "at", "fr", "pt", "es", "dk", "se", "it", "pl"] if lang in langs: self.base_url = self.base_url.replace(".net", "." + lang) else: logging.error("Radio.net not supported language: %s", str(lang)) + self.update_prefix() - def flush(self): - self.top_stations = [] - self.local_stations = [] - self.search_results = [] - - def set_api_prefix(self): - if self.api_prefix is not None: - return - + def update_prefix(self): tmp_str = self.session.get(self.base_url) - lang = self.base_url.split('.')[-1].replace('/', '') + lang = self.base_url.split(".")[-1].replace("/", "") self.api_prefix = "https://api.radio." + lang + "/info/v2" - def do_get(self, api_sufix, url_params=None): - self.set_api_prefix() - response = self.session.get(self.api_prefix + api_sufix, - params=url_params) + def set_apikey(self, api_key): + self.api_key = api_key + + def do_get(self, api_suffix, url_params=None): + if self.api_prefix is None: + return None + + if url_params is None: + url_params = {} + url_params["apikey"] = self.api_key + + response = self.session.get(self.api_prefix + api_suffix, params=url_params) return response + def get_cache(self, key): + if self.cache.get(key) is not None and self.cache[key].expired() is False: + return self.cache[key].value() + return None + + def set_cache(self, key, value, expires): + self.cache[key] = CacheItem(value, expires) + return value + def get_station_by_id(self, station_id): + cache_key = "station/" + str(station_id) + cache = self.get_cache(cache_key) + if cache is not None: + return cache - api_suffix = '/search/station' + api_suffix = "/search/station" url_params = { - 'station': station_id, + "station": station_id, } response = self.do_get(api_suffix, url_params) - if response.status_code is not 200: - logger.error('Radio.net: Error on get station by id ' + - str(station_id) + ". Error: " + response.text) + if response.status_code != 200: + logger.error( + "Radio.net: Error on get station by id " + + str(station_id) + + ". Error: " + + response.text + ) return False + + logger.debug("Radio.net: Done get top stations list") + json = response.json() + + station = Station() + station.id = json["id"] + station.continent = json["continent"] + station.country = json["country"] + station.city = json["city"] + station.genres = ", ".join(json["genres"]) + station.name = json["name"] + station.stream_url = self.get_stream_url(json["streamUrls"], self.min_bitrate) + station.image = json["logo100x100"] + station.description = json["shortDescription"] + if json["playable"] == "PLAYABLE": + station.playable = True + + return self.set_cache(cache_key, station, 1440) + + def get_genres(self): + return self._get_items("genres") + + def get_topics(self): + return self._get_items("topics") + + def get_languages(self): + return self._get_items("languages") + + def get_cities(self): + return self._get_items("cities") + + def get_countries(self): + return self._get_items("countries") + + def _get_items(self, key): + cached = self.get_cache(key) + if cached is not None: + return cached + + api_suffix = "/search/get" + key + response = self.do_get(api_suffix) + if response.status_code != 200: + logger.error( + "Radio.net: Error on get item list " + + str(api_suffix) + + ". Error: " + + response.text + ) + return False + return self.set_cache(key, response.json(), 1440) + + def get_sorted_category(self, category, name, sorting, page): + + if sorting == "az": + sorting = "STATION_NAME" else: - logger.debug('Radio.net: Done get top stations list') - json = response.json() + sorting = "RANK" - station = Station() - station.id = json['id'] - station.continent = json['continent'] - station.country = json['country'] - station.city = json['city'] - station.genres = ', '.join(json["genres"]) - station.name = json['name'] - station.stream_url = self.get_stream_url( - json['streamUrls'], self.min_bitrate) - station.image = json['logo100x100'] - station.description = json['shortDescription'] - if json['playable'] == 'PLAYABLE': - station.playable = True - - return station - - def get_local_stations(self): - self.local_stations = [] - - api_suffix = '/search/localstations' + cache_key = category + "/" + name + "/" + sorting + "/" + str(page) + cache = self.get_cache(cache_key) + if cache is not None: + return cache + api_suffix = "/search/stationsby" + self.category_param_map[category] url_params = { - 'pageindex': 1, - 'sizeperpage': 100, + self.category_param_map[category]: name, + "sorttype": sorting, + "sizeperpage": 50, + "pageindex": page, } response = self.do_get(api_suffix, url_params) - if response.status_code is not 200: - logger.error('Radio.net: Get local stations error. ' + - response.text) - else: - logger.debug('Radio.net: Done get local stations list') - json = response.json() - for match in json['categories'][0]['matches']: - station = self.get_station_by_id(match['id']) - if station: - if station.playable: - self.local_stations.append(station) + if response.status_code != 200: + logger.error( + "Radio.net: Error on get station by " + + str(category) + + ". Error: " + + response.text + ) + return False - logger.info('Radio.net: Loaded ' + str(len(self.local_stations)) + - ' local stations.') + json = response.json() + self.set_cache(category + "/" + name, int(json["numberPages"]), 10) + return self.set_cache(cache_key, json["categories"][0]["matches"], 10) + + def get_category(self, category, page): + cache_key = category + "/" + str(page) + cache = self.get_cache(cache_key) + if cache is not None: + return cache + + api_suffix = "/search/" + category + url_params = {"sizeperpage": 50, "pageindex": page} + + response = self.do_get(api_suffix, url_params) + logger.error(response.text) + if response.status_code != 200: + logger.error( + "Radio.net: Error on get station by " + + str(category) + + ". Error: " + + response.text + ) + return False + + json = response.json() + self.set_cache(category, int(json["numberPages"]), 10) + return self.set_cache(cache_key, json["categories"][0]["matches"], 10) + + def get_sorted_category_pages(self, category, name): + cache_key = category + "/" + name + cache = self.get_cache(cache_key) + if cache is not None: + return cache + + self.get_sorted_category(category, name, "rank", 1) + + return self.get_cache(cache_key) + + def get_category_pages(self, category): + cache_key = category + cache = self.get_cache(cache_key) + if cache is not None: + return cache + + self.get_category(category, 1) + + return self.get_cache(cache_key) def set_favorites(self, favorites): - self.favortes = favorites + self.favorites = favorites def get_favorites(self): - self.favorite_stations = [] + logger.error(str(self.favorites)) + cache_key = "favorites" + cache = self.get_cache(cache_key) + if cache is not None: + return cache - for station in self.favortes: - api_suffix = '/search/stationsonly' + favorite_stations = [] + for station in self.favorites: + logger.error(str(station)) + api_suffix = "/search/stationsonly" url_params = { - 'query': station, - 'pageindex': 1, + "query": station, + "pageindex": 1, } - response = self.do_get(api_suffix, url_params) - if response.status_code is not 200: - logger.error('Radio.net: Search error ' + response.text) + if response.status_code != 200: + logger.error("Radio.net: Search error " + response.text) else: - logger.debug('Radio.net: Done search') + logger.debug("Radio.net: Done search") json = response.json() # take only the first match! - station = self.get_station_by_id(json['categories'][0]['matches'][0]['id']) + station = self.get_station_by_id( + json["categories"][0]["matches"][0]["id"] + ) if station and station.playable: - self.favorite_stations.append(station) + favorite_stations.append(station) - logger.info('Radio.net: Loaded ' + str(len(self.favorite_stations)) + - ' favorite stations.') + logger.info( + "Radio.net: Loaded " + str(len(favorite_stations)) + " favorite stations." + ) + return self.set_cache(cache_key, favorite_stations, 1440) - def get_top_stations(self): - self.top_stations = [] - api_suffix = '/search/topstations' + def do_search(self, query_string, page_index=1, search_results=[]): + api_suffix = "/search/stationsonly" url_params = { - 'pageindex': 1, - 'sizeperpage': 100, + "query": query_string, + "sizeperpage": 50, + "pageindex": page_index, } response = self.do_get(api_suffix, url_params) - if response.status_code is not 200: - logger.error('Radio.net: Get top stations error. ' + response.text) + if response.status_code != 200: + logger.error("Radio.net: Search error " + response.text) else: - logger.debug('Radio.net: Done get top stations list') + logger.debug("Radio.net: Done search") json = response.json() - for match in json['categories'][0]['matches']: - station = self.get_station_by_id(match['id']) - if station: - if station.playable: - self.top_stations.append(station) - - logger.info('Radio.net: Loaded ' + str(len(self.top_stations)) + - ' top stations.') - - def do_search(self, query_string, page_index=1): - if page_index == 1: - self.search_results = [] - - api_suffix = '/search/stationsonly' - url_params = { - 'query': query_string, - 'pageindex': page_index, - } - - response = self.do_get(api_suffix, url_params) - - if response.status_code is not 200: - logger.error('Radio.net: Search error ' + response.text) - else: - logger.debug('Radio.net: Done search') - json = response.json() - for match in json['categories'][0]['matches']: - station = self.get_station_by_id(match['id']) + for match in json["categories"][0]["matches"]: + station = self.get_station_by_id(match["id"]) if station and station.playable: - self.search_results.append(station) + search_results.append(station) - number_pages = int(json['numberPages']) + number_pages = int(json["numberPages"]) if number_pages >= page_index: - self.do_search(query_string, page_index + 1) + self.do_search(query_string, page_index + 1, search_results) else: - logger.info('Radio.net: Found ' + - str(len(self.search_results)) + ' stations.') + logger.info( + "Radio.net: Found " + str(len(search_results)) + " stations." + ) + return search_results def get_stream_url(self, stream_json, bit_rate): stream_url = None for stream in stream_json: - if int(stream['bitRate']) >= bit_rate and \ - stream['streamStatus'] == 'VALID': - stream_url = stream['streamUrl'] + if int(stream["bitRate"]) >= bit_rate and stream["streamStatus"] == "VALID": + stream_url = stream["streamUrl"] break if stream_url is None and len(stream_json) > 0: - stream_url = stream_json[0]['streamUrl'] + stream_url = stream_json[0]["streamUrl"] return stream_url + + +class CacheItem(object): + def __init__(self, value, expires=10): + self._value = value + self._expires = expires = time.time() + expires * 60 + + def expired(self): + return self._expires < time.time() + + def value(self): + return self._value diff --git a/setup.cfg b/setup.cfg index c4829e0..d1587db 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,52 @@ +[metadata] +name = Mopidy-RadioNet +version = 0.2.2 +url = https://github.com/plintx/mopidy-radionet +license = Apache License, Version 2.0 +license_file = LICENSE +description = Mopidy extension for playing music from Radio.net +long_description = file: README.rst +classifiers = + Environment :: No Input/Output (Daemon) + Intended Audience :: End Users/Desktop + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Topic :: Multimedia :: Sound/Audio :: Players + +[options] +zip_safe = False +include_package_data = True +packages = find: +python_requires = >= 3.7 +install_requires = + Mopidy >= 3.0.0 + Pykka >= 2.0.1 + pyspotify >= 2.0.5 + requests >= 2.20.0 + setuptools + + +[options.extras_require] +lint = + black + check-manifest + flake8 + flake8-black + flake8-bugbear + flake8-import-order + isort +test = + pytest + pytest-cov + responses +dev = + %(lint)s + %(test)s + [flake8] application-import-names = mopidy_radionet,tests exclude = .git,.tox diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..8580305 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,24 @@ +from unittest import mock + +import pytest + +from mopidy_radionet import backend +from mopidy_radionet.radionet import RadioNetClient +from mopidy_radionet.library import RadioNetLibraryProvider + +@pytest.fixture +def backend_mock(): + backend_mock = mock.Mock(spec=backend.RadioNetBackend) + backend_mock.radionet = RadioNetClient(proxy_config=None) + backend_mock.library = RadioNetLibraryProvider(backend=backend_mock) + backend_mock.radionet.set_apikey('test') + backend_mock.radionet.set_favorites({'lush'}) + return backend_mock + +@pytest.fixture +def library(backend_mock): + return backend_mock.library + +@pytest.fixture +def radionet(backend_mock): + return backend_mock.radionet diff --git a/tests/test_library.py b/tests/test_library.py new file mode 100644 index 0000000..38455e4 --- /dev/null +++ b/tests/test_library.py @@ -0,0 +1,142 @@ +from unittest import mock + + +def test_browse_root(library): + results = library.browse('radionet:root'); + assert 8 == len(results) + + +def test_browse_localstations(library): + results = library.browse('radionet:localstations'); + assert len(results) > 0 + + page_uri = results[0].uri if results is not None else None + assert page_uri is not None + + results = library.browse(page_uri) + assert len(results) > 0 + + +def test_browse_topstations(library): + results = library.browse('radionet:topstations'); + assert len(results) > 0 + + +def test_browse_genres(library): + results = library.browse('radionet:genres'); + assert len(results) > 0 + + cat_uri = results[0].uri if results is not None else None + assert cat_uri is not None + + results = library.browse(cat_uri) + assert len(results) == 2 + + sort_uri = results[0].uri if results is not None else None + assert sort_uri is not None + + results = library.browse(sort_uri) + assert len(results) > 0 + + page_uri = results[0].uri if results is not None else None + assert page_uri is not None + + results = library.browse(page_uri) + assert len(results) > 0 + + +def test_browse_topics(library): + results = library.browse('radionet:topics'); + assert len(results) > 0 + + cat_uri = results[0].uri if results is not None else None + assert cat_uri is not None + + results = library.browse(cat_uri) + assert len(results) == 2 + + sort_uri = results[0].uri if results is not None else None + assert sort_uri is not None + + results = library.browse(sort_uri) + assert len(results) > 0 + + page_uri = results[0].uri if results is not None else None + assert page_uri is not None + + results = library.browse(page_uri) + assert len(results) > 0 + + +def test_browse_languages(library): + results = library.browse('radionet:languages'); + assert len(results) > 0 + + cat_uri = results[0].uri if results is not None else None + assert cat_uri is not None + + results = library.browse(cat_uri) + assert len(results) == 2 + + sort_uri = results[0].uri if results is not None else None + assert sort_uri is not None + + results = library.browse(sort_uri) + assert len(results) > 0 + + page_uri = results[0].uri if results is not None else None + assert page_uri is not None + + results = library.browse(page_uri) + assert len(results) > 0 + + +def test_browse_cities(library): + results = library.browse('radionet:cities'); + assert len(results) > 0 + + cat_uri = results[0].uri if results is not None else None + assert cat_uri is not None + + results = library.browse(cat_uri) + assert len(results) == 2 + + sort_uri = results[0].uri if results is not None else None + assert sort_uri is not None + + results = library.browse(sort_uri) + assert len(results) > 0 + + page_uri = results[0].uri if results is not None else None + assert page_uri is not None + + results = library.browse(page_uri) + assert len(results) > 0 + + +def test_browse_countries(library): + results = library.browse('radionet:countries'); + assert len(results) > 0 + + cat_uri = results[0].uri if results is not None else None + assert cat_uri is not None + + results = library.browse(cat_uri) + assert len(results) == 2 + + sort_uri = results[0].uri if results is not None else None + assert sort_uri is not None + + results = library.browse(sort_uri) + assert len(results) > 0 + + page_uri = results[0].uri if results is not None else None + assert page_uri is not None + + results = library.browse(page_uri) + assert len(results) > 0 + + +def test_browse_favorites(library): + results = library.browse('radionet:favorites'); + assert 1 == len(results) diff --git a/tests/test_radionet.py b/tests/test_radionet.py index 36971af..48eebe1 100644 --- a/tests/test_radionet.py +++ b/tests/test_radionet.py @@ -1,38 +1,28 @@ -import unittest - -from mopidy_radionet.radionet import RadioNetClient +from unittest import mock -class RadioNetClientTest(unittest.TestCase): - - def test_get_api_key(self): - radionet = RadioNetClient() - radionet.get_api_key() - - self.assertIsNotNone(radionet.api_key) - - def test_get_top_stations(self): - radionet = RadioNetClient() - radionet.get_top_stations() - self.assertGreater(len(radionet.top_stations), 0) - - def test_get_local_stations(self): - radionet = RadioNetClient() - radionet.get_local_stations() - self.assertGreater(len(radionet.local_stations), 0) - - def test_do_search(self): - radionet = RadioNetClient() - radionet.do_search("radio ram") - self.assertGreater(len(radionet.search_results), 0) - - def test_get_favorites(self): - test_favorites = ("Rock Antenne", "radio ram") - radionet = RadioNetClient() - radionet.set_favorites(test_favorites) - radionet.get_favorites() - self.assertEqual(len(radionet.favorite_stations), len(test_favorites)) +def test_get_genres(radionet): + genres = radionet.get_genres(); + assert len(genres) > 0 -if __name__ == "__main__": - unittest.main() +def test_get_top_stations(radionet): + result = radionet.get_category('topstations', 1) + assert len(result) > 0 + + +def test_get_local_stations(radionet): + result = radionet.get_category('localstations', 1) + assert len(result) > 0 + + +def test_do_search(radionet): + result = radionet.do_search("radio ram") + assert len(result) > 0 + + +def test_get_favorites(radionet): + test_favorites = ("Rock Antenne", "radio ram") + radionet.set_favorites(test_favorites) + result = radionet.get_favorites() + assert len(result) == len(test_favorites) From 76161adfdb78c81a0d2c4ff15726ef0e446c2e40 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Wed, 8 Sep 2021 18:20:12 +0200 Subject: [PATCH 04/18] Fixed regression in search --- mopidy_radionet/library.py | 4 +--- mopidy_radionet/radionet.py | 4 +++- tests/test_library.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index d62788a..6512b8c 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -209,9 +209,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): result = [] - self.backend.radionet.do_search(" ".join(query["any"])) - - for station in self.backend.radionet.search_results: + for station in self.backend.radionet.do_search(" ".join(query["any"])): result.append(self.station_to_track(station)) return SearchResult(tracks=result) diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 54d6404..5c30418 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -293,7 +293,7 @@ class RadioNetClient(object): ) return self.set_cache(cache_key, favorite_stations, 1440) - def do_search(self, query_string, page_index=1, search_results=[]): + def do_search(self, query_string, page_index=1, search_results=None): api_suffix = "/search/stationsonly" url_params = { @@ -308,6 +308,8 @@ class RadioNetClient(object): logger.error("Radio.net: Search error " + response.text) else: logger.debug("Radio.net: Done search") + if search_results is None: + search_results = [] json = response.json() for match in json["categories"][0]["matches"]: station = self.get_station_by_id(match["id"]) diff --git a/tests/test_library.py b/tests/test_library.py index 38455e4..492a23a 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -140,3 +140,15 @@ def test_browse_countries(library): def test_browse_favorites(library): results = library.browse('radionet:favorites'); assert 1 == len(results) + + +def test_search(library): + result = library.search({'any': ['radio ram']}) + + assert len(result.tracks) > 0 + + old_length = len(result.tracks) + + result = library.search({'any': ['radio ram']}) + + assert len(result.tracks) == old_length \ No newline at end of file From 10ead80966fd5e0933b07b38d50a67a1b3aaffe5 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Wed, 8 Sep 2021 20:25:52 +0200 Subject: [PATCH 05/18] Fixed favorites inconsistent results Should look up station by slug instead of search for station --- mopidy_radionet/radionet.py | 43 ++++++++++++++++++++----------------- tests/test_radionet.py | 4 +++- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 5c30418..33a69a3 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -259,34 +259,37 @@ class RadioNetClient(object): self.favorites = favorites def get_favorites(self): - logger.error(str(self.favorites)) cache_key = "favorites" cache = self.get_cache(cache_key) if cache is not None: return cache favorite_stations = [] - for station in self.favorites: - logger.error(str(station)) - api_suffix = "/search/stationsonly" - url_params = { - "query": station, - "pageindex": 1, - } - response = self.do_get(api_suffix, url_params) + for station_slug in self.favorites: - if response.status_code != 200: - logger.error("Radio.net: Search error " + response.text) - else: - logger.debug("Radio.net: Done search") - json = response.json() + station = self.get_station_by_id(station_slug) - # take only the first match! - station = self.get_station_by_id( - json["categories"][0]["matches"][0]["id"] - ) - if station and station.playable: - favorite_stations.append(station) + if station is False: + api_suffix = "/search/stationsonly" + url_params = { + "query": station_slug, + "pageindex": 1, + } + response = self.do_get(api_suffix, url_params) + + if response.status_code != 200: + logger.error("Radio.net: Search error " + response.text) + else: + logger.debug("Radio.net: Done search") + json = response.json() + logger.error(response.text) + # take only the first match! + station = self.get_station_by_id( + json["categories"][0]["matches"][0]["id"] + ) + + if station and station.playable: + favorite_stations.append(station) logger.info( "Radio.net: Loaded " + str(len(favorite_stations)) + " favorite stations." diff --git a/tests/test_radionet.py b/tests/test_radionet.py index 48eebe1..3a0ce48 100644 --- a/tests/test_radionet.py +++ b/tests/test_radionet.py @@ -22,7 +22,9 @@ def test_do_search(radionet): def test_get_favorites(radionet): - test_favorites = ("Rock Antenne", "radio ram") + test_favorites = ("Rock Antenne", "radio ram", "eska") radionet.set_favorites(test_favorites) result = radionet.get_favorites() assert len(result) == len(test_favorites) + + assert result[2].name == 'Eska' \ No newline at end of file From 155f063ecc2b807908b43b6de6196a90a1563c80 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Thu, 9 Sep 2021 01:19:59 +0200 Subject: [PATCH 06/18] Populate station list from search results to prevent unnecessary api requests --- mopidy_radionet/backend.py | 4 +- mopidy_radionet/library.py | 45 +++++---------- mopidy_radionet/radionet.py | 107 ++++++++++++++++++++++++++++++++---- tests/test_library.py | 12 +++- tests/test_radionet.py | 8 ++- 5 files changed, 129 insertions(+), 47 deletions(-) diff --git a/mopidy_radionet/backend.py b/mopidy_radionet/backend.py index 95dcb85..dfb9b61 100644 --- a/mopidy_radionet/backend.py +++ b/mopidy_radionet/backend.py @@ -61,8 +61,6 @@ class RadioNetPlaybackProvider(backend.PlaybackProvider): def translate_uri(self, uri): identifier = re.findall(r"^radionet:track:?([a-z0-9]+|\d+)?$", uri) if identifier: - radio_data = self.backend.radionet.get_station_by_id(identifier) - if radio_data: - return radio_data.stream_url + return self.backend.radionet.get_stream_url(identifier[0]) return None diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index 6512b8c..bf890d8 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -28,15 +28,21 @@ class RadioNetLibraryProvider(backend.LibraryProvider): artist = Artist(name=radio_data.name) - album = Album( - artists=[artist], - name=radio_data.description - + " / " + name = "" + if radio_data.description is not None: + name = radio_data.description + " / " + name = ( + name + radio_data.continent + " / " + radio_data.country + " - " - + radio_data.city, + + radio_data.city + ) + + album = Album( + artists=[artist], + name=name, uri="radionet:station:%s" % (identifier), ) @@ -56,8 +62,6 @@ class RadioNetLibraryProvider(backend.LibraryProvider): category, page, value, sorting = self.parse_uri(uri) - logger.error("Uri %s", uri) - if category == "root": return self._browse_root() elif category in ["favorites", "topstations", "localstations"]: @@ -87,21 +91,12 @@ class RadioNetLibraryProvider(backend.LibraryProvider): items = self._get_favorites() if items: for item in items: - result.append( - self.ref_track( - "radionet:station:{0}".format(str(item.id)), item.name - ) - ) + result.append(self.station_to_ref(item)) elif category == "topstations": items = self._get_topstations() if items: for item in items: - result.append( - self.ref_track( - "radionet:station:{0}".format(item["id"]), - item["name"]["value"], - ) - ) + result.append(self.station_to_ref(item)) elif not page: pages = self._get_category_pages(category) for index in range(pages): @@ -115,12 +110,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): items = self._get_category(category, page) if items: for item in items: - result.append( - self.ref_track( - "radionet:station:{0}".format(item["id"]), - item["name"]["value"], - ) - ) + result.append(self.station_to_ref(item)) return result def _browse_sorted_category(self, category, value, sorting, page): @@ -162,12 +152,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): items = self._get_sorted_category(category, value, sorting, page) if items: for item in items: - result.append( - self.ref_track( - "radionet:station:{0}".format(item["id"]), - item["name"]["value"], - ) - ) + result.append(self.station_to_ref(item)) return result def _get_genres(self): diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 33a69a3..12178a9 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -36,10 +36,13 @@ class RadioNetClient(object): api_key = None stations_images = [] - search_results = [] favorites = [] + cache = {} + stations_by_id = {} + stations_by_slug = {} + category_param_map = { "genres": "genre", "topics": "topic", @@ -104,6 +107,16 @@ class RadioNetClient(object): return value def get_station_by_id(self, station_id): + if not self.stations_by_id.get(station_id): + return self._get_station_by_id(station_id) + return self.stations_by_id.get(station_id) + + def get_station_by_slug(self, station_slug): + if not self.stations_by_slug.get(station_slug): + return self._get_station_by_id(station_slug) + return self.stations_by_slug.get(station_slug) + + def _get_station_by_id(self, station_id): cache_key = "station/" + str(station_id) cache = self.get_cache(cache_key) if cache is not None: @@ -129,20 +142,74 @@ class RadioNetClient(object): logger.debug("Radio.net: Done get top stations list") json = response.json() - station = Station() + if not self.stations_by_id.get(json["id"]): + station = Station() + station.playable = True + else: + station = self.stations_by_id[json["id"]] station.id = json["id"] station.continent = json["continent"] station.country = json["country"] station.city = json["city"] station.genres = ", ".join(json["genres"]) station.name = json["name"] - station.stream_url = self.get_stream_url(json["streamUrls"], self.min_bitrate) + station.slug = json["subdomain"] + station.stream_url = self._get_stream_url(json["streamUrls"], self.min_bitrate) station.image = json["logo100x100"] station.description = json["shortDescription"] if json["playable"] == "PLAYABLE": station.playable = True - return self.set_cache(cache_key, station, 1440) + self.stations_by_id[station.id] = station + self.stations_by_slug[station.slug] = station + + self.set_cache("station/" + str(station.id), station, 1440) + self.set_cache("station/" + station.slug, station, 1440) + return station + + def _get_station_from_search_result(self, result): + if not self.stations_by_id.get(result["id"]): + station = Station() + station.playable = True + else: + station = self.stations_by_id[result["id"]] + + station.id = result["id"] + if result["continent"] is not None: + station.continent = result["continent"]["value"] + else: + station.continent = "" + + if result["country"] is not None: + station.country = result["country"]["value"] + else: + station.country = "" + + if result["city"] is not None: + station.city = result["city"]["value"] + else: + station.city = "" + + if result["name"] is not None: + station.name = result["name"]["value"] + else: + station.name = "" + + if result["subdomain"] is not None: + station.slug = result["subdomain"]["value"] + else: + station.slug = "" + + if result["shortDescription"] is not None: + station.description = result["shortDescription"]["value"] + else: + station.description = "" + + station.image = result["logo100x100"] + + self.stations_by_id[station.id] = station + self.stations_by_slug[station.slug] = station + return station def get_genres(self): return self._get_items("genres") @@ -177,6 +244,12 @@ class RadioNetClient(object): return self.set_cache(key, response.json(), 1440) def get_sorted_category(self, category, name, sorting, page): + results = [] + for result in self._get_sorted_category(category, name, sorting, page): + results.append(self._get_station_from_search_result(result)) + return results + + def _get_sorted_category(self, category, name, sorting, page): if sorting == "az": sorting = "STATION_NAME" @@ -212,6 +285,12 @@ class RadioNetClient(object): return self.set_cache(cache_key, json["categories"][0]["matches"], 10) def get_category(self, category, page): + results = [] + for result in self._get_category(category, page): + results.append(self._get_station_from_search_result(result)) + return results + + def _get_category(self, category, page): cache_key = category + "/" + str(page) cache = self.get_cache(cache_key) if cache is not None: @@ -221,7 +300,7 @@ class RadioNetClient(object): url_params = {"sizeperpage": 50, "pageindex": page} response = self.do_get(api_suffix, url_params) - logger.error(response.text) + if response.status_code != 200: logger.error( "Radio.net: Error on get station by " @@ -267,7 +346,7 @@ class RadioNetClient(object): favorite_stations = [] for station_slug in self.favorites: - station = self.get_station_by_id(station_slug) + station = self.get_station_by_slug(station_slug) if station is False: api_suffix = "/search/stationsonly" @@ -282,10 +361,10 @@ class RadioNetClient(object): else: logger.debug("Radio.net: Done search") json = response.json() - logger.error(response.text) + # take only the first match! - station = self.get_station_by_id( - json["categories"][0]["matches"][0]["id"] + station = self._get_station_from_search_result( + json["categories"][0]["matches"][0] ) if station and station.playable: @@ -315,7 +394,7 @@ class RadioNetClient(object): search_results = [] json = response.json() for match in json["categories"][0]["matches"]: - station = self.get_station_by_id(match["id"]) + station = self._get_station_from_search_result(match) if station and station.playable: search_results.append(station) @@ -328,7 +407,13 @@ class RadioNetClient(object): ) return search_results - def get_stream_url(self, stream_json, bit_rate): + def get_stream_url(self, station_id): + station = self.get_station_by_id(station_id) + if not station.stream_url: + station = self._get_station_by_id(station.id) + return station.stream_url + + def _get_stream_url(self, stream_json, bit_rate): stream_url = None for stream in stream_json: diff --git a/tests/test_library.py b/tests/test_library.py index 492a23a..d551a6f 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -142,6 +142,7 @@ def test_browse_favorites(library): assert 1 == len(results) + def test_search(library): result = library.search({'any': ['radio ram']}) @@ -151,4 +152,13 @@ def test_search(library): result = library.search({'any': ['radio ram']}) - assert len(result.tracks) == old_length \ No newline at end of file + assert len(result.tracks) == old_length + + +def test_lookup(library): + + results = library.browse('radionet:favorites'); + assert 1 == len(results) + + for result in results: + assert library.lookup(result.uri) is not None \ No newline at end of file diff --git a/tests/test_radionet.py b/tests/test_radionet.py index 3a0ce48..cb2a816 100644 --- a/tests/test_radionet.py +++ b/tests/test_radionet.py @@ -20,11 +20,15 @@ def test_do_search(radionet): result = radionet.do_search("radio ram") assert len(result) > 0 + assert result[0].stream_url is None + + assert radionet.get_stream_url(result[0].id) is not None + def test_get_favorites(radionet): - test_favorites = ("Rock Antenne", "radio ram", "eska") + test_favorites = ("Rock Antenne", "radio ram", "eska", "dancefm") radionet.set_favorites(test_favorites) result = radionet.get_favorites() assert len(result) == len(test_favorites) - assert result[2].name == 'Eska' \ No newline at end of file + assert result[2].name == 'Eska' From 2f12cff16ad50dcad3264ba5c27eda6f188b3d0c Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Thu, 9 Sep 2021 01:30:01 +0200 Subject: [PATCH 07/18] Don't list pages if there is just a single page --- mopidy_radionet/library.py | 40 +++++++++++++++++++++++++------------- tests/test_library.py | 27 ++++++++++++++----------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index bf890d8..fff3d22 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -99,13 +99,19 @@ class RadioNetLibraryProvider(backend.LibraryProvider): result.append(self.station_to_ref(item)) elif not page: pages = self._get_category_pages(category) - for index in range(pages): - result.append( - self.ref_directory( - "radionet:{0}:{1}".format(category, str(index + 1)), - str(index + 1), + if pages == 1: + items = self._get_category(category, 1) + if items: + for item in items: + result.append(self.station_to_ref(item)) + else: + for index in range(pages): + result.append( + self.ref_directory( + "radionet:{0}:{1}".format(category, str(index + 1)), + str(index + 1), + ) ) - ) else: items = self._get_category(category, page) if items: @@ -139,15 +145,21 @@ class RadioNetLibraryProvider(backend.LibraryProvider): ) elif not page: pages = self._get_sorted_category_pages(category, value) - for index in range(pages): - result.append( - self.ref_directory( - "radionet:{0}:{1}:{2}:{3}".format( - category, value, sorting, str(index + 1) - ), - str(index + 1), + if pages == 1: + items = self._get_sorted_category(category, value, sorting, 1) + if items: + for item in items: + result.append(self.station_to_ref(item)) + else: + for index in range(pages): + result.append( + self.ref_directory( + "radionet:{0}:{1}:{2}:{3}".format( + category, value, sorting, str(index + 1) + ), + str(index + 1), + ) ) - ) else: items = self._get_sorted_category(category, value, sorting, page) if items: diff --git a/tests/test_library.py b/tests/test_library.py index d551a6f..5ea2d2c 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -13,8 +13,9 @@ def test_browse_localstations(library): page_uri = results[0].uri if results is not None else None assert page_uri is not None - results = library.browse(page_uri) - assert len(results) > 0 + # 1 Page, not results + # results = library.browse(page_uri) + # assert len(results) > 0 def test_browse_topstations(library): @@ -64,15 +65,16 @@ def test_browse_topics(library): page_uri = results[0].uri if results is not None else None assert page_uri is not None - results = library.browse(page_uri) - assert len(results) > 0 + # 1 Page, not results + # results = library.browse(page_uri) + # assert len(results) > 0 def test_browse_languages(library): results = library.browse('radionet:languages'); assert len(results) > 0 - cat_uri = results[0].uri if results is not None else None + cat_uri = results[5].uri if results is not None else None assert cat_uri is not None results = library.browse(cat_uri) @@ -87,8 +89,9 @@ def test_browse_languages(library): page_uri = results[0].uri if results is not None else None assert page_uri is not None - results = library.browse(page_uri) - assert len(results) > 0 + # 1 Page, not results + # results = library.browse(page_uri) + # assert len(results) > 0 def test_browse_cities(library): @@ -110,8 +113,9 @@ def test_browse_cities(library): page_uri = results[0].uri if results is not None else None assert page_uri is not None - results = library.browse(page_uri) - assert len(results) > 0 + # 1 Page, not results + # results = library.browse(page_uri) + # assert len(results) > 0 def test_browse_countries(library): @@ -133,8 +137,9 @@ def test_browse_countries(library): page_uri = results[0].uri if results is not None else None assert page_uri is not None - results = library.browse(page_uri) - assert len(results) > 0 + # 1 Page, not results + # results = library.browse(page_uri) + # assert len(results) > 0 def test_browse_favorites(library): From ca8b890951964ece913c9b63e6f2c300604e8e38 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Thu, 9 Sep 2021 19:46:20 +0200 Subject: [PATCH 08/18] Made favorites optional --- mopidy_radionet/__init__.py | 2 +- mopidy_radionet/library.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mopidy_radionet/__init__.py b/mopidy_radionet/__init__.py index 581dd09..24b0674 100644 --- a/mopidy_radionet/__init__.py +++ b/mopidy_radionet/__init__.py @@ -26,7 +26,7 @@ class Extension(ext.Extension): schema["language"] = config.String() schema["min_bitrate"] = config.String() schema["api_key"] = config.String() - schema["favorite_stations"] = config.List() + schema["favorite_stations"] = config.List(True) return schema def setup(self, registry): diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index fff3d22..ea835e0 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -74,7 +74,6 @@ class RadioNetLibraryProvider(backend.LibraryProvider): def _browse_root(self): directories = [ - self.ref_directory("radionet:favorites", "Favorites"), self.ref_directory("radionet:topstations", "Top stations"), self.ref_directory("radionet:localstations", "Local stations"), self.ref_directory("radionet:genres", "Genres"), @@ -83,6 +82,8 @@ class RadioNetLibraryProvider(backend.LibraryProvider): self.ref_directory("radionet:cities", "Cities"), self.ref_directory("radionet:countries", "Countries"), ] + if len(self.backend.radionet.favorites) > 0: + directories.insert(0, self.ref_directory("radionet:favorites", "Favorites")) return directories def _browse_category(self, category, page): From 4b611fb848e06bfc5bf1da9faaa1b728ff52e683 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Thu, 9 Sep 2021 19:50:53 +0200 Subject: [PATCH 09/18] Fixed setup.py version metadata regex --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c48878c..18fc41f 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from setuptools import find_packages, setup def get_version(filename): with open(filename) as fh: - metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", fh.read())) + metadata = dict(re.findall("__([a-z]+)__ = \"([^']+)\"", fh.read())) return metadata['version'] From bcae6be805dbac007f79a56c98509ad1575919fd Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Thu, 9 Sep 2021 19:53:40 +0200 Subject: [PATCH 10/18] Trim single quotes from favorite slugs --- mopidy_radionet/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy_radionet/backend.py b/mopidy_radionet/backend.py index dfb9b61..dd7b65e 100644 --- a/mopidy_radionet/backend.py +++ b/mopidy_radionet/backend.py @@ -33,7 +33,7 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend): self.radionet.set_apikey(str(config["radionet"]["api_key"])) self.radionet.set_favorites( tuple( - file_ext.lower() for file_ext in config["radionet"]["favorite_stations"] + file_ext.strip("'").lower() for file_ext in config["radionet"]["favorite_stations"] ) ) From cb2c4f07b5ffd4a94a93640fb4dbe2ee27c5678d Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Thu, 9 Sep 2021 19:59:38 +0200 Subject: [PATCH 11/18] Removed old refresh logic --- mopidy_radionet/backend.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/mopidy_radionet/backend.py b/mopidy_radionet/backend.py index dd7b65e..ba5edee 100644 --- a/mopidy_radionet/backend.py +++ b/mopidy_radionet/backend.py @@ -37,22 +37,6 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend): ) ) - def set_update_timeout(self, minutes=2): - self.update_timeout = time.time() + 60 * minutes - - def on_start(self): - self.set_update_timeout(0) - - def refresh(self, force=False): - if self.update_timeout is None: - self.set_update_timeout() - - if force or time.time() > self.update_timeout: - self.radionet.get_top_stations() - self.radionet.get_local_stations() - self.radionet.get_favorites() - self.set_update_timeout() - class RadioNetPlaybackProvider(backend.PlaybackProvider): def is_live(self, uri): From 424173da273946a5ea445a61305434db710f5d97 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Thu, 9 Sep 2021 20:27:41 +0200 Subject: [PATCH 12/18] Added image support --- mopidy_radionet/library.py | 19 ++++++++++++++++++- mopidy_radionet/radionet.py | 14 +++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index ea835e0..a1f45c7 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -4,7 +4,7 @@ import logging import re from mopidy import backend -from mopidy.models import Album, Artist, Ref, SearchResult, Track +from mopidy.models import Album, Artist, Ref, SearchResult, Track, Image logger = logging.getLogger(__name__) @@ -72,6 +72,23 @@ class RadioNetLibraryProvider(backend.LibraryProvider): logger.debug("Unknown URI: %s", uri) return [] + def get_images(self, uris): + images = {} + for uri in uris: + variant, identifier, sorting, page = self.parse_uri(uri) + station = self.backend.radionet.get_station_by_id(identifier) + if station: + images[uri] = [] + if station.image_tiny: + images[uri].append(Image(uri=station.image_tiny, height=44, width=44)) + if station.image_small: + images[uri].append(Image(uri=station.image_small, height=100, width=100)) + if station.image_medium: + images[uri].append(Image(uri=station.image_medium, height=175, width=175)) + if station.image_large: + images[uri].append(Image(uri=station.image_large, height=300, width=300)) + return images + def _browse_root(self): directories = [ self.ref_directory("radionet:topstations", "Top stations"), diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 12178a9..9a80275 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -20,7 +20,10 @@ class Station(object): genres = None name = None stream_url = None - image = None + image_tiny = None + image_small = None + image_medium = None + image_large = None description = None playable = False @@ -155,7 +158,10 @@ class RadioNetClient(object): station.name = json["name"] station.slug = json["subdomain"] station.stream_url = self._get_stream_url(json["streamUrls"], self.min_bitrate) - station.image = json["logo100x100"] + station.image_tiny = json["logo44x44"] + station.image_small = json["logo100x100"] + station.image_medium= json["logo175x175"] + station.image_large = json["logo300x300"] station.description = json["shortDescription"] if json["playable"] == "PLAYABLE": station.playable = True @@ -205,7 +211,9 @@ class RadioNetClient(object): else: station.description = "" - station.image = result["logo100x100"] + station.image_tiny = result["logo44x44"] + station.image_small = result["logo100x100"] + station.image_medium = result["logo175x175"] self.stations_by_id[station.id] = station self.stations_by_slug[station.slug] = station From 0df2ad24b7cac4d10128a74766468cb845e6fe67 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Fri, 10 Sep 2021 17:40:13 +0200 Subject: [PATCH 13/18] Fixed setup.py version metadata regex (2/2) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 18fc41f..06bcc20 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from setuptools import find_packages, setup def get_version(filename): with open(filename) as fh: - metadata = dict(re.findall("__([a-z]+)__ = \"([^']+)\"", fh.read())) + metadata = dict(re.findall("__([a-z]+)__ = \"([^\"]+)\"", fh.read())) return metadata['version'] From 34e489297586d31375e3c27fa569ee847fccbbae Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Tue, 28 Sep 2021 16:18:29 +0200 Subject: [PATCH 14/18] Fixed error caused by unrecognized favorite not yielding result in search --- mopidy_radionet/radionet.py | 13 +++++++++---- tests/test_radionet.py | 9 ++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 9a80275..79f600d 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -370,10 +370,15 @@ class RadioNetClient(object): logger.debug("Radio.net: Done search") json = response.json() - # take only the first match! - station = self._get_station_from_search_result( - json["categories"][0]["matches"][0] - ) + number_pages = int(json["numberPages"]) + logger.error(json) + if number_pages != 0: + # take only the first match! + station = self._get_station_from_search_result( + json["categories"][0]["matches"][0] + ) + else: + logger.warning("Radio.net: No results for %s", station_slug) if station and station.playable: favorite_stations.append(station) diff --git a/tests/test_radionet.py b/tests/test_radionet.py index cb2a816..f26a6d1 100644 --- a/tests/test_radionet.py +++ b/tests/test_radionet.py @@ -26,9 +26,16 @@ def test_do_search(radionet): def test_get_favorites(radionet): - test_favorites = ("Rock Antenne", "radio ram", "eska", "dancefm") + test_favorites = ["Rock Antenne", "radio ram", "eska", "dancefm"] radionet.set_favorites(test_favorites) result = radionet.get_favorites() assert len(result) == len(test_favorites) assert result[2].name == 'Eska' + + +def test_favorites_broken_slug(radionet): + test_favorites = ["radio357"] + radionet.set_favorites(test_favorites) + result = radionet.get_favorites() + assert len(result) == 0 From 5f40f6ccad4b7326758e0a823ba7c32558ccbcff Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Tue, 28 Sep 2021 16:25:36 +0200 Subject: [PATCH 15/18] Fixed some oddities with language setting - Strip whitespaces from setting - Accept "en" as alternative for "net" - Log warning instead of error --- mopidy_radionet/backend.py | 2 +- mopidy_radionet/radionet.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mopidy_radionet/backend.py b/mopidy_radionet/backend.py index ba5edee..0034e24 100644 --- a/mopidy_radionet/backend.py +++ b/mopidy_radionet/backend.py @@ -29,7 +29,7 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend): self.uri_schemes = ["radionet"] self.radionet.min_bitrate = int(config["radionet"]["min_bitrate"]) - self.radionet.set_lang(str(config["radionet"]["language"])) + self.radionet.set_lang(str(config["radionet"]["language"]).strip()) self.radionet.set_apikey(str(config["radionet"]["api_key"])) self.radionet.set_favorites( tuple( diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 79f600d..0062b95 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -73,11 +73,14 @@ class RadioNetClient(object): self.session.close() def set_lang(self, lang): + if lang == "en": + lang = "net" langs = ["net", "de", "at", "fr", "pt", "es", "dk", "se", "it", "pl"] + self.base_url = "https://radio.net/" if lang in langs: self.base_url = self.base_url.replace(".net", "." + lang) else: - logging.error("Radio.net not supported language: %s", str(lang)) + logging.warning("Radio.net not supported language: %s, defaulting to English", str(lang)) self.update_prefix() def update_prefix(self): From 9382eb2ff45374d75f20421636967ada6d134949 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Tue, 28 Sep 2021 17:15:24 +0200 Subject: [PATCH 16/18] Support for track uri with station slug instead of id --- mopidy_radionet/library.py | 21 +++++++++++++++++---- tests/test_library.py | 11 +++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index a1f45c7..b285181 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -23,8 +23,11 @@ class RadioNetLibraryProvider(backend.LibraryProvider): variant, identifier, sorting, page = self.parse_uri(uri) if variant == "station" or variant == "track": - identifier = int(identifier) - radio_data = self.backend.radionet.get_station_by_id(identifier) + try: + identifier = int(identifier) + radio_data = self.backend.radionet.get_station_by_id(identifier) + except ValueError: + radio_data = self.backend.radionet.get_station_by_slug(identifier) artist = Artist(name=radio_data.name) @@ -43,7 +46,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): album = Album( artists=[artist], name=name, - uri="radionet:station:%s" % (identifier), + uri="radionet:station:%s" % (radio_data.id), ) track = Track( @@ -52,7 +55,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): name=radio_data.name, genre=radio_data.genres, comment=radio_data.description, - uri="radionet:track:%s" % (identifier), + uri="radionet:track:%s" % (radio_data.id), ) return [track] @@ -277,4 +280,14 @@ class RadioNetLibraryProvider(backend.LibraryProvider): category = result[0][0] page = result[0][2] + else: + result = re.findall( + r"^radionet:(track):([^:]+)$", + uri, + ) + + if result: + category = result[0][0] + page = result[0][1] + return category, page, value, sorting diff --git a/tests/test_library.py b/tests/test_library.py index 5ea2d2c..ac70bcd 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -162,8 +162,15 @@ def test_search(library): def test_lookup(library): - results = library.browse('radionet:favorites'); + results = library.browse('radionet:favorites') assert 1 == len(results) for result in results: - assert library.lookup(result.uri) is not None \ No newline at end of file + assert library.lookup(result.uri) is not None + + +def test_track_by_slug(library): + + results = library.lookup('radionet:track:dancefm') + assert 1 == len(results) + assert results[0].uri == 'radionet:track:2180' From 55aab87f4ec6cd341c72d54f138ac0bdb6210be1 Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Tue, 28 Sep 2021 17:25:37 +0200 Subject: [PATCH 17/18] Code cleanup --- mopidy_radionet/backend.py | 2 -- mopidy_radionet/library.py | 7 ++++--- mopidy_radionet/radionet.py | 6 ++---- tests/conftest.py | 3 +++ tests/test_library.py | 22 +++++++++------------- tests/test_radionet.py | 3 +-- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/mopidy_radionet/backend.py b/mopidy_radionet/backend.py index 0034e24..89d0647 100644 --- a/mopidy_radionet/backend.py +++ b/mopidy_radionet/backend.py @@ -1,8 +1,6 @@ from __future__ import unicode_literals import re -import time - import pykka from mopidy import backend diff --git a/mopidy_radionet/library.py b/mopidy_radionet/library.py index b285181..69e7a8f 100644 --- a/mopidy_radionet/library.py +++ b/mopidy_radionet/library.py @@ -6,6 +6,7 @@ import re from mopidy import backend from mopidy.models import Album, Artist, Ref, SearchResult, Track, Image + logger = logging.getLogger(__name__) @@ -46,7 +47,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): album = Album( artists=[artist], name=name, - uri="radionet:station:%s" % (radio_data.id), + uri="radionet:station:%s" % radio_data.id, ) track = Track( @@ -55,7 +56,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): name=radio_data.name, genre=radio_data.genres, comment=radio_data.description, - uri="radionet:track:%s" % (radio_data.id), + uri="radionet:track:%s" % radio_data.id, ) return [track] @@ -234,7 +235,7 @@ class RadioNetLibraryProvider(backend.LibraryProvider): def station_to_ref(self, station): return Ref.track( - uri="radionet:station:%s" % (station.id), + uri="radionet:station:%s" % station.id, name=station.name, ) diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 0062b95..37f1221 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import logging -import re import time import requests @@ -84,7 +83,6 @@ class RadioNetClient(object): self.update_prefix() def update_prefix(self): - tmp_str = self.session.get(self.base_url) lang = self.base_url.split(".")[-1].replace("/", "") self.api_prefix = "https://api.radio." + lang + "/info/v2" @@ -163,7 +161,7 @@ class RadioNetClient(object): station.stream_url = self._get_stream_url(json["streamUrls"], self.min_bitrate) station.image_tiny = json["logo44x44"] station.image_small = json["logo100x100"] - station.image_medium= json["logo175x175"] + station.image_medium = json["logo175x175"] station.image_large = json["logo300x300"] station.description = json["shortDescription"] if json["playable"] == "PLAYABLE": @@ -446,7 +444,7 @@ class RadioNetClient(object): class CacheItem(object): def __init__(self, value, expires=10): self._value = value - self._expires = expires = time.time() + expires * 60 + self._expires = time.time() + expires * 60 def expired(self): return self._expires < time.time() diff --git a/tests/conftest.py b/tests/conftest.py index 8580305..c614e15 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ from mopidy_radionet import backend from mopidy_radionet.radionet import RadioNetClient from mopidy_radionet.library import RadioNetLibraryProvider + @pytest.fixture def backend_mock(): backend_mock = mock.Mock(spec=backend.RadioNetBackend) @@ -15,10 +16,12 @@ def backend_mock(): backend_mock.radionet.set_favorites({'lush'}) return backend_mock + @pytest.fixture def library(backend_mock): return backend_mock.library + @pytest.fixture def radionet(backend_mock): return backend_mock.radionet diff --git a/tests/test_library.py b/tests/test_library.py index ac70bcd..4369ca5 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -1,13 +1,12 @@ -from unittest import mock def test_browse_root(library): - results = library.browse('radionet:root'); + results = library.browse('radionet:root') assert 8 == len(results) def test_browse_localstations(library): - results = library.browse('radionet:localstations'); + results = library.browse('radionet:localstations') assert len(results) > 0 page_uri = results[0].uri if results is not None else None @@ -19,12 +18,12 @@ def test_browse_localstations(library): def test_browse_topstations(library): - results = library.browse('radionet:topstations'); + results = library.browse('radionet:topstations') assert len(results) > 0 def test_browse_genres(library): - results = library.browse('radionet:genres'); + results = library.browse('radionet:genres') assert len(results) > 0 cat_uri = results[0].uri if results is not None else None @@ -47,7 +46,7 @@ def test_browse_genres(library): def test_browse_topics(library): - results = library.browse('radionet:topics'); + results = library.browse('radionet:topics') assert len(results) > 0 cat_uri = results[0].uri if results is not None else None @@ -71,7 +70,7 @@ def test_browse_topics(library): def test_browse_languages(library): - results = library.browse('radionet:languages'); + results = library.browse('radionet:languages') assert len(results) > 0 cat_uri = results[5].uri if results is not None else None @@ -95,7 +94,7 @@ def test_browse_languages(library): def test_browse_cities(library): - results = library.browse('radionet:cities'); + results = library.browse('radionet:cities') assert len(results) > 0 cat_uri = results[0].uri if results is not None else None @@ -119,7 +118,7 @@ def test_browse_cities(library): def test_browse_countries(library): - results = library.browse('radionet:countries'); + results = library.browse('radionet:countries') assert len(results) > 0 cat_uri = results[0].uri if results is not None else None @@ -143,11 +142,10 @@ def test_browse_countries(library): def test_browse_favorites(library): - results = library.browse('radionet:favorites'); + results = library.browse('radionet:favorites') assert 1 == len(results) - def test_search(library): result = library.search({'any': ['radio ram']}) @@ -161,7 +159,6 @@ def test_search(library): def test_lookup(library): - results = library.browse('radionet:favorites') assert 1 == len(results) @@ -170,7 +167,6 @@ def test_lookup(library): def test_track_by_slug(library): - results = library.lookup('radionet:track:dancefm') assert 1 == len(results) assert results[0].uri == 'radionet:track:2180' diff --git a/tests/test_radionet.py b/tests/test_radionet.py index f26a6d1..20258cb 100644 --- a/tests/test_radionet.py +++ b/tests/test_radionet.py @@ -1,8 +1,7 @@ -from unittest import mock def test_get_genres(radionet): - genres = radionet.get_genres(); + genres = radionet.get_genres() assert len(genres) > 0 From 9c2c78c37350dceafb818645d7b09ce9f4ba5a7f Mon Sep 17 00:00:00 2001 From: Eric van Blokland Date: Tue, 28 Sep 2021 17:32:12 +0200 Subject: [PATCH 18/18] Removed lingering logging statement and fixed caching issue causing tests to fail --- mopidy_radionet/radionet.py | 2 +- tests/test_radionet.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mopidy_radionet/radionet.py b/mopidy_radionet/radionet.py index 37f1221..0001a58 100644 --- a/mopidy_radionet/radionet.py +++ b/mopidy_radionet/radionet.py @@ -372,7 +372,7 @@ class RadioNetClient(object): json = response.json() number_pages = int(json["numberPages"]) - logger.error(json) + if number_pages != 0: # take only the first match! station = self._get_station_from_search_result( diff --git a/tests/test_radionet.py b/tests/test_radionet.py index 20258cb..b12e50f 100644 --- a/tests/test_radionet.py +++ b/tests/test_radionet.py @@ -25,6 +25,7 @@ def test_do_search(radionet): def test_get_favorites(radionet): + radionet.cache = {}; test_favorites = ["Rock Antenne", "radio ram", "eska", "dancefm"] radionet.set_favorites(test_favorites) result = radionet.get_favorites() @@ -34,6 +35,7 @@ def test_get_favorites(radionet): def test_favorites_broken_slug(radionet): + radionet.cache = {}; test_favorites = ["radio357"] radionet.set_favorites(test_favorites) result = radionet.get_favorites()