deemix-js/deemix/decryption.js

166 lines
5.2 KiB
JavaScript
Raw Normal View History

2021-04-12 18:40:39 +00:00
const got = require('got')
const fs = require('fs')
2021-07-20 12:44:19 +00:00
const {_md5, _ecbCrypt, _ecbDecrypt, generateBlowfishKey, decryptChunk} = require('./utils/crypto.js')
2021-08-02 19:33:58 +00:00
const { DownloadCanceled, DownloadEmpty} = require('./errors.js')
2021-04-12 18:52:15 +00:00
const { USER_AGENT_HEADER, pipeline } = require('./utils/index.js')
function generateStreamPath(sngID, md5, mediaVersion, format){
let urlPart = md5+"¤"+format+"¤"+sngID+"¤"+mediaVersion
let md5val = _md5(urlPart)
let step2 = md5val+"¤"+urlPart+"¤"
2021-06-08 17:35:56 +00:00
step2 += '.'.repeat(16 - (step2.length % 16))
urlPart = _ecbCrypt('jo6aey6haid2Teih', step2)
return urlPart
}
function reverseStreamPath(urlPart){
let step2 = _ecbDecrypt('jo6aey6haid2Teih', urlPart)
let [, md5, format, sngID, mediaVersion] = step2.split("¤")
return [sngID, md5, mediaVersion, format]
}
2021-07-20 12:44:19 +00:00
function generateCryptedStreamURL(sngID, md5, mediaVersion, format){
let urlPart = generateStreamPath(sngID, md5, mediaVersion, format)
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart
}
function generateStreamURL(sngID, md5, mediaVersion, format){
let urlPart = generateStreamPath(sngID, md5, mediaVersion, format)
2021-07-19 16:42:43 +00:00
return "https://cdns-proxy-" + md5[0] + ".dzcdn.net/api/1/" + urlPart
}
function reverseStreamURL(url){
2021-04-21 17:00:51 +00:00
let urlPart = url.slice(url.find("/1/")+3)
return reverseStreamPath(urlPart)
}
async function streamTrack(writepath, track, downloadObject, listener){
if (downloadObject && downloadObject.isCanceled) throw new DownloadCanceled
2021-04-12 18:40:39 +00:00
let headers = {'User-Agent': USER_AGENT_HEADER}
let chunkLength = 0
let complete = 0
2021-07-25 10:19:57 +00:00
let isCryptedStream = track.downloadURL.includes("/mobile/") || track.downloadURL.includes("/media/")
2021-07-20 12:44:19 +00:00
let blowfishKey
let outputStream = fs.createWriteStream(writepath)
2021-06-07 18:14:41 +00:00
let itemData = {
id: track.id,
title: track.title,
artist: track.mainArtist.name
}
let error = ''
2021-07-20 12:44:19 +00:00
if (isCryptedStream) blowfishKey = generateBlowfishKey(String(track.id))
async function* decrypter(source){
let modifiedStream = Buffer.alloc(0)
for await (let chunk of source){
if (!isCryptedStream){
yield chunk
} else {
modifiedStream = Buffer.concat([modifiedStream, chunk])
while (modifiedStream.length >= 2048 * 3){
let decryptedChunks = Buffer.alloc(0)
let decryptingChunks = modifiedStream.slice(0, 2048 * 3)
modifiedStream = modifiedStream.slice(2048 * 3)
if (decryptingChunks.length >= 2048){
decryptedChunks = decryptChunk(decryptingChunks.slice(0, 2048), blowfishKey)
decryptedChunks = Buffer.concat([decryptedChunks, decryptingChunks.slice(2048)])
}
yield decryptedChunks
}
}
}
if (isCryptedStream){
let decryptedChunks = Buffer.alloc(0)
if (modifiedStream.length >= 2048){
decryptedChunks = decryptChunk(modifiedStream.slice(0, 2048), blowfishKey)
decryptedChunks = Buffer.concat([decryptedChunks, modifiedStream.slice(2048)])
2021-07-25 14:11:44 +00:00
yield decryptedChunks
}else{
yield modifiedStream
2021-07-20 12:44:19 +00:00
}
}
}
async function* depadder(source){
let isStart = true
for await (let chunk of source){
2022-01-04 21:06:44 +00:00
if (isStart && chunk[0] == 0 && chunk.slice(4, 8).toString() !== "ftyp"){
let i
for (i = 0; i < chunk.length; i++){
let byte = chunk[i]
if (byte !== 0) break
}
chunk = chunk.slice(i)
}
isStart = false
yield chunk
}
}
let request = got.stream(track.downloadURL, {
headers: headers,
timeout: 7000,
2021-09-30 17:00:36 +00:00
https: {rejectUnauthorized: false}
}).on('response', (response)=>{
2021-04-12 17:32:57 +00:00
complete = parseInt(response.headers["content-length"])
if (complete == 0) {
error = "DownloadEmpty"
request.destroy()
}
if (listener) listener.send('downloadInfo', {
uuid: downloadObject.uuid,
data: itemData,
state: "downloading",
alreadyStarted: false,
value: complete
})
2021-05-13 16:11:43 +00:00
}).on('data', function(chunk){
if (downloadObject.isCanceled) {
error = "DownloadCanceled"
request.destroy()
}
2021-05-13 16:11:43 +00:00
chunkLength += chunk.length
2021-04-12 18:40:39 +00:00
2021-05-13 16:11:43 +00:00
if (downloadObject){
downloadObject.progressNext += ((chunk.length / complete) / downloadObject.size) * 100
2021-05-13 16:11:43 +00:00
downloadObject.updateProgress(listener)
}
})
2021-04-12 18:40:39 +00:00
try {
await pipeline(request, decrypter, depadder, outputStream)
} catch (e){
outputStream.close()
if (e instanceof got.ReadError || e instanceof got.TimeoutError || ["ESOCKETTIMEDOUT", "ERR_STREAM_PREMATURE_CLOSE", "ETIMEDOUT"].includes(e.code)){
if (downloadObject && chunkLength != 0){
downloadObject.progressNext -= ((chunkLength / complete) / downloadObject.size) * 100
downloadObject.updateProgress(listener)
}
return await streamTrack(writepath, track, downloadObject, listener)
} else if (request.destroyed) {
switch (error) {
case 'DownloadEmpty': throw new DownloadEmpty
case 'DownloadCanceled': throw new DownloadCanceled
2021-05-19 21:03:47 +00:00
default: throw e
}
} else {
console.trace(e)
throw e
}
}
outputStream.close()
}
module.exports = {
generateStreamPath,
generateStreamURL,
2021-07-20 12:44:19 +00:00
generateCryptedStreamURL,
reverseStreamPath,
reverseStreamURL,
2021-08-02 19:33:58 +00:00
streamTrack
}