import requests import urllib.parse import SubSonic """ Navidrome has some API endpoints that are not included in the SubSonic or OpenSubSonic standards. Nor are they documented anywhere I can find, the NavidromeAPI class is a collection of methods to interact with this hidden API. The endpoints are reverse engineered from observing requests made by the Navidrome WebUI. """ class NavidromeAPI: username: str password: str base_url: str auth_token: str auth_header: dict = {} def __init__(self, u: str, p: str, base_url: str): self.username = u self.password = p self.base_url = base_url self.auth_token = self._login() self.auth_header['X-Nd-Authorization'] = f"Bearer {self.auth_token}" def _encodePayload(self, payload: dict) -> str: return urllib.parse.urlencode(payload, quote_via=urllib.parse.quote_plus) def _login(self) -> str: url = self.base_url + '/auth/login' payload = { 'username': self.username, 'password': self.password } response = requests.post(url, json=payload) j = response.json() return j['token'] def getAllSongs(self, end: int, start: int, order: str = 'ASC', sort: str = 'title') -> list[SubSonic.Song]: url = self.base_url + '/api/song?' payload = { "_end": end, "_order": order, "_sort": sort, "_start": start } response = requests.get(url + self._encodePayload(payload), headers=self.auth_header) print(len(response.content)) return [SubSonic.Song(song_d) for song_d in response.json()] def getAlbums(self, end: int, start: int, order: str = 'ACS', sort: str = 'name', name: str | None = None, seed: float | None = None) -> list[SubSonic.Album]: url = self.base_url + '/api/album?' payload = { "_end": end, "_order": order, "_sort" : sort, "_start": start, "seed": 0.0 } if seed: payload['seed'] = seed if name: payload['name'] = name response = requests.get(url + self._encodePayload(payload), headers=self.auth_header) print(response.content) return [SubSonic.Album(album_d) for album_d in response.json()] def getSong(self, id: str): url = self.base_url + '/api/song?' payload = { "id" : id } response = requests.get(url + self._encodePayload(payload), headers=self.auth_header) return response.json() def isSongStarred(self, id: str) -> bool: info = self.getSong(id) return info[0]['starred']