First commit

This commit is contained in:
RemixDev 2021-04-01 13:38:59 +02:00
commit f19f744153
15 changed files with 1735 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

81
deemix/index.js Normal file
View file

@ -0,0 +1,81 @@
const got = require('got')
const {
generateTrackItem,
generateAlbumItem,
generatePlaylistItem,
generateArtistItem,
generateArtistDiscographyItem,
generateArtistTopItem
} = require('./itemgen.js')
async function parseLink(link){
if (link.indexOf('deezer.page.link') != -1) link = await got.get(link).url // Resolve URL shortner
// Remove extra stuff
if (link.indexOf('?') != -1) link = link.substring(0, link.indexOf('?'))
if (link.indexOf('&') != -1) link = link.substring(0, link.indexOf('&'))
if (link.endsWith('/')) link = link.substring(0, link.length-1) // Remove last slash if present
let type, id
if (link.indexOf('deezer') == -1) return [link, type, id] // return if not a deezer link
if (link.search(/\/track\/(.+)/g) != -1){
type = 'track'
id = link.match(/\/track\/(.+)/g)[1]
}else if (link.search(/\/playlist\/(\d+)/g) != -1){
type = 'playlist'
id = link.match(/\/playlist\/(\d+)/g)[1]
}else if (link.search(/\/album\/(.+)/g) != -1){
type = 'album'
id = link.match(/\/album\/(.+)/g)[1]
}else if (link.search(/\/artist\/(\d+)\/top_track/g) != -1){
type = 'artist_top'
id = link.match(/\/artist\/(\d+)\/top_track/g)[1]
}else if (link.search(/\/artist\/(\d+)\/discography/g) != -1){
type = 'artist_discography'
id = link.match(/\/artist\/(\d+)\/discography/g)[1]
}else if (link.search(/\/artist\/(\d+)/g) != -1){
type = 'artist'
id = link.match(/\/artist\/(\d+)/g)[1]
}
return [link, type, id]
}
async function generateDownloadObject(dz, link, bitrate){
let [link, type, id] = await parseLink(link)
if (type == null || id == null) return null
switch (type) {
case 'track':
return generateTrackItem(dz, id, bitrate)
case 'album':
return generateAlbumItem(dz, id, bitrate)
case 'playlist':
return generatePlaylistItem(dz, id, bitrate)
case 'artist':
return generateArtistItem(dz, id, bitrate)
case 'artist_discography':
return generateArtistDiscographyItem(dz, id, bitrate)
case 'artist_top':
return generateArtistTopItem(dz, id, bitrate)
}
return null
}
module.exports = {
parseLink,
generateDownloadObject,
VARIOUS_ARTISTS,
types: {
...require('./types/index.js'),
...require('./types/Album.js'),
...require('./types/Artist.js'),
...require('./types/Date.js'),
...require('./types/Lyrics.js'),
...require('./types/Picture.js'),
...require('./types/Playlist.js'),
...require('./types/Track.js'),
}
}

246
deemix/itemgen.js Normal file
View file

