import html import venigma import os import base64 import re import hashlib class AudioListItem: def __init__(self, json_item): self._jitem = json_item def id(self): return self._jitem["audio_raw_id"] def key(self): return self._jitem["access_key"] def cid(self): return f"{self.id()}_{self.key()}" def _code(self): return self._jitem["track_code"] def audio_list(plain_list): return [AudioListItem(item) for item in plain_list["payload"][1][0]] class AudioDataItem: def __init__(self, json_data): self._jdata = json_data self._manifest = None def cid(self): return f"{self._jdata[1]}_{self._jdata[0]}" def index(self): return base64.b16encode(self.hindex().encode()).decode() def cursed_url(self): return self._jdata[2] def blessed_url(self, config): return venigma.decode(config, self.cursed_url()) def root_url(self, config): blessed_url = self.blessed_url(config) parts = blessed_url.split("/") return "/".join(parts[:-1]) + "/" def title(self): return html.unescape(self._jdata[3]) def artist(self): return html.unescape(self._jdata[4]) def img(self): return self._jdata[14] def hindex(self): return f"{self.title()} - {self.artist()}" def findex(self, config): return f"{config.audio_directory()}{self.cid()}.mp3" def _hash(self, manifest_hash, mp3): hash = hashlib.sha256(manifest_hash) hash.update(mp3) return hash.hexdigest() def _hashfile(self, config): return f"{self.manifest().directory(config)}sha256sum" def hash(self, config): f = open(self._hashfile(config), "w+") fmp3 = open(self.findex(config), "rb") manifest_hash = self.manifest().hashed(config) h = self._hash(manifest_hash.encode(), fmp3.read()) fmp3.close() f.write(h) f.close() return h def hashed(self, config): hashfile = self._hashfile(config) findex = self.findex(config) manifest_hash = self.manifest().hashed(config) if ( not os.path.isfile(hashfile) or not os.path.isfile(findex) or not manifest_hash ): return None fmp3 = open(findex, "rb") h = self._hash(manifest_hash.encode(), fmp3.read()) fmp3.close() f = open(hashfile, "r") fh = f.read() f.close() return h if h == fh else None def manifest(self, manifest=None): if manifest is None: return self._manifest else: self._manifest = manifest if manifest.data() is None: manifest.data(self) def audio_data(plain_data): return [AudioDataItem(data) for data in plain_data["payload"][1][0]] class Manifest: def __init__(self, raw_content): self._rcontent = raw_content self._data = None def load(config, data): index = Manifest._index_for(config, data) if os.path.isfile(index): f = open(index, "rb") manifest = Manifest(f.read()) f.close() data.manifest(manifest) return manifest else: return None def content(self): return self._rcontent def _directory_for(config, data): directory = config.manifests_directory() return f"{directory}{data.index()}/" def directory(self, config): return Manifest._directory_for(config, self.data()) def _index_for(config, data): return f"{Manifest._directory_for(config, data)}index.m3u8" def index(self, config): return Manifest._index_for(config, self.data()) def store(self, config): directory = self.directory(config) if not os.path.exists(directory): os.makedirs(directory) f = open(self.index(config), "wb+") f.write(self.content()) f.close() def chunks(self): regex = r"seg\-\d+\-\w+\.ts\?siren=\d" return re.findall(regex, self.content().decode("utf-8")) def _hash(self, chunks): hash = hashlib.sha1(self.content()) for chunk in chunks: if not chunk: continue hash.update(chunk.data()) return hash.hexdigest() def _hashfile(self, config): return f"{self.directory(config)}sha1sum" def hash(self, config, chunks): f = open(self._hashfile(config), "w+") h = self._hash(chunks) f.write(h) f.close() return h def hashed(self, config): hashfile = self._hashfile(config) chunks = [Chunk.load(config, self, chunk) for chunk in self.chunks()] if not os.path.isfile(hashfile) or any(chunks) is None: return None h = self._hash(chunks) f = open(hashfile, "r") fh = f.read() f.close() return h if h == fh else None def data(self, data=None): if data is None: return self._data else: self._data = data if data.manifest() is None: data.manifest(self) class Chunk: def __init__(self, manifest, name, raw_data): self._name = name self._rdata = raw_data self._manifest = manifest def _index_for(config, manifest, name): return f"{manifest.directory(config)}{name}" def load(config, manifest, name): index = Chunk._index_for(config, manifest, name) if os.path.isfile(index): f = open(index, "rb") chunk = Chunk(manifest, name, f.read()) f.close() return chunk else: return None def name(self): return self._name def data(self): return self._rdata def manifest(self): return self._manifest def store(self, config): f = open(f"{Chunk._index_for(config, self.manifest(), self.name())}", "wb+") f.write(self.data()) f.close()