deemix-py/deemix/decryption.py

157 lines
6.5 KiB
Python
Raw Normal View History

2021-04-10 11:53:52 +02:00
from ssl import SSLError
from time import sleep
import logging
2021-03-19 15:44:21 +01:00
from requests import get
2021-06-07 20:25:51 +02:00
from requests.exceptions import ConnectionError as RequestsConnectionError, ReadTimeout, ChunkedEncodingError
2021-03-19 15:44:21 +01:00
from urllib3.exceptions import SSLError as u3SSLError
2021-06-07 20:25:51 +02:00
from deemix.utils.crypto import _md5, _ecbCrypt, _ecbDecrypt, generateBlowfishKey, decryptChunk
from deemix.utils import USER_AGENT_HEADER
2021-04-10 11:53:52 +02:00
from deemix.types.DownloadObjects import Single
2021-03-19 15:44:21 +01:00
logger = logging.getLogger('deemix')
2021-04-10 11:53:52 +02:00
def generateStreamPath(sng_id, md5, media_version, media_format):
urlPart = b'\xa4'.join(
2021-04-10 11:53:52 +02:00
[md5.encode(), str(media_format).encode(), str(sng_id).encode(), str(media_version).encode()])
md5val = _md5(urlPart)
2021-04-10 11:53:52 +02:00
step2 = md5val.encode() + b'\xa4' + urlPart + b'\xa4'
step2 = step2 + (b'.' * (16 - (len(step2) % 16)))
2021-06-07 20:25:51 +02:00
urlPart = _ecbCrypt('jo6aey6haid2Teih', step2)
2021-03-19 14:31:32 +01:00
return urlPart.decode("utf-8")
2021-03-19 14:31:32 +01:00
def reverseStreamPath(urlPart):
2021-06-07 20:25:51 +02:00
step2 = _ecbDecrypt('jo6aey6haid2Teih', urlPart)
2021-04-10 11:53:52 +02:00
(_, md5, media_format, sng_id, media_version, _) = step2.split(b'\xa4')
return (sng_id.decode('utf-8'), md5.decode('utf-8'), media_version.decode('utf-8'), media_format.decode('utf-8'))
2021-03-19 14:31:32 +01:00
2021-06-07 20:25:51 +02:00
def generateCryptedStreamURL(sng_id, md5, media_version, media_format):
2021-04-10 11:53:52 +02:00
urlPart = generateStreamPath(sng_id, md5, media_version, media_format)
2021-03-19 14:31:32 +01:00
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart
2021-06-07 20:25:51 +02:00
def generateStreamURL(sng_id, md5, media_version, media_format):
2021-04-10 11:53:52 +02:00
urlPart = generateStreamPath(sng_id, md5, media_version, media_format)
2021-03-19 14:31:32 +01:00
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/api/1/" + urlPart
def reverseStreamURL(url):
urlPart = url[url.find("/1/")+3:]
2021-04-10 11:53:52 +02:00
return reverseStreamPath(urlPart)
2021-03-19 15:44:21 +01:00
2021-06-07 20:25:51 +02:00
def streamTrack(outputStream, track, start=0, downloadObject=None, listener=None):
if downloadObject.isCanceled: raise DownloadCanceled
2021-03-19 15:44:21 +01:00
headers= {'User-Agent': USER_AGENT_HEADER}
chunkLength = start
itemName = f"[{track.mainArtist.name} - {track.title}]"
try:
with get(track.downloadUrl, headers=headers, stream=True, timeout=10) as request:
request.raise_for_status()
complete = int(request.headers["Content-Length"])
if complete == 0: raise DownloadEmpty
if start != 0:
responseRange = request.headers["Content-Range"]
2021-06-07 20:25:51 +02:00
if listener:
listener.send('downloadInfo', {
'uuid': downloadObject.uuid,
'itemName': itemName,
'state': "downloading",
'alreadyStarted': True,
'value': responseRange
})
2021-03-19 15:44:21 +01:00
else:
2021-06-07 20:25:51 +02:00
if listener:
listener.send('downloadInfo', {
'uuid': downloadObject.uuid,
'itemName': itemName,
'state': "downloading",
'alreadyStarted': False,
'value': complete
})
2021-03-19 15:44:21 +01:00
for chunk in request.iter_content(2048 * 3):
outputStream.write(chunk)
chunkLength += len(chunk)
if downloadObject:
if isinstance(downloadObject, Single):
2021-06-07 20:25:51 +02:00
chunkProgres = (chunkLength / (complete + start)) * 100
downloadObject.progressNext = chunkProgres
2021-03-19 15:44:21 +01:00
else:
chunkProgres = (len(chunk) / (complete + start)) / downloadObject.size * 100
downloadObject.progressNext += chunkProgres
2021-06-07 20:25:51 +02:00
downloadObject.updateProgress(listener)
2021-03-19 15:44:21 +01:00
2021-04-10 11:53:52 +02:00
except (SSLError, u3SSLError):
logger.info('%s retrying from byte %s', itemName, chunkLength)
2021-06-07 20:25:51 +02:00
streamTrack(outputStream, track, chunkLength, downloadObject, listener)
except (RequestsConnectionError, ReadTimeout, ChunkedEncodingError):
2021-03-19 15:44:21 +01:00
sleep(2)
2021-06-07 20:25:51 +02:00
streamTrack(outputStream, track, start, downloadObject, listener)
2021-03-19 15:44:21 +01:00
2021-06-07 20:25:51 +02:00
def streamCryptedTrack(outputStream, track, start=0, downloadObject=None, listener=None):
if downloadObject.isCanceled: raise DownloadCanceled
2021-03-19 15:44:21 +01:00
headers= {'User-Agent': USER_AGENT_HEADER}
chunkLength = start
itemName = f"[{track.mainArtist.name} - {track.title}]"
try:
with get(track.downloadUrl, headers=headers, stream=True, timeout=10) as request:
request.raise_for_status()
blowfish_key = str.encode(generateBlowfishKey(str(track.id)))
complete = int(request.headers["Content-Length"])
if complete == 0: raise DownloadEmpty
if start != 0:
responseRange = request.headers["Content-Range"]
2021-06-07 20:25:51 +02:00
if listener:
listener.send('downloadInfo', {
'uuid': downloadObject.uuid,
'itemName': itemName,
'state': "downloading",
'alreadyStarted': True,
'value': responseRange
})
2021-03-19 15:44:21 +01:00
else:
2021-06-07 20:25:51 +02:00
if listener:
listener.send('downloadInfo', {
'uuid': downloadObject.uuid,
'itemName': itemName,
'state': "downloading",
'alreadyStarted': False,
'value': complete
})
2021-03-19 15:44:21 +01:00
for chunk in request.iter_content(2048 * 3):
if len(chunk) >= 2048:
2021-06-07 20:25:51 +02:00
chunk = decryptChunk(blowfish_key, chunk[0:2048]) + chunk[2048:]
2021-03-19 15:44:21 +01:00
outputStream.write(chunk)
chunkLength += len(chunk)
if downloadObject:
if isinstance(downloadObject, Single):
2021-06-07 20:25:51 +02:00
chunkProgres = (chunkLength / (complete + start)) * 100
downloadObject.progressNext = chunkProgres
2021-03-19 15:44:21 +01:00
else:
chunkProgres = (len(chunk) / (complete + start)) / downloadObject.size * 100
downloadObject.progressNext += chunkProgres
2021-06-07 20:25:51 +02:00
downloadObject.updateProgress(listener)
2021-03-19 15:44:21 +01:00
2021-04-10 11:53:52 +02:00
except (SSLError, u3SSLError):
logger.info('%s retrying from byte %s', itemName, chunkLength)
2021-06-07 20:25:51 +02:00
streamCryptedTrack(outputStream, track, chunkLength, downloadObject, listener)
except (RequestsConnectionError, ReadTimeout, ChunkedEncodingError):
2021-03-19 15:44:21 +01:00
sleep(2)
2021-06-07 20:25:51 +02:00
streamCryptedTrack(outputStream, track, start, downloadObject, listener)
class DownloadCanceled(Exception):
pass
2021-03-19 15:44:21 +01:00
class DownloadEmpty(Exception):
pass