@ -0,0 +1,246 @@
async function generateTrackItem(dz, id, bitrate, trackAPI, albumAPI){
// Check if is an isrc: url
if (str(id).startsWith("isrc")){
try {
trackAPI = await dz.api.get_track(id)
} catch {
throw Exception("WrongURL")
}
if (trackAPI.id && trackAPI.title){
id = trackAPI.id
} else {
throw Exception("ISRCnotOnDeezer")
}
}
// Get essential track info
try {
trackAPI_gw = await dz.gw.get_track_with_fallback(id)
} catch {
throw Exception("WrongURL")
}
let title = trackAPI_gw.SNG_TITLE.trim()
if (trackAPI_gw.VERSION && title.indexOf(trackAPI_gw.VERSION.trim()) == -1){
title += ` ${trackAPI_gw.VERSION.trim()}`
}
const explicit = bool(int(trackAPI_gw.EXPLICIT_LYRICS || "0"))
return Single({
type: 'track',
id: id,
bitrate: bitrate,
title: title,
artist: trackAPI_gw.ART_NAME,
cover: `https://e-cdns-images.dzcdn.net/images/cover/${trackAPI_gw.ALB_PICTURE}/75x75-000000-80-0-0.jpg`,
explicit: explicit,
single: {
trackAPI_gw: trackAPI_gw,
trackAPI: trackAPI,
albumAPI: albumAPI
}
})
}
async function generateAlbumItem(dz, id, bitrate, rootArtist){
// Get essential album info
let albumAPI
try{
albumAPI = dz.api.get_album(id)
} catch {
throw Exception("WrongURL")
}
if str(id).startswith('upc') { id = albumAPI['id'] }
// Get extra info about album
// This saves extra api calls when downloading
let albumAPI_gw = dz.gw.get_album(id)
albumAPI.nb_disk = albumAPI_gw.NUMBER_DISK
albumAPI.copyright = albumAPI_gw.COPYRIGHT
albumAPI.root_artist = rootArtist
// If the album is a single download as a track
if (albumAPI.nb_tracks == 1){
return generateTrackItem(dz, albumAPI.tracks.data[0].id, bitrate, null, albumAPI)
}
tracksArray = dz.gw.get_album_tracks(id)
if (albumAPI.cover_small){
const 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`
}
const totalSize = tracksArray.length
albumAPI.nb_tracks = totalSize
let collection = []
tracksArray.forEach((trackAPI, pos) => {
trackAPI.POSITION = pos+1
trackAPI.SIZE = totalSize
collection.push(trackAPI)
})
explicit = albumAPI_gw.get('EXPLICIT_ALBUM_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN) in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]
return Collection({
type: 'album',
id: id,
bitrate: bitrate,
title: albumAPI.title,
artist: albumAPI.artist.name,
cover: cover,
explicit: explicit,
size: totalSize,
collection: {
tracks_gw: collection,
albumAPI: albumAPI
}
})
}
async function generatePlaylistItem(dz, id, bitrate, playlistAPI, playlistTracksAPI){
if (!playlistAPI){
// Get essential playlist info
try{
playlistAPI = dz.api.get_playlist(id)
}catch{
playlistAPI = null
}
// Fallback to gw api if the playlist is private
if (!playlistAPI){
try{
let userPlaylist = dz.gw.get_playlist_page(id)
playlistAPI = map_user_playlist(userPlaylist['DATA'])
}catch{
throw Exception("WrongURL")
}
}
// Check if private playlist and owner
if (!playlsitAPI.public && playlistAPI.creator.id != dz.current_user.id){
throw Exception("notYourPrivatePlaylist")
}
}
if (!playlistTracksAPI){
playlistTracksAPI = dz.gw.get_playlist_tracks(id)
}
playlistAPI.various_artist = dz.api.get_artist(5080) // Useful for save as compilation
const totalSize = playlistTracksAPI.length
playlistAPI.nb_tracks = totalSize
let collection = []
playlistTracksAPI.forEach((trackAPI, pos) => {
//TODO: Add explicit check
trackAPI.POSITION = pos+1
trackAPI.SIZE = totalSize
collection.push(trackAPI)
});
if (!playlistAPI.explicit) playlistAPI.explicit = false
return Collection({
type: 'playlist',
id: id,
bitrate: bitrate,
title: playlistAPI.title,
artist: playlistAPI.creator.name,
cover: playlistAPI.cover_small.substring(0, playlistAPI.cover_small.length-24) + '/75x75-000000-80-0-0.jpg',
explicit: playlistAPI.explicit,
size: totalSize,
collection: {
tracks_gw: collection,
playlistAPI: playlistAPI
}
})
}
async function generateArtistItem(dz, id, bitrate, interface=None):
// Get essential artist info
try:
artistAPI = dz.api.get_artist(id)
except APIError as e:
e = str(e)
raise GenerationError("https://deezer.com/artist/"+str(id), f"Wrong URL: {e}")
if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
rootArtist = {
'id': artistAPI['id'],
'name': artistAPI['name']
}
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
allReleases = artistDiscographyAPI.pop('all', [])
albumList = []
for album in allReleases:
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
if interface: interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
return albumList
async function generateArtistDiscographyItem(dz, id, bitrate, interface=None):
// Get essential artist info
try:
artistAPI = dz.api.get_artist(id)
except APIError as e:
e = str(e)
raise GenerationError("https://deezer.com/artist/"+str(id)+"/discography", f"Wrong URL: {e}")
if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
rootArtist = {
'id': artistAPI['id'],
'name': artistAPI['name']
}
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
artistDiscographyAPI.pop('all', None) // all contains albums and singles, so its all duplicates. This removes them
albumList = []
for type in artistDiscographyAPI:
for album in artistDiscographyAPI[type]:
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
if interface: interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
return albumList
async function generateArtistTopItem(dz, id, bitrate, interface=None):
// Get essential artist info
try:
artistAPI = dz.api.get_artist(id)
except APIError as e:
e = str(e)
raise GenerationError("https://deezer.com/artist/"+str(id)+"/top_track", f"Wrong URL: {e}")
// Emulate the creation of a playlist
// Can't use generatePlaylistItem directly as this is not a real playlist
playlistAPI = {
'id': str(artistAPI['id'])+"_top_track",
'title': artistAPI['name']+" - Top Tracks",
'description': "Top Tracks for "+artistAPI['name'],
'duration': 0,
'public': True,
'is_loved_track': False,
'collaborative': False,
'nb_tracks': 0,
'fans': artistAPI['nb_fan'],
'link': "https://www.deezer.com/artist/"+str(artistAPI['id'])+"/top_track",
'share': None,
'picture': artistAPI['picture'],
'picture_small': artistAPI['picture_small'],
'picture_medium': artistAPI['picture_medium'],
'picture_big': artistAPI['picture_big'],
'picture_xl': artistAPI['picture_xl'],
'checksum': None,
'tracklist': "https://api.deezer.com/artist/"+str(artistAPI['id'])+"/top",
'creation_date': "XXXX-00-00",
'creator': {
'id': "art_"+str(artistAPI['id']),
'name': artistAPI['name'],
'type': "user"
},
'type': "playlist"
}
artistTopTracksAPI_gw = dz.gw.get_artist_toptracks(id)
return generatePlaylistItem(dz, playlistAPI['id'], bitrate, playlistAPI=playlistAPI, playlistTracksAPI=artistTopTracksAPI_gw)

164
deemix/types/Album.js Normal file
View file

@ -0,0 +1,164 @@
const { LyricsStatus } = require('deezer-js').gw
const { removeDuplicateArtists, removeFeatures } = require('../utils/index.js')
const { Artist } = require('./Artist.js')
const { Date } = require('./Date.js')
const { Picture } = require('./Picture.js')
const { VARIOUS_ARTISTS } = require('./index.js')
class Album {
constructor(id = 0, title = "", pic_md5 = ""){
this.id = id
this.title = title
this.pic = Picture(md5=pic_md5, type="cover")
this.artist = {"Main": []}
this.artists = []
self.mainArtist = null
this.date = Date()
this.dateString = ""
this.trackTotal = "0"
this.discTotal = "0"
this.embeddedCoverPath = ""
this.embeddedCoverURL = ""
this.explicit = false
this.genre = []
this.barcode = "Unknown"
this.label = "Unknown"
this.recordType = "album"
this.bitrate = 0
self.rootArtist = null
self.variousArtists = null
}
parseAlbum(albumAPI){
this.title = albumAPI.title
// Getting artist image ID
// 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(
albumAPI.artist.id,
albumAPI.artist.name,
pic_md5 = 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(
albumAPI.root_artist.id,
albumAPI.root_artist.name,
pic_md5 = art_pic
)
}
albumAPI.contributors.forEach(artist => {
let isVariousArtists = str(artist.id) == VARIOUS_ARTISTS
let isMainArtist = artist.role == "Main"
if (isVariousArtists){
this.variousArtists = Artist(
artist.id,
artist.name,
artist.role
)
return
}
if (this.artists.includes(artist.name)){
this.artists.push(artist.name)
}
if (isMainArtist || !this.artist['Main'].includes(artist.name) && !isMainArtist){
if (!this.artist[artist.role]) this.artist[artist.role] = []
this.artist[artist.role].push(artist.name)
}
});
this.trackTotal = albumAPI.nb_tracks
this.recordType = albumAPI.record_type
this.barcode = albumAPI.upc || this.barcode
this.label = albumAPI.label || this.label
this.explicit = bool(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)
this.date.day = albumAPI.release_date.substring(8,10)
this.date.fixDayMonth()
}
this.discTotal = albumAPI.nb_disk || "1"
this.copyright = albumAPI.copyright
if (this.pic.md5 == ""){
// Getting album cover MD5
// ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg
let alb_pic = albumAPI.cover_small
this.pic.md5 = alb_pic.substring( alb_pic.indexOf('cover/')+6, alb_pic.length-24 )
}
if (albumAPI.genres && albumAPI.genres.data && albumAPI.genres.data.length > 0) {
albumAPI.genres.data.forEach(genre => {
this.genre.push(genre.name)
});
}
}
parseAlbumGW(albumAPI_gw){
this.title = albumAPI_gw.ALB_TITLE
this.mainArtist = Aritst(
albumAPI_gw.ART_ID,
albumAPI_gw.ART_NAME
)
this.artists = [albumAPI_gw.ART_NAME]
this.trackTotal = albumAPI_gw.NUMBER_TRACK
this.discTotal = albumAPI_gw.NUMBER_DISK
this.label = albumAPI_gw.LABEL_NAME || this.label
let explicitLyricsStatus = albumAPI_gw.EXPLICIT_ALBUM_CONTENT.EXPLICIT_LYRICS_STATUS
if (this.pic.md5 == ""){
this.pic.md5 = albumAPI_gw.ALB_PICTURE
}
if (albumAPI_gw.PHYSICAL_RELEASE_DATE){
this.date.year = albumAPI_gw.PHYSICAL_RELEASE_DATE.substring(0,4)
this.date.month = albumAPI_gw.PHYSICAL_RELEASE_DATE.substring(5,7)
this.date.day = albumAPI_gw.PHYSICAL_RELEASE_DATE.substring(8,10)
this.date.fixDayMonth()
}
}
makePlaylistCompilation(playlist){
this.variousArtists = playlist.variousArtists
this.mainArtist = playlist.mainArtist
this.title = playlist.title
this.rootArtist = playlist.rootArtist
this.artist = playlist.artist
this.artists = playlist.artists
this.trackTotal = playlist.trackTotal
this.recordType = playlist.recordType
this.barcode = playlist.barcode
this.label = playlist.label
this.explicit = playlist.explicit
this.date = playlist.date
this.discTotal = playlist.discTotal
this.playlistId = playlist.playlistId
this.owner = playlist.owner
this.pic = playlist.pic
}
removeDuplicateArtists(){
[self.artist, self.artists] = removeDuplicateArtists(self.artist, self.artists)
}
getCleanTitle(){
return removeFeatures(self.title)
}
}
module.exports = {
Album
}

20
deemix/types/Artist.js Normal file
View file

@ -0,0 +1,20 @@
const { Picture } = require('./Picture.js')
const { VARIOUS_ARTISTS } = require('../index.js')
class Artist {
constructor(id="0", name="", role = "", pic_md5 = ""){
this.id = str(id)
this.name = name
this.pic = Picture(md5 = pic_md5, type="artist")
this.role = role
this.save = True
}
ifVariousArtist(){
return this.id == VARIOUS_ARTISTS
}
}
module.exports = {
Artist
}

27
deemix/types/Date.js Normal file
View file

@ -0,0 +1,27 @@
class Date {
constructor(day = "00", month = "00", year = "XXXX"){
this.day = day
this.month = month
this.year = year
this.fixDayMonth()
}
fixDayMonth(){
if (int(this.month) > 12){
let temp = this.month
this.month = this.day
this.day = temp
}
}
format(template){
template = template.replaceAll(/D+/g, this.day)
template = template.replaceAll(/M+/g, this.month)
template = template.replaceAll(/Y+/g, this.year)
return template
}
}
module.exports = {
Date
}

View file

@ -0,0 +1,142 @@
class IDownloadObject{
constructor(obj){
this.type = obj.type
this.id = obj.id
this.bitrate = obj.bitrate
this.title = obj.title
this.artist = obj.artist
this.cover = obj.cover
this.explicit = obj.explicit || false
this.size = obj.size
this.downloaded = obj.downloaded || 0
this.failed = obj.failed || 0
this.progress = obj.progress || 0
this.errors = obj.errors || []
this.files = obj.files || []
this.progressNext = 0
this.uuid = `${this.type}_${this.id}_${this.bitrate}`
this.ack = null
this.__type__ = null
}
toDict(){
return {
type: this.type,
id: this.id,
bitrate: this.bitrate,
uuid: this.uuid,
title: this.title,
artist: this.artist,
cover: this.cover,
explicit: this.explicit,
size: this.size,
downloaded: this.downloaded,
failed: this.failed,
progress: this.progress,
errors: this.errors,
files: this.files,
ack: this.ack,
__type__: this.__type__
}
}
getResettedDict(){
item = this.toDict()
item.downloaded = 0
item.failed = 0
item.progress = 0
item.errors = []
item.files = []
return item
}
getSlimmedDict(){
light = this.toDict()
propertiesToDelete = ['single', 'collection', 'convertable']
propertiesToDelete.forEach((property) => {
if (Object.keys(light).includes(property)){
delete light[property]
}
})
return light
}
updateProgress(interface){
if (Math.round(this.progressNext) != this.progress && Math.round(this.progressNext) % 2 == 0){
this.progress = Math.round(this.progressNext)
if (interface) interface.emit('updateQueue', {uuid: this.uuid, progress: this.progress})
}
}
}
class Single(IDownloadObject){
constructor(obj){
super(obj)
this.size = 1
this.single = obj.single
this.__type__ = "Single"
}
toDict(){
item = super().toDict()
item.single = this.single
return item
}
completeTrackProgress(interface){
this.progressNext = 100
this.updateProgress(interface)
}
removeTrackProgress(interface){
this.progressNext = 0
this.updateProgress(interface)
}
}
class Collection(IDownloadObject){
constructor(obj){
super(obj)
this.collection = obj.collection
this.__type__ = "Collection"
}
toDict(){
item = super().toDict()
item.collection = this.collection
return item
}
completeTrackProgress(interface){
this.progressNext += (1 / this.size) * 100
this.updateProgress(interface)
}
removeTrackProgress(interface){
this.progressNext -= (1 / this.size) * 100
this.updateProgress(interface)
}
}
class Convertable(Collection){
constructor(obj){
super(obj)
this.plugin = obj.plugin
this.conversion_data = obj.conversion_data
this.__type__ = "Convertable"
}
toDict(){
item = super().toDict()
item.plugin = this.plugin
item.conversion_data = this.conversion_data
}
}
module.exports = {
IDownloadObject,
Single,
Collection,
Convertable
}

33
deemix/types/Lyrics.js Normal file
View file

@ -0,0 +1,33 @@
class Lyrics {
constructor(id = "0") {
this.id = id
this.sync = ""
this.unsync = ""
this.syncID3 = []
}
parseLyrics(lyricsAPI) {
this.unsync = lyricsAPI.LYRICS_TEXT || ""
if (lyricsAPI.LYRICS_SYNC_JSON) {
let syncLyricsJson = lyricsAPI.LYRICS_SYNC_JSON
let timestamp = ""
let milliseconds = 0
for (let line = 0; line < syncLyricsJson.length; line++) {
if (syncLyricsJson[line].line != ""){
timestamp = syncLyricsJson[line].lrc_timestamp
milliseconds = int(syncLyricsJson[line].milliseconds)
this.syncID3.push([syncLyricsJson[line].line, milliseconds])
}else{
let notEmptyLine = line + 1
while (syncLyricsJson[notEmptyLine].line == "") notEmptyLine = line + 1
timestamp = syncLyricsJson[notEmptyLine].lrc_timestamp
}
this.sync += timestamp + syncLyricsJson[line].line + "\r\n"
}
}
}
}
module.exports = {
Lyrics
}

30
deemix/types/Picture.js Normal file
View file

@ -0,0 +1,30 @@
class Picture {
constructor(md5 = "", type = "", url) {
this.md5 = md5
this.type = type
this.staticUrl = url
}
getURL(size, format) {
if (this.staticUrl) return this.staticUrl
let url: string = `https://e-cdns-images.dzcdn.net/images/${this.type}/${this.md5}/${str(size)}x${str(size)}`
if (format.startsWith('jpg')){
let quality: number = 80
if (format.indexOf('-') != -1) quality = int(format.substr(4))
format = 'jpg'
return url+`-000000-${str(quality)}-0-0.jpg`
}
if (format == 'png'){
return url+`-none-100-0-0.png`
}
return url+'.jpg'
}
}
module.exports = {
Picture
}

