From 6edcd1db988bc7b037355579eb32c0a6d69f3e66 Mon Sep 17 00:00:00 2001 From: 0x01FE Date: Thu, 16 Jan 2025 12:18:37 -0600 Subject: [PATCH] writing my own subsonic api library i guess --- Artist.py | 15 ++ ArtistIndex.py | 65 ++++++ SubSonic.py | 330 +++++++++++++++++++++++------ responses/artist_response.xml | 382 ++++++++++++++++++++++++++++++++++ responses/getHelios.xml | 141 +++++++++++++ responses/getIndexes.xml | 297 ++++++++++++++++++++++++++ responses/getYakui.xml | 116 +++++++++++ 7 files changed, 1283 insertions(+), 63 deletions(-) create mode 100644 Artist.py create mode 100644 ArtistIndex.py create mode 100644 responses/artist_response.xml create mode 100644 responses/getHelios.xml create mode 100644 responses/getIndexes.xml create mode 100644 responses/getYakui.xml diff --git a/Artist.py b/Artist.py new file mode 100644 index 0000000..3fa0694 --- /dev/null +++ b/Artist.py @@ -0,0 +1,15 @@ +class Artist: + id: str + name: str + albumCount: int + coverArt: str + artistImageUrl: str + + def __init__(self, id: str, name: str, albumCount: str | int, coverArt: str, artistImageUrl: str): + self.id = id + self.name = name + self.albumCount = int(albumCount) + self.coverArt = coverArt + self.artistImageUrl = artistImageUrl + + diff --git a/ArtistIndex.py b/ArtistIndex.py new file mode 100644 index 0000000..634ae63 --- /dev/null +++ b/ArtistIndex.py @@ -0,0 +1,65 @@ +import xml.etree.ElementTree + +import Artist + + +class ArtistIndex: + name: str + artists: list[Artist.Artist] + + def __init__(self, indexInfo: dict, artists: list[dict]): + self.name = indexInfo['name'] + + self.artists = [] + for artist in artists: + self.artists.append(Artist.Artist( + artist['id'], + artist['name'], + artist['albumCount'], + artist['coverArt'], + artist['artistImageUrl'] + )) + + + +class ArtistIndexes: + indexes: list[ArtistIndex] + + def __init__(self, xml_data: bytes): + self._parseXml(xml_data.decode('utf-8')) + + def _parseXml(self, xml_data: str): + root = xml.etree.ElementTree.fromstring(xml_data) + + self.indexes: list[ArtistIndex] = [] + + if root.get('status') != 'ok': + return None + + in_index = False + artists = [] + index_info = None + for item in root.iter(): + print(item.tag, dict(item.items())) + + if item.tag == '{http://subsonic.org/restapi}index': + + if in_index: + in_index = not in_index + + self.indexes.append(ArtistIndex(index_info, artists)) + + artists = [] + else: + in_index = not in_index + index_info = dict(item.items()) + + elif item.tag == '{http://subsonic.org/restapi}artist': + artists.append(dict(item.items())) + + + + + + + diff --git a/SubSonic.py b/SubSonic.py index 95a437b..cb0cf03 100644 --- a/SubSonic.py +++ b/SubSonic.py @@ -10,76 +10,280 @@ import xml.etree.ElementTree import requests import libsonic import simpleaudio -import pyaudio +# import pyaudio u = 'pseudo' p = 'telegram9dock-MICHEL' - -def saltGenerator() -> str: - return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(8)) - -salt = saltGenerator() -t = hashlib.md5((p + salt).encode('utf-8')).hexdigest() - -payload = { - "u" : u, - "t" : t, - "s" : salt, - "v" : '1.16.1', - "c" : 'testing' -} - -url = 'https://music.0x01fe.net/rest/getArtists?' - -url = url + urllib.parse.urlencode(payload, quote_via=urllib.parse.quote_plus) - -# print(url) -# response = requests.get(url) - -# with open("artist_response.xml", 'wb') as file: -# file.write(response.content) - - - class Artist: - id: str - imageUrl: str name: str albumCount: int + coverArt: str + artistImageUrl: str - def __init__(self): - pass + def __init__(self, id: str, name: str, albumCount: str | int, coverArt: str, artistImageUrl: str): + self.id = id + self.name = name + self.albumCount = int(albumCount) + self.coverArt = coverArt + self.artistImageUrl = artistImageUrl - def fromDict(self, d: dict): - self.id = d['id'] - self.imageUrl = d['artistImageUrl'] - self.name = d['name'] - self.albumCount = d['albumCount'] +class ArtistIndex: + name: str + artists: list[Artist] -def getArtists() -> list[Artist]: - tree = xml.etree.ElementTree.parse("artist_response.xml") - root = tree.getroot() + def __init__(self, indexInfo: dict, artists: list[dict]): + self.name = indexInfo['name'] - artists: list[Artist] = [] + self.artists = [] + for artist in artists: + self.artists.append(Artist( + artist['id'], + artist['name'], + artist['albumCount'], + artist['coverArt'], + artist['artistImageUrl'] + )) - if root.get('status') != 'ok': - return [] +class ArtistIndexes: + indexes: list[ArtistIndex] - for item in root.iter(): - print(item.tag, dict(item.items())) + def __init__(self, xml_data: bytes): + self._parseXml(xml_data.decode('utf-8')) - if item.tag == '{http://subsonic.org/restapi}artist': - a = Artist() - a.fromDict(dict(item.items())) + def _parseXml(self, xml_data: str): + root = xml.etree.ElementTree.fromstring(xml_data) - artists.append(a) + self.indexes: list[ArtistIndex] = [] + + if root.get('status') != 'ok': + return None + + in_index = False + artists = [] + index_info = None + for item in root.iter(): + # print(item.tag, dict(item.items())) + + if item.tag == '{http://subsonic.org/restapi}index': + + if in_index: + in_index = not in_index + + self.indexes.append(ArtistIndex(index_info, artists)) + + artists = [] + else: + in_index = not in_index + index_info = dict(item.items()) + + elif item.tag == '{http://subsonic.org/restapi}artist': + artists.append(dict(item.items())) + +class Album: + id: str + title: str + name: str + album: str + coverArtId: str + duration: int + artistId: str + songCount: int + + def __init__(self, album_info: dict): + self.id = album_info['id'] + self.title = album_info['title'] + self.name = album_info['name'] + self.album = album_info['album'] + self.coverArtId = album_info['coverArt'] + self.duration = int(album_info['duration']) + self.artistId = album_info['artistId'] + self.songCount = int(album_info['songCount']) + +class Song: + id: str + title: str + album: str + artist: str + trackNumber: int + contentType: str + suffix: str + duration: int + bitRate: int + path: str + discNumber: int + albumId: str + artistId: str + comment: str + channelCount: int + samplingRate: int + + def __init__(self, song_info: dict): + self.id = song_info['id'] + self.title = song_info['title'] + self.album = song_info['album'] + self.artist = song_info['artist'] + self.trackNumber = int(song_info['track']) + self.contentType = song_info['contentType'] + self.suffix = song_info['suffix'] + self.duration = int(song_info['duration']) + self.bitRate = int(song_info['bitRate']) + self.path = song_info['path'] + self.discNumber = int(song_info['discNumber']) + self.albumId = song_info['albumId'] + self.artistId = song_info['artistId'] + self.comment = song_info['comment'] + self.channelCount = int(song_info['channelCount']) + self.samplingRate = int(song_info['samplingRate']) + +class SubpyConn: + username: str + password: str + base_url: str + salt: str + salt_len: int = 8 + version: str = "1.16.1" + client_name: str = "testing" + + def __init__(self, u: str, p: str, base_url: str, salt_len: int | None = 8): + self.username = u + self.password = p + self.base_url = base_url + + self.salt_len = salt_len + + def _saltGenerator(self) -> None: + self.salt = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(self.salt_len)) + + def _hashPass(self) -> str: + self._saltGenerator() + return hashlib.md5((self.password + self.salt).encode('utf-8')).hexdigest() + + def _getBasePayload(self) -> dict: + return { + "u" : self.username, + "t" : self._hashPass(), + "s" : self.salt, + "v" : self.version, + "c" : self.client_name + } + + def _encodePayload(self, payload: dict) -> str: + return urllib.parse.urlencode(payload, quote_via=urllib.parse.quote_plus) + + def getIndexes(self) -> ArtistIndexes: + url = self.base_url + '/rest/getIndexes?' + + return ArtistIndexes(requests.get(url + self._encodePayload(self._getBasePayload())).content) + + def _getMusicDirectory(self, id: str): + url = self.base_url + '/rest/getMusicDirectory?' + + payload = self._getBasePayload() + payload['id'] = id + + return requests.get(url + self._encodePayload(payload)) + + def getArtistAlbums(self, artist_id: str) -> list[Album]: + r: requests.Response = self._getMusicDirectory(artist_id) + xml_data: str = r.content.decode('utf-8') + + root: xml.etree.ElementTree.Element = xml.etree.ElementTree.fromstring(xml_data) + albums: list[Album] = [] + + for item in root.iter(): + # print(item.tag, dict(item.items())) + + if item.tag == '{http://subsonic.org/restapi}child': + albums.append(Album(dict(item.items()))) + + return albums + + def getAlbumSongs(self, album_id: str) -> list[Song]: + r: requests.Response = self._getMusicDirectory(album_id) + xml_data: str = r.content.decode('utf-8') + + root: xml.etree.ElementTree.ElementTree = xml.etree.ElementTree.fromstring(xml_data) + songs: list[Song] = [] + + for item in root.iter(): + + if item.tag == '{http://subsonic.org/restapi}child': + songs.append(Song(dict(item.items()))) + + return songs + + def stream(self, song_id: str): + url = self.base_url + '/rest/stream?' + + payload = self._getBasePayload() + + payload['id'] = song_id + + return requests.get(url + self._encodePayload(payload)) + +conn = SubpyConn(u, p, "https://music.0x01fe.net") - return artists +# artistIndexes = conn.getIndexes() +# for artistIndex in artistIndexes.indexes: +# print(artistIndex.name, artistIndex.artists) + +helios_id = 'c69cdc593487ed813f1e11ecb270b228' +yakui_id = "7f6ca9951af294420382442061a2d6ee" +# r = conn.getMusicDirectory(helios_id) +# with open("getHelios.xml", 'wb') as file: +# file.write(r.content) + +# yakui_albums = conn.getArtistAlbums(yakui_id) + +# for album in yakui_albums: +# print(album.name) + +helios_songs = conn.getAlbumSongs(helios_id) +for song in helios_songs: + print(song.trackNumber, song.title) + +# class Artist: + +# id: str +# imageUrl: str +# name: str +# albumCount: int + +# def __init__(self): +# pass + +# def fromDict(self, d: dict): +# self.id = d['id'] +# self.imageUrl = d['artistImageUrl'] +# self.name = d['name'] +# self.albumCount = d['albumCount'] + +# def getArtists() -> list[Artist]: +# tree = xml.etree.ElementTree.parse("artist_response.xml") +# root = tree.getroot() + +# artists: list[Artist] = [] + +# if root.get('status') != 'ok': +# return [] + +# for item in root.iter(): +# print(item.tag, dict(item.items())) + +# if item.tag == '{http://subsonic.org/restapi}artist': +# a = Artist() +# a.fromDict(dict(item.items())) + +# artists.append(a) + + + +# return artists @@ -89,26 +293,26 @@ def getArtists() -> list[Artist]: # print(artist.name) -conn = libsonic.Connection('https://music.0x01fe.net', 'pseudo', p, port=443) +# conn = libsonic.Connection('https://music.0x01fe.net', 'pseudo', p, port=443) -song = conn.getRandomSongs(1)['randomSongs']['song'][0] -song_id = song['id'] -rate = song['samplingRate'] +# song = conn.getRandomSongs(1)['randomSongs']['song'][0] +# song_id = song['id'] +# rate = song['samplingRate'] -print(song_id) +# print(song_id) -stream = conn.stream(song_id) +# stream = conn.stream(song_id) -while not stream.fp: - pass +# while not stream.fp: +# pass -pya = pyaudio.PyAudio() -pya_stream = pya.open(format=) -wave_obj = simpleaudio.WaveObject(stream.fp) -play_obj = wave_obj.play() -play_obj.wait_done() +# pya = pyaudio.PyAudio() +# pya_stream = pya.open(format=) +# wave_obj = simpleaudio.WaveObject(stream.fp) +# play_obj = wave_obj.play() +# play_obj.wait_done() diff --git a/responses/artist_response.xml b/responses/artist_response.xml new file mode 100644 index 0000000..fa0272e --- /dev/null +++ b/responses/artist_response.xml @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/responses/getHelios.xml b/responses/getHelios.xml new file mode 100644 index 0000000..6e4bbe7 --- /dev/null +++ b/responses/getHelios.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/responses/getIndexes.xml b/responses/getIndexes.xml new file mode 100644 index 0000000..4f46f50 --- /dev/null +++ b/responses/getIndexes.xml @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/responses/getYakui.xml b/responses/getYakui.xml new file mode 100644 index 0000000..c16a748 --- /dev/null +++ b/responses/getYakui.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file