123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- # SPDX-License-Identifier: AGPL-3.0-or-later
- """Search radio stations from RadioBrowser by `Advanced station search API`_.
- .. _Advanced station search API:
- https://de1.api.radio-browser.info/#Advanced_station_search
- """
- from urllib.parse import urlencode
- import babel
- from flask_babel import gettext
- from searx.network import get
- from searx.enginelib.traits import EngineTraits
- from searx.locales import language_tag
- traits: EngineTraits
- about = {
- "website": 'https://www.radio-browser.info/',
- "wikidata_id": 'Q111664849',
- "official_api_documentation": 'https://de1.api.radio-browser.info/',
- "use_official_api": True,
- "require_api_key": False,
- "results": 'JSON',
- }
- paging = True
- categories = ['music', 'radio']
- base_url = "https://de1.api.radio-browser.info" # see https://api.radio-browser.info/ for all nodes
- number_of_results = 10
- station_filters = [] # ['countrycode', 'language']
- """A list of filters to be applied to the search of radio stations. By default
- none filters are applied. Valid filters are:
- ``language``
- Filter stations by selected language. For instance the ``de`` from ``:de-AU``
- will be translated to `german` and used in the argument ``language=``.
- ``countrycode``
- Filter stations by selected country. The 2-digit countrycode of the station
- comes from the region the user selected. For instance ``:de-AU`` will filter
- out all stations not in ``AU``.
- .. note::
- RadioBrowser has registered a lot of languages and countrycodes unknown to
- :py:obj:`babel` and note that when searching for radio stations, users are
- more likely to search by name than by region or language.
- """
- def request(query, params):
- args = {
- 'name': query,
- 'order': 'votes',
- 'offset': (params['pageno'] - 1) * number_of_results,
- 'limit': number_of_results,
- 'hidebroken': 'true',
- 'reverse': 'true',
- }
- if 'language' in station_filters:
- lang = traits.get_language(params['searxng_locale']) # type: ignore
- if lang:
- args['language'] = lang
- if 'countrycode' in station_filters:
- if len(params['searxng_locale'].split('-')) > 1:
- countrycode = params['searxng_locale'].split('-')[-1].upper()
- if countrycode in traits.custom['countrycodes']: # type: ignore
- args['countrycode'] = countrycode
- params['url'] = f"{base_url}/json/stations/search?{urlencode(args)}"
- return params
- def response(resp):
- results = []
- json_resp = resp.json()
- for result in json_resp:
- url = result['homepage']
- if not url:
- url = result['url_resolved']
- content = []
- tags = ', '.join(result.get('tags', '').split(','))
- if tags:
- content.append(tags)
- for x in ['state', 'country']:
- v = result.get(x)
- if v:
- v = str(v).strip()
- content.append(v)
- metadata = []
- codec = result.get('codec')
- if codec and codec.lower() != 'unknown':
- metadata.append(f'{codec} ' + gettext('radio'))
- for x, y in [
- (gettext('bitrate'), 'bitrate'),
- (gettext('votes'), 'votes'),
- (gettext('clicks'), 'clickcount'),
- ]:
- v = result.get(y)
- if v:
- v = str(v).strip()
- metadata.append(f"{x} {v}")
- results.append(
- {
- 'url': url,
- 'title': result['name'],
- 'thumbnail': result.get('favicon', '').replace("http://", "https://"),
- 'content': ' | '.join(content),
- 'metadata': ' | '.join(metadata),
- 'iframe_src': result['url_resolved'].replace("http://", "https://"),
- }
- )
- return results
- def fetch_traits(engine_traits: EngineTraits):
- """Fetch languages and countrycodes from RadioBrowser
- - ``traits.languages``: `list of languages API`_
- - ``traits.custom['countrycodes']``: `list of countries API`_
- .. _list of countries API: https://de1.api.radio-browser.info/#List_of_countries
- .. _list of languages API: https://de1.api.radio-browser.info/#List_of_languages
- """
- # pylint: disable=import-outside-toplevel
- from babel.core import get_global
- babel_reg_list = get_global("territory_languages").keys()
- language_list = get(f'{base_url}/json/languages').json() # type: ignore
- country_list = get(f'{base_url}/json/countries').json() # type: ignore
- for lang in language_list:
- babel_lang = lang.get('iso_639')
- if not babel_lang:
- # the language doesn't have any iso code, and hence can't be parsed
- # print(f"ERROR: lang - no iso code in {lang}")
- continue
- try:
- sxng_tag = language_tag(babel.Locale.parse(babel_lang, sep="-"))
- except babel.UnknownLocaleError:
- # print(f"ERROR: language tag {babel_lang} is unknown by babel")
- continue
- eng_tag = lang['name']
- conflict = engine_traits.languages.get(sxng_tag)
- if conflict:
- if conflict != eng_tag:
- print("CONFLICT: babel %s --> %s, %s" % (sxng_tag, conflict, eng_tag))
- continue
- engine_traits.languages[sxng_tag] = eng_tag
- countrycodes = set()
- for region in country_list:
- # country_list contains duplicates that differ only in upper/lower case
- _reg = region['iso_3166_1'].upper()
- if _reg not in babel_reg_list:
- print(f"ERROR: region tag {region['iso_3166_1']} is unknown by babel")
- continue
- countrycodes.add(_reg)
- countrycodes = list(countrycodes)
- countrycodes.sort()
- engine_traits.custom['countrycodes'] = countrycodes
|