Initial commit
This commit is contained in:
36
mopidy_radionet/__init__.py
Normal file
36
mopidy_radionet/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from mopidy import config, ext
|
||||
|
||||
|
||||
__version__ = '0.1.0'
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Extension(ext.Extension):
|
||||
|
||||
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')
|
||||
return config.read(conf_file)
|
||||
|
||||
def get_config_schema(self):
|
||||
schema = super(Extension, self).get_config_schema()
|
||||
schema['username'] = config.String()
|
||||
schema['password'] = config.Secret()
|
||||
schema['language'] = config.String()
|
||||
schema['min_bitrate'] = config.String()
|
||||
return schema
|
||||
|
||||
def setup(self, registry):
|
||||
from .backend import RadioNetBackend
|
||||
registry.add('backend', RadioNetBackend)
|
||||
|
||||
46
mopidy_radionet/backend.py
Normal file
46
mopidy_radionet/backend.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import pykka
|
||||
import time
|
||||
|
||||
from mopidy import backend
|
||||
import mopidy_radionet
|
||||
from .library import RadioNetLibraryProvider
|
||||
from .radionet import RadioNetClient
|
||||
|
||||
|
||||
class RadioNetBackend(pykka.ThreadingActor, backend.Backend):
|
||||
update_timeout = None
|
||||
|
||||
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.uri_schemes = ['radionet']
|
||||
self.username = config['radionet']['username']
|
||||
self.password = config['radionet']['password']
|
||||
|
||||
self.radionet.min_bitrate = int(config['radionet']['min_bitrate'])
|
||||
self.radionet.set_lang(str(config['radionet']['language']))
|
||||
|
||||
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.login(self.username, self.password)
|
||||
self.radionet.get_bookmarks()
|
||||
self.radionet.get_my_stations_streams()
|
||||
self.radionet.get_top_stations()
|
||||
self.radionet.get_local_stations()
|
||||
self.set_update_timeout()
|
||||
6
mopidy_radionet/ext.conf
Normal file
6
mopidy_radionet/ext.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
[radionet]
|
||||
enabled = true
|
||||
username = alice666.9
|
||||
password = secret
|
||||
language = pl
|
||||
min_bitrate = 96
|
||||
116
mopidy_radionet/library.py
Normal file
116
mopidy_radionet/library.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import pykka
|
||||
import logging
|
||||
import re
|
||||
from mopidy import backend
|
||||
from mopidy.models import Album, Artist, Ref, Track, SearchResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RadioNetLibraryProvider(backend.LibraryProvider):
|
||||
root_directory = Ref.directory(uri='radionet:root', name='Radio.net')
|
||||
|
||||
def lookup(self, uri):
|
||||
|
||||
if not uri.startswith('radionet:'):
|
||||
return None
|
||||
|
||||
variant, identifier = self.parse_uri(uri)
|
||||
|
||||
if variant == 'station':
|
||||
identifier = int(identifier)
|
||||
radio_data = self.backend.radionet.get_station_by_id(identifier)
|
||||
|
||||
artist = Artist(name=radio_data.name)
|
||||
|
||||
album = Album(
|
||||
artists=[artist],
|
||||
images=[radio_data.image],
|
||||
name=radio_data.description + " / " + radio_data.continent +
|
||||
" / " +
|
||||
radio_data.country + " - " + radio_data.city,
|
||||
uri='radionet:station:%s' % (identifier))
|
||||
|
||||
track = Track(
|
||||
artists=[artist],
|
||||
album=album,
|
||||
name=radio_data.name,
|
||||
genre=radio_data.genres,
|
||||
comment=radio_data.description,
|
||||
uri=radio_data.stream_url)
|
||||
|
||||
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.fav_stations:
|
||||
directories.append(
|
||||
self.ref_directory(
|
||||
"radionet:category:favstations", "My stations")
|
||||
)
|
||||
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")
|
||||
)
|
||||
elif variant == 'category' and identifier:
|
||||
if identifier == "favstations":
|
||||
for station in self.backend.radionet.fav_stations:
|
||||
tracks.append(self.station_to_ref(station))
|
||||
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))
|
||||
else:
|
||||
logger.debug('Unknown URI: %s', uri)
|
||||
return []
|
||||
|
||||
tracks.sort(key=lambda ref: ref.name)
|
||||
|
||||
return directories + tracks
|
||||
|
||||
def search(self, query=None, uris=None, exact=False):
|
||||
|
||||
result = []
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
def station_to_ref(self, station):
|
||||
return Ref.track(
|
||||
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)
|
||||
|
||||
def ref_directory(self, uri, name):
|
||||
return Ref.directory(uri=uri, name=name)
|
||||
|
||||
def parse_uri(self, uri):
|
||||
result = re.findall(r'^radionet:([a-z]+):?([a-z0-9]+|\d+)?$', uri)
|
||||
if result:
|
||||
return result[0]
|
||||
return None, None
|
||||
320
mopidy_radionet/radionet.py
Normal file
320
mopidy_radionet/radionet.py
Normal file
@@ -0,0 +1,320 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import requests
|
||||
import re
|
||||
import time
|
||||
|
||||
from mopidy import httpclient
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Station(object):
|
||||
id = None
|
||||
continent = None
|
||||
country = None
|
||||
city = None
|
||||
genres = None
|
||||
name = None
|
||||
stream_url = None
|
||||
image = None
|
||||
description = None
|
||||
playable = False
|
||||
|
||||
|
||||
class RadioNetClient(object):
|
||||
|
||||
base_url = 'https://radio.net/'
|
||||
api_base_url = 'https://api.radio.net/info/v2/'
|
||||
|
||||
session = requests.Session()
|
||||
api_key = None
|
||||
user_login = None
|
||||
min_bitrate = 96
|
||||
max_top_stations = 100
|
||||
station_bookmarks = None
|
||||
|
||||
fav_stations = []
|
||||
top_stations = []
|
||||
local_stations = []
|
||||
search_results = []
|
||||
|
||||
username = None
|
||||
password = None
|
||||
|
||||
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})
|
||||
|
||||
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.get_api_key()
|
||||
|
||||
def set_lang(self, lang):
|
||||
langs = ['net', 'de', 'at', 'fr', 'pt', 'es', 'dk', 'se', 'it', 'pl']
|
||||
if lang in langs:
|
||||
self.base_url = self.base_url.replace(".net", "." + lang)
|
||||
self.api_base_url = self.api_base_url.replace(".net", "." + lang)
|
||||
else:
|
||||
logging.error("Radio.net not supported language: " - str(lang))
|
||||
|
||||
def flush(self):
|
||||
self.fav_stations = []
|
||||
self.top_stations = []
|
||||
self.local_stations = []
|
||||
self.search_results = []
|
||||
|
||||
def current_milli_time(self):
|
||||
return int(round(time.time() * 1000))
|
||||
|
||||
def get_api_key(self):
|
||||
tmp_str = self.session.get(self.base_url)
|
||||
m = re.search('apiKey ?= ?[\'|"](.*)[\'|"];', tmp_str.content)
|
||||
self.api_key = m.group(1).encode()
|
||||
|
||||
def check_auth(self):
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
}
|
||||
logger.debug('Radio.net: Check auth.')
|
||||
api_sufix = 'user/account'
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=url_params)
|
||||
|
||||
if response.status_code is not 200:
|
||||
logger.error('Radio.net: Auth error.')
|
||||
else:
|
||||
json = response.json()
|
||||
self.user_login = json['login']
|
||||
if len(self.user_login) == 0:
|
||||
self.auth = False
|
||||
else:
|
||||
self.auth = True
|
||||
|
||||
def login(self, username, password):
|
||||
self.check_auth()
|
||||
|
||||
if self.auth:
|
||||
return
|
||||
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
}
|
||||
|
||||
logger.debug('Radio.net: Login.')
|
||||
api_sufix = 'user/login'
|
||||
payload = {
|
||||
'login': username,
|
||||
'password': password,
|
||||
}
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=url_params, data=payload)
|
||||
|
||||
if response.status_code is not 200:
|
||||
logger.error('Radio.net: Login error. ' + response.text)
|
||||
else:
|
||||
json = response.json()
|
||||
self.user_login = json['login']
|
||||
|
||||
if self.user_login == username:
|
||||
logger.debug('Radio.net: Login successful.')
|
||||
|
||||
def logout(self):
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
}
|
||||
api_sufix = 'user/logout'
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=url_params)
|
||||
|
||||
if response.status_code is not 200:
|
||||
logger.error('Radio.net: Error logout.')
|
||||
else:
|
||||
json = response.json()
|
||||
if len(json['login']) > 0:
|
||||
logger.error('Radio.net: Error logout.')
|
||||
else:
|
||||
logger.info('Radio.net: Logout successful.')
|
||||
|
||||
self.session.cookies.clear_session_cookies()
|
||||
|
||||
def get_bookmarks(self):
|
||||
self.station_bookmarks = None
|
||||
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
'list': 'STATION',
|
||||
}
|
||||
api_sufix = 'user/bookmarks'
|
||||
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=url_params)
|
||||
|
||||
if response.status_code is not 200:
|
||||
logger.error('Radio.net: ' + response.text)
|
||||
else:
|
||||
logger.debug('Radio.net: Done get bookmarks')
|
||||
json = response.json()
|
||||
self.station_bookmarks = json['stationBookmarks']
|
||||
|
||||
def __get_stream_url(self, stream_json, bitrate):
|
||||
stream_url = None
|
||||
|
||||
for stream in stream_json:
|
||||
if int(stream['bitRate']) >= bitrate 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']
|
||||
|
||||
return stream_url
|
||||
|
||||
def get_my_stations_streams(self):
|
||||
self.fav_stations = []
|
||||
for station_item in self.station_bookmarks['pageItems']:
|
||||
station = self.get_station_by_id(station_item['id'])
|
||||
if station.playable:
|
||||
self.fav_stations.append(station)
|
||||
|
||||
logger.info('Radio.net: Loaded ' + str(len(self.fav_stations)) +
|
||||
' my stations.')
|
||||
|
||||
def get_station_by_id(self, station_id):
|
||||
|
||||
api_sufix = 'search/station'
|
||||
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
'station': station_id,
|
||||
}
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=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)
|
||||
return False
|
||||
else:
|
||||
logger.debug('Radio.net: Done get top stations list')
|
||||
json = response.json()
|
||||
|
||||
tmpStation = Station()
|
||||
tmpStation.id = json['id']
|
||||
tmpStation.continent = json['continent']
|
||||
tmpStation.country = json['country']
|
||||
tmpStation.city = json['city']
|
||||
tmpStation.genres = ', '.join(json["genres"])
|
||||
tmpStation.name = json['name']
|
||||
tmpStation.stream_url = self.__get_stream_url(
|
||||
json['streamUrls'], self.min_bitrate)
|
||||
tmpStation.image = json['logo300x300']
|
||||
tmpStation.description = json['shortDescription']
|
||||
if json['playable'] == 'PLAYABLE':
|
||||
tmpStation.playable = True
|
||||
|
||||
return tmpStation
|
||||
|
||||
def get_local_stations(self):
|
||||
api_sufix = 'search/localstations'
|
||||
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
'pageindex': 1,
|
||||
'sizeperpage': 100,
|
||||
}
|
||||
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=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)
|
||||
|
||||
logger.info('Radio.net: Loaded ' + str(len(self.local_stations)) +
|
||||
' local stations.')
|
||||
|
||||
def get_top_stations(self):
|
||||
api_sufix = 'search/topstations'
|
||||
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
'pageindex': 1,
|
||||
'sizeperpage': 100,
|
||||
}
|
||||
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=url_params)
|
||||
|
||||
if response.status_code is not 200:
|
||||
logger.error('Radio.net: Get top stations error. ' + response.text)
|
||||
else:
|
||||
logger.debug('Radio.net: Done get top 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.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_sufix = 'search/stationsonly'
|
||||
url_params = {
|
||||
'apikey': self.api_key,
|
||||
'_': self.current_milli_time(),
|
||||
'query': query_string,
|
||||
'pageindex': page_index,
|
||||
}
|
||||
response = self.session.post(self.api_base_url + api_sufix,
|
||||
params=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'])
|
||||
if station and station.playable:
|
||||
self.search_results.append(station)
|
||||
|
||||
number_pages = int(json['numberPages'])
|
||||
if number_pages >= page_index:
|
||||
self.do_search(query_string, page_index + 1)
|
||||
else:
|
||||
logger.info('Radio.net: Found ' +
|
||||
str(len(self.search_results)) + ' stations.')
|
||||
Reference in New Issue
Block a user