mirror of
https://gitlab.com/RemixDev/deemix-js.git
synced 2024-12-26 17:36:12 +00:00
Partially implemented decryption and continued working on downloader
Also added eslint
This commit is contained in:
parent
a6383eb5f4
commit
1f2fddc693
13
.eslintrc.json
Normal file
13
.eslintrc.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"env": {
|
||||
"commonjs": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
120
deemix/decryption.js
Normal file
120
deemix/decryption.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
const crypto = require('crypto')
|
||||
const got = require('got');
|
||||
|
||||
function _md5 (data, type = 'binary') {
|
||||
let md5sum = crypto.createHash('md5')
|
||||
md5sum.update(Buffer.from(data, type))
|
||||
return md5sum.digest('hex')
|
||||
}
|
||||
|
||||
function _ecbCrypt (key, data) {
|
||||
let cipher = crypto.createCipheriv("aes-128-ecb", Buffer.from(key), Buffer.from(""));
|
||||
return Buffer.concat([cipher.update(data, 'binary'), cipher.final()]).toString("hex").toLowerCase();
|
||||
}
|
||||
|
||||
function _ecbDecrypt (key, data) {
|
||||
let cipher = crypto.createDecipheriv("aes-128-ecb", Buffer.from(key), Buffer.from(""));
|
||||
return Buffer.concat([cipher.update(data, 'binary'), cipher.final()]).toString("hex").toLowerCase();
|
||||
}
|
||||
|
||||
function generateStreamPath(sngID, md5, mediaVersion, format){
|
||||
let urlPart = md5+"¤"+format+"¤"+sngID+"¤"+mediaVersion
|
||||
let md5val = _md5(urlPart)
|
||||
let step2 = md5val+"¤"+urlPart+"¤"
|
||||
step2 += ('.' * (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]
|
||||
}
|
||||
|
||||
function generateStreamURL(sngID, md5, mediaVersion, format){
|
||||
let urlPart = generateStreamPath(sngID, md5, mediaVersion, format)
|
||||
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/api/1/" + urlPart
|
||||
}
|
||||
|
||||
function reverseStreamURL(url){
|
||||
let urlPart = url.substring(url.find("/1/")+3)
|
||||
return reverseStreamPath(urlPart)
|
||||
}
|
||||
|
||||
function streamTrack(outputStream, track, start=0, downloadObject, listener){
|
||||
let headers = {'User-Agent': ""}
|
||||
let chunkLength = start
|
||||
let complete = 0
|
||||
|
||||
got.get(track.downloadUrl, {
|
||||
headers: headers,
|
||||
stream: true,
|
||||
timeout: 10000
|
||||
}).on('response', (response)=>{
|
||||
complete = parseInt(response.headers["Content-Length"])
|
||||
if (complete == 0) throw new DownloadEmpty
|
||||
if (start != 0){
|
||||
let responseRange = response.headers["Content-Range"]
|
||||
console.log(`downloading range ${responseRange}`)
|
||||
}else {
|
||||
console.log(`downloading ${complete} bytes`)
|
||||
}
|
||||
}).on("data", (data)=>{
|
||||
outputStream.write(data)
|
||||
chunkLength += data.length
|
||||
|
||||
if (downloadObject){
|
||||
let chunkProgres
|
||||
if (downloadObject.__type__ === "Single"){
|
||||
chunkProgres = (chunkLength / (complete + start)) * 100
|
||||
downloadObject.progressNext = chunkProgres
|
||||
}else{
|
||||
chunkProgres = (data.length / (complete + start)) / downloadObject.size * 100
|
||||
downloadObject.progressNext += chunkProgres
|
||||
}
|
||||
downloadObject.updateProgress(listener)
|
||||
}
|
||||
}).on("error", (error)=>{
|
||||
console.error(error)
|
||||
return streamTrack(outputStream, track, chunkLength, downloadObject, listener)
|
||||
})
|
||||
}
|
||||
|
||||
class DownloadEmpty extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "DownloadEmpty"
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function generateBlowfishKey(trackId) {
|
||||
const SECRET = 'g4el58wc0zvf9na1';
|
||||
const idMd5 = _md5(trackId.toString(), 'ascii')
|
||||
let bfKey = ''
|
||||
for (let i = 0; i < 16; i++) {
|
||||
bfKey += String.fromCharCode(idMd5.charCodeAt(i) ^ idMd5.charCodeAt(i + 16) ^ SECRET.charCodeAt(i))
|
||||
}
|
||||
return bfKey;
|
||||
}
|
||||
|
||||
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 decryptChunk(chunk, blowFishKey){
|
||||
var cipher = crypto.createDecipheriv('bf-cbc', blowFishKey, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]))
|
||||
cipher.setAutoPadding(false)
|
||||
return cipher.update(chunk, 'binary', 'binary') + cipher.final()
|
||||
}
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
generateStreamPath,
|
||||
generateStreamURL,
|
||||
reverseStreamPath,
|
||||
reverseStreamURL,
|
||||
streamTrack
|
||||
}
|
|
@ -1,3 +1,69 @@
|
|||
const { Track } = require('./types/Track.js')
|
||||
const { streamTrack, generateStreamURL } = require('./decryption.js')
|
||||
const { TrackFormats } = require('deezer-js')
|
||||
|
||||
function getPreferredBitrate(track, bitrate, shouldFallback, uuid, listener){
|
||||
bitrate = parseInt(bitrate)
|
||||
if (track.localTrack) { return TrackFormats.LOCAL }
|
||||
|
||||
let falledBack = false
|
||||
|
||||
const formats_non_360 = {
|
||||
"FLAC": TrackFormats.FLAC,
|
||||
"MP3_320": TrackFormats.MP3_320,
|
||||
"MP3_128": TrackFormats.MP3_128
|
||||
}
|
||||
const formats_360 = {
|
||||
"MP4_RA3": TrackFormats.MP4_RA3,
|
||||
"MP4_RA2": TrackFormats.MP4_RA2,
|
||||
"MP4_RA1": TrackFormats.MP4_RA1
|
||||
}
|
||||
|
||||
const is360Format = Object.values(formats_360).contains(bitrate)
|
||||
let formats
|
||||
if (!shouldFallback){
|
||||
formats = {...formats_360, ...formats_non_360}
|
||||
}else if (is360Format){
|
||||
formats = {...formats_360}
|
||||
}else{
|
||||
formats = {...formats_non_360}
|
||||
}
|
||||
|
||||
for (let i = 0; i < Object.keys(formats).length; i++){
|
||||
let formatName = Object.keys(formats)[i]
|
||||
let formatNumber = formats[formatName]
|
||||
|
||||
if (formatNumber >= bitrate) { continue }
|
||||
if (Object.keys(track.filesizes).contains(`FILESIZE_${formatName}`)){
|
||||
if (parseInt(track.filesizes[`FILESIZE_${formatName}`]) != 0) return formatNumber
|
||||
if (!track.filesizes[`FILESIZE_${formatName}_TESTED`]){
|
||||
// Try getting the streamURL
|
||||
generateStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber)
|
||||
track.filesizes[`FILESIZE_${formatName}_TESTED`] = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldFallback){
|
||||
throw new PreferredBitrateNotFound
|
||||
}else if (!falledBack){
|
||||
falledBack = true
|
||||
if (listener && uuid){
|
||||
listener.send("queueUpdate", {
|
||||
uuid,
|
||||
bitrateFallback: true,
|
||||
data:{
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
artist: track.mainArtist.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is360Format) throw new TrackNot360
|
||||
return TrackFormats.DEFAULT
|
||||
}
|
||||
|
||||
class Downloader {
|
||||
constructor(dz, downloadObject, settings, listener){
|
||||
|
@ -13,21 +79,34 @@ class Downloader {
|
|||
}
|
||||
|
||||
start(){
|
||||
if (typeof this.downloadObject == "Single"){
|
||||
|
||||
} else if (typeof this.downloadObject == "Collection") {
|
||||
|
||||
if (this.downloadObject.__type__ === "Single"){
|
||||
this.download({
|
||||
trackAPI_gw: this.downloadObject.single.trackAPI_gw,
|
||||
trackAPI: this.downloadObject.single.trackAPI,
|
||||
albumAPI: this.downloadObject.single.albumAPI
|
||||
})
|
||||
} else if (this.downloadObject.__type__ === "Collection") {
|
||||
let tracks = []
|
||||
this.downloadObject.collection.tracks_gw.forEach((track, pos) => {
|
||||
tracks[pos] = this.download({
|
||||
trackAPI_gw: track,
|
||||
albumAPI: this.downloadObject.collection.albumAPI,
|
||||
playlistAPI: this.downloadObject.collection.playlistAPI
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (this.listener) this.listener.send("finishedDownload", this.downloadObject.uuid)
|
||||
}
|
||||
|
||||
download(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track){
|
||||
async download(extraData, track){
|
||||
const { trackAPI_gw, trackAPI, albumAPI, playlistAPI } = extraData
|
||||
if (trackAPI_gw.SNG_ID == "0") throw new DownloadFailed("notOnDeezer")
|
||||
|
||||
// Generate track object
|
||||
if (!track){
|
||||
track = Track().parseData(
|
||||
track = Track()
|
||||
await track.parseData(
|
||||
this.dz,
|
||||
trackAPI_gw,
|
||||
trackAPI,
|
||||
|
@ -51,5 +130,42 @@ class Downloader {
|
|||
track.album.bitrate = selectedFormat
|
||||
|
||||
// Download the track
|
||||
track.downloadURL = generateStreamURL(track.id, track.MD5, track.mediaVersion, track.bitrate)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "DownloadError"
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadFailed extends DownloadError {
|
||||
constructor(errid, track) {
|
||||
super(errid);
|
||||
this.name = "ISRCnotOnDeezer"
|
||||
this.track = track
|
||||
}
|
||||
}
|
||||
|
||||
class TrackNot360 extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "TrackNot360"
|
||||
}
|
||||
}
|
||||
|
||||
class PreferredBitrateNotFound extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "PreferredBitrateNotFound"
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Downloader,
|
||||
DownloadError,
|
||||
DownloadFailed
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
const {
|
||||
Single,
|
||||
Collection,
|
||||
Convertable
|
||||
Collection
|
||||
} = require('./types/DownloadObjects.js')
|
||||
const { LyricsStatus } = require('deezer-js').gw
|
||||
const { map_user_playlist } = require('deezer-js').utils
|
||||
|
||||
async function generateTrackItem(dz, id, bitrate, trackAPI, albumAPI){
|
||||
// Check if is an isrc: url
|
||||
|
@ -22,6 +23,7 @@ async function generateTrackItem(dz, id, bitrate, trackAPI, albumAPI){
|
|||
}
|
||||
|
||||
// Get essential track info
|
||||
let trackAPI_gw
|
||||
try {
|
||||
trackAPI_gw = await dz.gw.get_track_with_fallback(id)
|
||||
} catch (e){
|
||||
|
@ -75,12 +77,13 @@ async function generateAlbumItem(dz, id, bitrate, rootArtist){
|
|||
return generateTrackItem(dz, albumAPI.tracks.data[0].id, bitrate, null, albumAPI)
|
||||
}
|
||||
|
||||
tracksArray = await dz.gw.get_album_tracks(id)
|
||||
let tracksArray = await dz.gw.get_album_tracks(id)
|
||||
|
||||
let cover
|
||||
if (albumAPI.cover_small){
|
||||
const cover = albumAPI.cover_small.substring(0, albumAPI.cover_small.length-24) + '/75x75-000000-80-0-0.jpg'
|
||||
cover = albumAPI.cover_small.substring(0, albumAPI.cover_small.length-24) + '/75x75-000000-80-0-0.jpg'
|
||||
}else{
|
||||
const cover = `https://e-cdns-images.dzcdn.net/images/cover/${albumAPI_gw.ALB_PICTURE}/75x75-000000-80-0-0.jpg`
|
||||
cover = `https://e-cdns-images.dzcdn.net/images/cover/${albumAPI_gw.ALB_PICTURE}/75x75-000000-80-0-0.jpg`
|
||||
}
|
||||
|
||||
const totalSize = tracksArray.length
|
||||
|
@ -92,7 +95,7 @@ async function generateAlbumItem(dz, id, bitrate, rootArtist){
|
|||
collection.push(trackAPI)
|
||||
})
|
||||
|
||||
explicit = [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT].includes(albumAPI_gw.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS || LyricsStatus.UNKNOWN)
|
||||
let explicit = [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT].includes(albumAPI_gw.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS || LyricsStatus.UNKNOWN)
|
||||
|
||||
return new Collection({
|
||||
type: 'album',
|
||||
|
@ -130,7 +133,7 @@ async function generatePlaylistItem(dz, id, bitrate, playlistAPI, playlistTracks
|
|||
}
|
||||
}
|
||||
// Check if private playlist and owner
|
||||
if (!playlsitAPI.public && playlistAPI.creator.id != dz.current_user.id){
|
||||
if (!playlistAPI.public && playlistAPI.creator.id != dz.current_user.id){
|
||||
throw new NotYourPrivatePlaylist()
|
||||
}
|
||||
}
|
||||
|
@ -216,8 +219,8 @@ async function generateArtistDiscographyItem(dz, id, bitrate, listener){
|
|||
}
|
||||
if (listener) { listener.send("startAddingArtist", rootArtist) }
|
||||
|
||||
let artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
|
||||
artistDiscographyAPI.pop('all', None)
|
||||
let artistDiscographyAPI = await dz.gw.get_artist_discography_tabs(id, 100)
|
||||
artistDiscographyAPI.pop('all', null)
|
||||
let albumList = []
|
||||
artistDiscographyAPI.forEach((type) => {
|
||||
type.forEach(async (album) => {
|
||||
|
@ -307,6 +310,7 @@ module.exports = {
|
|||
generateArtistItem,
|
||||
generateArtistDiscographyItem,
|
||||
generateArtistTopItem,
|
||||
|
||||
GenerationError,
|
||||
ISRCnotOnDeezer,
|
||||
NotYourPrivatePlaylist
|
||||
|
|
|
@ -10,10 +10,10 @@ class Album {
|
|||
constructor(id = 0, title = "", pic_md5 = ""){
|
||||
this.id = id
|
||||
this.title = title
|
||||
this.pic = Picture(md5=pic_md5, type="cover")
|
||||
this.pic = new Picture(pic_md5, "cover")
|
||||
this.artist = {"Main": []}
|
||||
this.artists = []
|
||||
self.mainArtist = null
|
||||
this.mainArtist = null
|
||||
this.date = Date()
|
||||
this.dateString = ""
|
||||
this.trackTotal = "0"
|
||||
|
@ -26,8 +26,8 @@ class Album {
|
|||
this.label = "Unknown"
|
||||
this.recordType = "album"
|
||||
this.bitrate = 0
|
||||
self.rootArtist = null
|
||||
self.variousArtists = null
|
||||
this.rootArtist = null
|
||||
this.variousArtists = null
|
||||
}
|
||||
|
||||
parseAlbum(albumAPI){
|
||||
|
@ -37,27 +37,29 @@ class Album {
|
|||
// ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
|
||||
let art_pic = albumAPI.artist.picture_small
|
||||
art_pic = art_pic.substring( art_pic.indexOf('artist/')+7, art_pic.length-24 )
|
||||
this.mainArtist = Artist(
|
||||
this.mainArtist = new Artist(
|
||||
albumAPI.artist.id,
|
||||
albumAPI.artist.name,
|
||||
pic_md5 = art_pic
|
||||
"Main",
|
||||
art_pic
|
||||
)
|
||||
if (albumAPI.root_artist){
|
||||
let art_pic = albumAPI.root_artist.picture_small
|
||||
art_pic = art_pic.substring( art_pic.indexOf('artist/')+7, art_pic.length-24 )
|
||||
this.rootArtist = Artist(
|
||||
this.rootArtist = new Artist(
|
||||
albumAPI.root_artist.id,
|
||||
albumAPI.root_artist.name,
|
||||
pic_md5 = art_pic
|
||||
"Root",
|
||||
art_pic
|
||||
)
|
||||
}
|
||||
|
||||
albumAPI.contributors.forEach(artist => {
|
||||
let isVariousArtists = str(artist.id) == VARIOUS_ARTISTS
|
||||
let isVariousArtists = String(artist.id) == VARIOUS_ARTISTS
|
||||
let isMainArtist = artist.role == "Main"
|
||||
|
||||
if (isVariousArtists){
|
||||
this.variousArtists = Artist(
|
||||
this.variousArtists = new Artist(
|
||||
artist.id,
|
||||
artist.name,
|
||||
artist.role
|
||||
|
@ -80,7 +82,7 @@ class Album {
|
|||
|
||||
this.barcode = albumAPI.upc || this.barcode
|
||||
this.label = albumAPI.label || this.label
|
||||
this.explicit = bool(albumAPI.explicit_lyrics || false)
|
||||
this.explicit = Boolean(albumAPI.explicit_lyrics || false)
|
||||
if (albumAPI.release_date){
|
||||
this.date.year = albumAPI.release_date.substring(0,4)
|
||||
this.date.month = albumAPI.release_date.substring(5,7)
|
||||
|
@ -107,7 +109,7 @@ class Album {
|
|||
|
||||
parseAlbumGW(albumAPI_gw){
|
||||
this.title = albumAPI_gw.ALB_TITLE
|
||||
this.mainArtist = Aritst(
|
||||
this.mainArtist = new Artist(
|
||||
albumAPI_gw.ART_ID,
|
||||
albumAPI_gw.ART_NAME
|
||||
)
|
||||
|
@ -118,6 +120,7 @@ class Album {
|
|||
this.label = albumAPI_gw.LABEL_NAME || this.label
|
||||
|
||||
let explicitLyricsStatus = albumAPI_gw.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS
|
||||
this.explicit = [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT].contains(explicitLyricsStatus)
|
||||
|
||||
if (this.pic.md5 == ""){
|
||||
this.pic.md5 = albumAPI_gw.ALB_PICTURE
|
||||
|
@ -150,11 +153,11 @@ class Album {
|
|||
}
|
||||
|
||||
removeDuplicateArtists(){
|
||||
[self.artist, self.artists] = removeDuplicateArtists(self.artist, self.artists)
|
||||
[this.artist, this.artists] = removeDuplicateArtists(this.artist, this.artists)
|
||||
}
|
||||
|
||||
getCleanTitle(){
|
||||
return removeFeatures(self.title)
|
||||
return removeFeatures(this.title)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ const { VARIOUS_ARTISTS } = require('../index.js')
|
|||
|
||||
class Artist {
|
||||
constructor(id="0", name="", role = "", pic_md5 = ""){
|
||||
this.id = str(id)
|
||||
this.id = String(id)
|
||||
this.name = name
|
||||
this.pic = Picture(md5 = pic_md5, type="artist")
|
||||
this.pic = new Picture(pic_md5, "artist")
|
||||
this.role = role
|
||||
this.save = True
|
||||
this.save = true
|
||||
}
|
||||
|
||||
ifVariousArtist(){
|
||||
|
|
|
@ -7,7 +7,7 @@ class Date {
|
|||
}
|
||||
|
||||
fixDayMonth(){
|
||||
if (int(this.month) > 12){
|
||||
if (parseInt(this.month) > 12){
|
||||
let temp = this.month
|
||||
this.month = this.day
|
||||
this.day = temp
|
||||
|
|
|
@ -39,7 +39,7 @@ class IDownloadObject{
|
|||
}
|
||||
|
||||
getResettedDict(){
|
||||
item = this.toDict()
|
||||
let item = this.toDict()
|
||||
item.downloaded = 0
|
||||
item.failed = 0
|
||||
item.progress = 0
|
||||
|
@ -49,8 +49,8 @@ class IDownloadObject{
|
|||
}
|
||||
|
||||
getSlimmedDict(){
|
||||
light = this.toDict()
|
||||
propertiesToDelete = ['single', 'collection', 'convertable']
|
||||
let light = this.toDict()
|
||||
let propertiesToDelete = ['single', 'collection', 'convertable']
|
||||
propertiesToDelete.forEach((property) => {
|
||||
if (Object.keys(light).includes(property)){
|
||||
delete light[property]
|
||||
|
@ -77,7 +77,7 @@ class Single extends IDownloadObject{
|
|||
}
|
||||
|
||||
toDict(){
|
||||
item = super.toDict()
|
||||
let item = super.toDict()
|
||||
item.single = this.single
|
||||
return item
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ class Collection extends IDownloadObject{
|
|||
}
|
||||
|
||||
toDict(){
|
||||
item = super.toDict()
|
||||
let item = super.toDict()
|
||||
item.collection = this.collection
|
||||
return item
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ class Convertable extends Collection{
|
|||
}
|
||||
|
||||
toDict(){
|
||||
item = super.toDict()
|
||||
let item = super.toDict()
|
||||
item.plugin = this.plugin
|
||||
item.conversion_data = this.conversion_data
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class Lyrics {
|
|||
for (let line = 0; line < syncLyricsJson.length; line++) {
|
||||
if (syncLyricsJson[line].line != ""){
|
||||
timestamp = syncLyricsJson[line].lrc_timestamp
|
||||
milliseconds = int(syncLyricsJson[line].milliseconds)
|
||||
milliseconds = parseInt(syncLyricsJson[line].milliseconds)
|
||||
this.syncID3.push([syncLyricsJson[line].line, milliseconds])
|
||||
}else{
|
||||
let notEmptyLine = line + 1
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
class Picture {
|
||||
constructor(md5 = "", type = "", url) {
|
||||
constructor(md5 = "", type = "") {
|
||||
this.md5 = md5
|
||||
this.type = type
|
||||
this.staticUrl = url
|
||||
}
|
||||
|
||||
getURL(size, format) {
|
||||
if (this.staticUrl) return this.staticUrl
|
||||
|
||||
let url = `https://e-cdns-images.dzcdn.net/images/${this.type}/${this.md5}/${str(size)}x${str(size)}`
|
||||
let url = `https://e-cdns-images.dzcdn.net/images/${this.type}/${this.md5}/${size}x${size}`
|
||||
|
||||
if (format.startsWith('jpg')){
|
||||
let quality = 80
|
||||
if (format.indexOf('-') != -1) quality = int(format.substr(4))
|
||||
if (format.indexOf('-') != -1) quality = parseInt(format.substr(4))
|
||||
format = 'jpg'
|
||||
return url+`-000000-${str(quality)}-0-0.jpg`
|
||||
return url+`-000000-${quality}-0-0.jpg`
|
||||
}
|
||||
if (format == 'png'){
|
||||
return url+`-none-100-0-0.png`
|
||||
|
@ -25,6 +24,17 @@ class Picture {
|
|||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Picture
|
||||
class StaticPicture {
|
||||
constructor(url){
|
||||
this.staticUrl = url
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
return this.staticUrl
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Picture,
|
||||
StaticPicture
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const { Artist } = require('./Artist.js')
|
||||
const { Date } = require('./Date.js')
|
||||
const { Picture } = require('./Picture.js')
|
||||
const { Picture, StaticPicture } = require('./Picture.js')
|
||||
|
||||
class Playlist {
|
||||
constructor(playlistAPI) {
|
||||
this.id = `pl_${str(playlistAPI.id)}`
|
||||
this.id = `pl_${playlistAPI.id}`
|
||||
this.title = playlistAPI.title
|
||||
this.artist = {"Main": []}
|
||||
this.artists = []
|
||||
|
@ -25,23 +25,23 @@ class Playlist {
|
|||
this.owner = playlistAPI.creator
|
||||
|
||||
if (playlistAPI.picture_small.indexOf("dzcdn.net") != -1) {
|
||||
url = playlistAPI.picture_small
|
||||
let url = playlistAPI.picture_small
|
||||
let picType = url.substring(url.indexOf('images/')+7)
|
||||
picType = picType.substring(0, picType.indexOf('/'))
|
||||
let md5 = url.substring( url.indexOf(picType+'/') + picType.length+1, url.length-24 )
|
||||
this.pic = Picture(md5 = md5, type = picType)
|
||||
this.pic = Picture(md5, picType)
|
||||
} else {
|
||||
this.pic = Picture(url = playlistAPI.picture_xl)
|
||||
this.pic = StaticPicture(playlistAPI.picture_xl)
|
||||
}
|
||||
|
||||
if (playlistAPI.various_artist) {
|
||||
let pic_md5 = playlistAPI.various_artist.picture_small
|
||||
pic_md5 = pic_md5.substring( pic_md5.indexOf('artist/')+7, pic_md5.length-24 )
|
||||
this.variousArtists = Artist(
|
||||
id = playlistAPI.various_artist.id,
|
||||
name = playlistAPI.various_artist.name,
|
||||
role = playlistAPI.various_artist.role,
|
||||
pic_md5 = pic_md5
|
||||
playlistAPI.various_artist.id,
|
||||
playlistAPI.various_artist.name,
|
||||
playlistAPI.various_artist.role,
|
||||
pic_md5
|
||||
)
|
||||
this.mainArtist = this.variousArtists
|
||||
}
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
const got = require('got')
|
||||
const { Artist } = require('./Artist.js')
|
||||
const { Album } = require('./Album.js')
|
||||
const { Playlist } = require('./Playlist.js')
|
||||
const { Picture } = require('./Picture.js')
|
||||
const { Lyrics } = require('./Lyrics.js')
|
||||
const { VARIOUS_ARTISTS } = require('./index.js')
|
||||
|
||||
const {
|
||||
generateReplayGainString,
|
||||
removeDuplicateArtists,
|
||||
removeFeatures,
|
||||
andCommaConcat
|
||||
} = require('../utils/index.js')
|
||||
|
||||
class Track {
|
||||
constructor(){
|
||||
|
@ -36,14 +49,14 @@ class Track {
|
|||
}
|
||||
|
||||
parseEssentialData(trackAPI_gw, trackAPI){
|
||||
this.id = str(trackAPI_gw.SNG_ID)
|
||||
this.id = String(trackAPI_gw.SNG_ID)
|
||||
this.duration = trackAPI_gw.DURATION
|
||||
this.MD5 = trackAPI_gw.MD5_ORIGIN
|
||||
if (!this.MD5){
|
||||
if (trackAPI && trackAPI.md5_origin){
|
||||
this.MD5 = trackAPI.md5_origin
|
||||
}else{
|
||||
throw MD5NotFound
|
||||
throw new MD5NotFound
|
||||
}
|
||||
}
|
||||
this.mediaVersion = trackAPI_gw.MEDIA_VERSION
|
||||
|
@ -51,13 +64,14 @@ class Track {
|
|||
if (trackAPI_gw.FALLBACK){
|
||||
this.fallbackID = trackAPI_gw.FALLBACK.SNG_ID
|
||||
}
|
||||
this.localTrack = int(this.id) < 0
|
||||
this.localTrack = parseInt(this.id) < 0
|
||||
}
|
||||
|
||||
async retriveFilesizes(dz){
|
||||
const guest_sid = await dz.cookie_jar.getCookies('deezer.com').sid
|
||||
let result_json
|
||||
try{
|
||||
const result_json = await got.post("https://api.deezer.com/1.0/gateway.php",{
|
||||
result_json = await got.post("https://api.deezer.com/1.0/gateway.php",{
|
||||
searchParams:{
|
||||
api_key: "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
|
||||
sid: guest_sid,
|
||||
|
@ -69,12 +83,12 @@ class Track {
|
|||
headers: dz.headers,
|
||||
timeout: 30000
|
||||
}).json()
|
||||
}catch{
|
||||
console.log(e)
|
||||
}catch (e){
|
||||
console.error(e)
|
||||
await new Promise(r => setTimeout(r, 2000)) // sleep(2000ms)
|
||||
return this.retriveFilesizes(dz)
|
||||
}
|
||||
if (result_json.error.length){ throw APIError }
|
||||
if (result_json.error.length){ throw new TrackError(result_json.error) }
|
||||
const response = result_json.results
|
||||
let filesizes = {}
|
||||
Object.entries(response).forEach((value, key) => {
|
||||
|
@ -88,7 +102,7 @@ class Track {
|
|||
|
||||
async parseData(dz, id, trackAPI_gw, trackAPI, albumAPI_gw, albumAPI, playlistAPI){
|
||||
if (id && !trackAPI_gw) { trackAPI_gw = await dz.gw.get_track_with_fallback(id) }
|
||||
else if (!trackAPI_gw) { throw NoDataToParse }
|
||||
else if (!trackAPI_gw) { throw new NoDataToParse }
|
||||
|
||||
if (!trackAPI) {
|
||||
try { trackAPI = await dz.api.get_track(trackAPI_gw.SNG_ID) }
|
||||
|
@ -111,18 +125,18 @@ class Track {
|
|||
if (this.lyrics.id != "0"){ this.lyrics.parseLyrics(trackAPI_gw.LYRICS) }
|
||||
|
||||
// Parse Album Data
|
||||
this.album = Album(trackAPI_gw.ALB_ID, trackAPI_gw.ALB_TITLE, trackAPI_gw.ALB_PICTURE || "")
|
||||
this.album = new Album(trackAPI_gw.ALB_ID, trackAPI_gw.ALB_TITLE, trackAPI_gw.ALB_PICTURE || "")
|
||||
|
||||
// Get album Data
|
||||
if (!albumAPI){
|
||||
try { albumAPI = await dz.api.get_album(this.album.id) }
|
||||
catch { albumAPI = None }
|
||||
catch { albumAPI = null }
|
||||
}
|
||||
|
||||
// Get album_gw Data
|
||||
if (!albumAPI_gw){
|
||||
try { albumAPI_gw = await dz.gw.get_album(this.album.id) }
|
||||
catch { albumAPI_gw = None }
|
||||
catch { albumAPI_gw = null }
|
||||
}
|
||||
|
||||
if (albumAPI){
|
||||
|
@ -132,10 +146,10 @@ class Track {
|
|||
// albumAPI_gw doesn't contain the artist cover
|
||||
// Getting artist image ID
|
||||
// ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
|
||||
const artistAPI = await dz.api.get_artist(self.album.mainArtist.id)
|
||||
self.album.mainArtist.pic.md5 = artistAPI.picture_small.substring( artistAPI.picture_small.search('artist/')+7, artistAPI.picture_small.length-24 )
|
||||
const artistAPI = await dz.api.get_artist(this.album.mainArtist.id)
|
||||
this.album.mainArtist.pic.md5 = artistAPI.picture_small.substring( artistAPI.picture_small.search('artist/')+7, artistAPI.picture_small.length-24 )
|
||||
}else{
|
||||
throw AlbumDoesntExists
|
||||
throw new AlbumDoesntExists
|
||||
}
|
||||
|
||||
// Fill missing data
|
||||
|
@ -156,7 +170,7 @@ class Track {
|
|||
|
||||
this.position = trackAPI_gw.POSITION
|
||||
|
||||
if (playlistAPI) { this.playlist = Playlist(playlistAPI) }
|
||||
if (playlistAPI) { this.playlist = new Playlist(playlistAPI) }
|
||||
|
||||
this.generateMainFeatStrings()
|
||||
return this
|
||||
|
@ -166,12 +180,12 @@ class Track {
|
|||
// Local tracks has only the trackAPI_gw page and
|
||||
// contains only the tags provided by the file
|
||||
this.title = trackAPI_gw.SNG_TITLE
|
||||
this.album = Album(trackAPI_gw.ALB_TITLE)
|
||||
this.album.pic = Picture(
|
||||
this.album = new Album(trackAPI_gw.ALB_TITLE)
|
||||
this.album.pic = new Picture(
|
||||
trackAPI_gw.ALB_PICTURE || "",
|
||||
"cover"
|
||||
)
|
||||
this.mainArtist = Artist(trackAPI_gw.ART_NAME)
|
||||
this.mainArtist = new Artist(trackAPI_gw.ART_NAME)
|
||||
this.artists = [trackAPI_gw.ART_NAME]
|
||||
this.artist = {
|
||||
'Main': [trackAPI_gw.ART_NAME]
|
||||
|
@ -190,16 +204,16 @@ class Track {
|
|||
}
|
||||
|
||||
this.discNumber = trackAPI_gw.DISK_NUMBER
|
||||
this.explicit = bool(int(trackAPI_gw.EXPLICIT_LYRICS || "0"))
|
||||
this.explicit = Boolean(parseInt(trackAPI_gw.EXPLICIT_LYRICS || "0"))
|
||||
this.copyright = trackAPI_gw.COPYRIGHT
|
||||
if (trackAPI_gw.GAIN) this.replayGain = generateReplayGainString(trackAPI_gw.GAIN)
|
||||
this.ISRC = trackAPI_gw.ISRC
|
||||
this.trackNumber = trackAPI_gw.TRACK_NUMBER
|
||||
this.contributors = trackAPI_gw.SNG_CONTRIBUTORS
|
||||
|
||||
this.lyrics = Lyrics(trackAPI_gw.LYRICS_ID || "0")
|
||||
this.lyrics = new Lyrics(trackAPI_gw.LYRICS_ID || "0")
|
||||
|
||||
this.mainArtist = Artist(
|
||||
this.mainArtist = new Artist(
|
||||
trackAPI_gw.ART_ID,
|
||||
trackAPI_gw.ART_NAME,
|
||||
trackAPI_gw.ART_PICTRUE
|
||||
|
@ -221,7 +235,7 @@ class Track {
|
|||
if (!this.discNumber) this.discNumber = trackAPI.disk_number
|
||||
|
||||
trackAPI.contributors.forEach(artist => {
|
||||
const isVariousArtists = str(artist.id) == VARIOUS_ARTISTS
|
||||
const isVariousArtists = String(artist.id) == VARIOUS_ARTISTS
|
||||
const isMainArtist = artist.role == "Main"
|
||||
|
||||
if (trackAPI.contributors.length > 1 && isVariousArtists) return
|
||||
|
@ -230,7 +244,7 @@ class Track {
|
|||
this.artsits.push(artist.name)
|
||||
|
||||
if (isMainArtist || !this.artsit.Main.contains(artist.name) && !isMainArtist){
|
||||
if (!this.artist[aritst.role])
|
||||
if (!this.artist[artist.role])
|
||||
this.artist[artist.role] = []
|
||||
this.artist[artist.role].push(artist.name)
|
||||
}
|
||||
|
@ -260,8 +274,37 @@ class Track {
|
|||
}
|
||||
}
|
||||
|
||||
applySettings(settings, TEMPDIR, embeddedImageFormat){
|
||||
applySettings(settings){
|
||||
// TODO: Applay settings
|
||||
settings;
|
||||
}
|
||||
}
|
||||
|
||||
class TrackError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "TrackError";
|
||||
}
|
||||
}
|
||||
|
||||
class MD5NotFound extends TrackError {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MD5NotFound";
|
||||
}
|
||||
}
|
||||
|
||||
class NoDataToParse extends TrackError {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "NoDataToParse";
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumDoesntExists extends TrackError {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "AlbumDoesntExists";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
function generateReplayGainString(trackGain){
|
||||
return `${Math.round((float(trackGain) + 18.4)*-100)/100} dB`
|
||||
return `${Math.round((parseFloat(trackGain) + 18.4)*-100)/100} dB`
|
||||
}
|
||||
|
||||
function removeFeatures(title){
|
||||
|
|
2299
package-lock.json
generated
2299
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -9,7 +9,11 @@
|
|||
"author": "RemixDev",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"crypto": "^1.0.1",
|
||||
"deezer-js": "^0.0.3",
|
||||
"got": "^11.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.23.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue