import requests import logging 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 server 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) 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) logging.info(info) return info[0].get('starred') if info[0].get('starred') else False """ A strange one. Used for reordering tracks on a playlist, since it's not a native SubSonic thing. playlist_id (str): Id of the playlist you're updating song_index (int): Track you want to move insert_before_index (int): For example, if you want to move a track to the start of a playlist, this is 1. RETURNS: index of the editting song (song_index) """ def playlistInsertTrackBefore(self, playlist_id: str, song_index: int, insert_before_index: int) -> int: url = self.base_url + f'/api/playlist/{playlist_id}/tracks/{song_index}' payload = { "insert_before" : str(insert_before_index) } response = requests.put(url, headers=self.auth_header, json=payload) return response['id']