53
deemix/types/Playlist.js Normal file
View file

@ -0,0 +1,53 @@
const { Artist } = require('./Artist.js')
const { Date } = require('./Date.js')
const { Picture } = require('./Picture.js')
class Playlist {
constructor(playlistAPI) {
this.id = `pl_${str(playlistAPI.id)}`
this.title = playlistAPI.title
this.artist = {"Main": []}
this.artists = []
this.trackTotal = playlistAPI.nb_tracks
this.recordType = "compile"
this.barcode = ""
this.label = ""
this.explicit = playlistAPI.explicit
this.genre = ["Compilation", ]
let year = playlistAPI.creation_date.substring(0,4)
let month = playlistAPI.creation_date.substring(5,7)
let day = playlistAPI.creation_date.substring(8,10)
this.date = Date(day, month, year)
this.discTotal = "1"
this.playlistID = playlistAPI.id
this.owner = playlistAPI.creator
if (playlistAPI.picture_small.indexOf("dzcdn.net") != -1) {
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)
} else {
this.pic = Picture(url = 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
)
this.mainArtist = this.variousArtists
}
}
}
module.exports = {
Playlist
}

270
deemix/types/Track.js Normal file
View file

@ -0,0 +1,270 @@
const got = require('got')
class Track(){
constructor(){
this.id = "0",
this.title = "",
this.MD5 = ""
this.mediaVersion = ""
this.duration = 0
this.fallbackID = "0"
this.filesizes = {}
this.localTrack = false
this.mainArtist = null
this.artist = {"Main": []}
this.artists = []
this.album = null
this.trackNumber = "0"
this.discNumber = "0"
this.date = null
this.lyrics = null
this.bpm = 0
this.contributors = {}
this.copyright = ""
this.explicit = false
this.ISRC = ""
this.replayGain = ""
this.playlist = null
this.position = null
this.searched = false
this.selectedFormat = 0
this.singleDownload = false
this.dateString = ""
this.artistsString = ""
this.mainArtistsString = ""
this.featArtistsString = ""
}
parseEssentialData(trackAPI_gw, trackAPI){
this.id = str(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
}
}
this.mediaVersion = trackAPI_gw.MEDIA_VERSION
this.fallbackID = "0"
if (trackAPI_gw.FALLBACK){
this.fallbackID = trackAPI_gw.FALLBACK.SNG_ID
}
this.localTrack = int(this.id) < 0
}
async retriveFilesizes(dz){
const guest_sid = await dz.cookie_jar.getCookies('deezer.com').sid
try{
const result_json = await got.post("https://api.deezer.com/1.0/gateway.php",{
searchParams:{
api_key: "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
sid: guest_sid,
input: '3',
output: '3',
method: 'song_getData'
},
json: {sng_id: this.id},
headers: dz.headers,
timeout: 30000
}).json()
}catch{
console.log(e)
await new Promise(r => setTimeout(r, 2000)) // sleep(2000ms)
return this.retriveFilesizes(dz)
}
if (result_json.error.length){ throw APIError }
const response = result_json.results
let filesizes = {}
Object.entries(response).forEach((value, key) => {
if (key.startsWith("FILESIZE_")){
filesizes[key] = value
filesizes[key+"_TESTED"] = false
}
})
this.filesizes = filesizes
}
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 }
if (!trackAPI) {
try { trackAPI = await dz.api.get_track(trackAPI_gw.SNG_ID) }
catch { trackAPI = null }
}
this.parseEssentialData(trackAPI_gw, trackAPI)
if (this.localTrack){
this.parseLocalTrackData(trackAPI_gw)
}else{
await this.retriveFilesizes(dz)
this.parseTrackGW(trackAPI_gw)
// Get Lyrics Data
if (!trackAPI_gw.LYRICS and this.lyrics.id != "0"){
try { trackAPI_gw.LYRICS = await dz.gw.get_track_lyrics(this.id) }
catch { this.lyrics.id = "0" }
}
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 || "")
// Get album Data
if (!albumAPI){
try { albumAPI = await dz.api.get_album(this.album.id) }
catch { albumAPI = None }
}
// Get album_gw Data
if (!albumAPI_gw){
try { albumAPI_gw = await dz.gw.get_album(this.album.id) }
catch { albumAPI_gw = None }
}
if (albumAPI){
this.album.parseAlbum(albumAPI)
}else if (albumAPI_gw){
this.album.parseAlbumGW(albumAPI_gw)
// 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 )
}else{
throw AlbumDoesntExists
}
// Fill missing data
if (this.album.date && !this.date) this.date = this.album.date
if (!this.album.discTotal) this.album.discTotal = albumAPI_gw.NUMBER_DISK || "1"
if (!this.copyright) this.copyright = albumAPI_gw.COPYRIGHT
this.parseTrack(trackAPI)
}
// Remove unwanted charaters in track name
// Example: track/127793
this.title = this.title.replace(/\s\s+/g, ' ')
// Make sure there is at least one artist
if (!this.artist.Main.length){
this.artist.Main = [this.mainArtist.name]
}
this.position = trackAPI_gw.POSITION
if (playlistAPI) { this.playlist = Playlist(playlistAPI) }
this.generateMainFeatStrings()
return this
}
parseLocalTrackData(trackAPI_gw){
// 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(
trackAPI_gw.ALB_PICTURE || "",
"cover"
)
this.mainArtist = Artist(trackAPI_gw.ART_NAME)
this.artists = [trackAPI_gw.ART_NAME]
this.artist = {
'Main': [trackAPI_gw.ART_NAME]
}
this.date = Date()
this.album.artist = this.artist
this.album.artists = this.artists
this.album.date = this.date
this.album.mainArtist = this.mainArtist
}
parseTrackGW(trackAPI_gw){
this.title = trackAPI_gw.SNG_TITLE.trim()
if (trackAPI_gw.VERSION && this.title.indexOf(trackAPI_gw.VERSION.trim()) == -1){
this.title += ` ${trackAPI_gw.VERSION.trim()}`
}
this.discNumber = trackAPI_gw.DISK_NUMBER
this.explicit = bool(int(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.mainArtist = Artist(
trackAPI_gw.ART_ID,
trackAPI_gw.ART_NAME,
trackAPI_gw.ART_PICTRUE
)
if (trackAPI_gw.PHYSICAL_RELEASE_DATE){
const day = trackAPI_gw.PHYSICAL_RELEASE_DATE.substring(8,10)
const month = trackAPI_gw.PHYSICAL_RELEASE_DATE.substring(5,7)
const year = trackAPI_gw.PHYSICAL_RELEASE_DATE.substring(0,4)
this.date = Date(day, month, year)
}
}
parseTrack(trackAPI){
this.bpm = trackAPI.bpm
if (!this.replayGain && trackAPI.gain) this.replayGain = generateReplayGainString(trackAPI.gain)
if (!this.explicit) this.explicit = trackAPI.explicit_lyrics
if (!this.discNumber) this.discNumber = trackAPI.disk_number
trackAPI.contributors.forEach(artist => {
const isVariousArtists = str(artist.id) == VARIOUS_ARTISTS
const isMainArtist = artist.role == "Main"
if (trackAPI.contributors.length > 1 && isVariousArtists) return
if (!this.artsits.contains(artist.name))
this.artsits.push(artist.name)
if (isMainArtist || !this.artsit.Main.contains(artist.name) && !isMainArtist){
if (!this.artist[aritst.role])
this.artist[artist.role] = []
this.artist[artist.role].push(artist.name)
}
});
}
removeDuplicateArtists(){
[this.artist, this.artsits] = removeDuplicateArtists(this.artist, this.artists)
}
getCleanTitle(){
return removeFeatures(this.title)
}
getFeatTitle(){
if (this.featArtistsString && this.title.toLowerCase().indexOf("feat.") == -1){
return `${this.title} (${this.featArtistsString})`
}
return this.title
}
generateMainFeatStrings(){
this.mainArtistsString = andCommaConcat(this.artist.Main)
this.featArtistsString = ""
if (this.artist.Featured){
this.featArtistsString = `feat. ${andCommaConcat(this.artist.Featured)}`
}
}
applySettings(settings, TEMPDIR, embeddedImageFormat){
}
}
module.exports = {
Track
}

1
deemix/types/index.js Normal file
View file

@ -0,0 +1 @@
const VARIOUS_ARTISTS = "5080"

59
deemix/utils/index.js Normal file
View file

@ -0,0 +1,59 @@
function generateReplayGainString(trackGain){
return `${Math.round((float(trackGain) + 18.4)*-100)/100} dB`
}
function removeFeatures(title){
let clean = title
if (clean.search(/\(feat\./gi) != -1){
const pos = clean.search(/\(feat\./gi)
let tempTrack = clean.substring(0, pos)
if (clean.indexOf(')') != -1)
tempTrack += clean.substring(clean.indexOf(')', pos+1)+1)
clean = tempTrack.trim()
clean = clean.replace(/\s\s+/g, ' ') // remove extra spaces
}
return clean
}
function andCommaConcat(lst){
const tot = lst.length
let result = ""
lst.forEach((art, i) => {
result += art
if (tot != i+1){
if (tot - 1 == i+1){
result += " & "
} else {
result += ", "
}
}
})
return result
}
function uniqueArray(arr){
arr.forEach((namePrinc, iPrinc) => {
arr.forEach((nameRest, iRest) => {
if (iPrinc != iRest && nameRest.toLowerCase().indexOf(namePrinc.toLowerCase()) != -1){
arr.splice(iRest, 1)
}
})
})
return arr
}
function removeDuplicateArtists(artist, artists){
artists = uniqueArray(artists)
Object.keys(artist).forEach((role) => {
artist[role] = uniqueArray(artist[role])
})
return [artist, artists]
}
module.exports = {
generateReplayGainString,
removeFeatures,
andCommaConcat,
uniqueArray,
removeDuplicateArtists
}

593
package-lock.json generated Normal file
View file

@ -0,0 +1,593 @@
{
"name": "deemix",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "deemix",
"version": "0.0.0",
"license": "GPL-3.0-or-later",
"dependencies": {
"deezer-js": "^0.0.2",
"got": "^11.8.2"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz",
"integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz",
"integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==",
"dependencies": {
"defer-to-connect": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
"integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==",
"dependencies": {
"@types/http-cache-semantics": "*",
"@types/keyv": "*",
"@types/node": "*",
"@types/responselike": "*"
}
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A=="
},
"node_modules/@types/keyv": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
"integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "14.14.34",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz",
"integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA=="
},
"node_modules/@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"engines": {
"node": ">=10.6.0"
}
},
"node_modules/cacheable-request": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
"integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^4.1.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
"dependencies": {
"mimic-response": "^1.0.0"
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/decompress-response/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deezer-js": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/deezer-js/-/deezer-js-0.0.2.tgz",
"integrity": "sha512-v78DXcqeeui6wWEKd+6vVDITT76ck3pp8p+fSUZrvX3P8N6fQXGFzHqjgHaGm2rJpEnG5Ad8a8KTesDKZ56A6Q==",
"dependencies": {
"got": "^11.8.2",
"tough-cookie": "^4.0.0"
}
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"engines": {
"node": ">=10"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/got": {
"version": "11.8.2",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
"integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=10.19.0"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
},
"engines": {
"node": ">=10.19.0"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
"node_modules/keyv": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
"integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"engines": {
"node": ">=4"
}
},
"node_modules/normalize-url": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
"integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/p-cancelable": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz",
"integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"engines": {
"node": ">=6"
}
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/resolve-alpn": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
"integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA=="
},
"node_modules/responselike": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
"dependencies": {
"lowercase-keys": "^2.0.0"
}
},
"node_modules/tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.1.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
},
"dependencies": {
"@sindresorhus/is": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz",
"integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ=="
},
"@szmarczak/http-timer": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz",
"integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==",
"requires": {
"defer-to-connect": "^2.0.0"
}
},
"@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
"integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==",
"requires": {
"@types/http-cache-semantics": "*",
"@types/keyv": "*",
"@types/node": "*",
"@types/responselike": "*"
}
},
"@types/http-cache-semantics": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A=="
},
"@types/keyv": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
"integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==",
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "14.14.34",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.34.tgz",
"integrity": "sha512-dBPaxocOK6UVyvhbnpFIj2W+S+1cBTkHQbFQfeeJhoKFbzYcVUGHvddeWPSucKATb3F0+pgDq0i6ghEaZjsugA=="
},
"@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"requires": {
"@types/node": "*"
}
},
"cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
},
"cacheable-request": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
"integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^4.1.0",
"responselike": "^2.0.0"
}
},
"clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
"requires": {
"mimic-response": "^1.0.0"
}
},
"decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"requires": {
"mimic-response": "^3.1.0"
},
"dependencies": {
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
}
}
},
"deezer-js": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/deezer-js/-/deezer-js-0.0.2.tgz",
"integrity": "sha512-v78DXcqeeui6wWEKd+6vVDITT76ck3pp8p+fSUZrvX3P8N6fQXGFzHqjgHaGm2rJpEnG5Ad8a8KTesDKZ56A6Q==",
"requires": {
"got": "^11.8.2",
"tough-cookie": "^4.0.0"
}
},
"defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"requires": {
"once": "^1.4.0"
}
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"requires": {
"pump": "^3.0.0"
}
},
"got": {
"version": "11.8.2",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
"integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
}
},
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
},
"http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"requires": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
}
},
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
"keyv": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
"integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
"requires": {
"json-buffer": "3.0.1"
}
},
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
},
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
},
"normalize-url": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
"integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"p-cancelable": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz",
"integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ=="
},
"psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
},
"resolve-alpn": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
"integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA=="
},
"responselike": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
"requires": {
"lowercase-keys": "^2.0.0"
}
},
"tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.1.2"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

15
package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "deemix",
"version": "0.0.0",
"description": "a barebones deezer downloader library",
"main": "deemix/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "RemixDev",
"license": "GPL-3.0-or-later",
"dependencies": {
"deezer-js": "^0.0.2",
"got": "^11.8.2"
}
}