Refactored library and radionet class
- Changes in radio.net API (added support for configurable apikey) - Added better caching - Added support for browsing additional categories
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user