"""SomaFM: the station catalogue Beefy Player was born with. Everything SomaFM-specific lives in this package — the ``channels.json`` schema, the ``songs/.json`` history, the ``.pls`` layout and the guessed icecast mirrors. The rest of the app only sees :class:`SomaFMProvider`, which implements :class:`beefy_player.providers.base.RadioProvider`. """ from __future__ import annotations import httpx from ... import config from ...models import Song, Station from ..base import RadioProvider from . import api, fallback from . import hero as hero_art __all__ = ["SomaFMProvider"] class SomaFMProvider(RadioProvider): id = "somafm" name = "SomaFM" homepage = "somafm.com" brand = "SOMA FM" tagline = "listener-supported radio · san francisco" station_source = "somafm.com/channels.json" history_source = "songs/.json" hero = hero_art.LOGO hero_gradient = True hero_lines = hero_art.TAGLINES @property def user_agent(self) -> str: # SomaFM asks clients to identify themselves, so say who we are and # where we come from. return f"{config.USER_AGENT}; +https://somafm.com" async def fetch_stations(self, client: httpx.AsyncClient) -> list[Station]: return await api.fetch_channels(client) def load_cached_stations(self) -> list[Station]: return api.load_cached_channels() def fallback_stations(self) -> list[Station]: return api.parse_channels({"channels": fallback.fallback_channels()}) async def fetch_history( self, client: httpx.AsyncClient, station_id: str ) -> list[Song]: return await api.fetch_history(client, station_id) def load_cached_history(self, station_id: str) -> list[Song]: return api.load_cached_songs(station_id) def fallback_urls(self, station: Station) -> list[str]: return api.synthetic_mirrors(station.id)