2987dff8b9407452ecbfc41a8b05728f03e945c8
[DeezloaderRemix.git] / app / app.js
1 /*
2 *   _____                _                 _             _____                _
3 *  |  __ \              | |               | |           |  __ \              (_)
4 *  | |  | | ___  ___ ___| | ___   __ _  __| | ___ _ __  | |__) |___ _ __ ___  ___  __
5 *  | |  | |/ _ \/ _ \_  / |/ _ \ / _` |/ _` |/ _ \ '__| |  _  // _ \ '_ ` _ \| \ \/ /
6 *  | |__| |  __/  __// /| | (_) | (_| | (_| |  __/ |    | | \ \  __/ | | | | | |>  <
7 *  |_____/ \___|\___/___|_|\___/ \__,_|\__,_|\___|_|    |_|  \_\___|_| |_| |_|_/_/\_\
8 *
9 **/
10
11 // Server stuff
12 const express = require('express')
13 const app = express()
14 const server = require('http').createServer(app)
15 const io = require('socket.io').listen(server, {log: false, wsEngine: 'ws'})
16 // Music tagging stuff
17 const mflac = require('./lib/flac-metadata')
18 const ID3Writer = require('./lib/browser-id3-writer')
19 const deezerApi = require('deezer-api')
20 const spotifyApi = require('spotify-web-api-node')
21 // App stuff
22 const fs = require('fs-extra')
23 const async = require('async')
24 const request = require('request-promise')
25 const requestOld = require('request')
26 const os = require('os')
27 const path = require('path')
28 const logger = require('./utils/logger.js')
29 const queue = require('queue')
30 const localpaths = require('./utils/localpaths.js')
31 const package = require('./package.json')
32 const stq = require('sequential-task-queue')
33
34 // First run, create config file
35 if(!fs.existsSync(localpaths.user+"config.json")){
36         fs.outputFileSync(localpaths.user+"config.json",fs.readFileSync(__dirname+path.sep+"default.json",'utf8'))
37 }
38
39 // Main Constants
40 // Files
41 const configFileLocation = localpaths.user+"config.json"
42 // Folders
43 var coverArtFolder = 'deezloader-imgs' + path.sep
44 var defaultDownloadFolder = path.sep + 'Deezloader Music' + path.sep
45
46 if(process.platform == "android"){
47     coverArtFolder = localpaths.user + coverArtFolder
48     fs.ensureFileSync(coverArtFolder + '.nomedia');
49     defaultDownloadFolder = localpaths.home + path.sep + 'Music' + defaultDownloadFolder
50 }else{
51     coverArtFolder = os.tmpdir() + path.sep + coverArtFolder
52     defaultDownloadFolder = localpaths.home + defaultDownloadFolder
53 }
54
55 // Default settings
56 const defaultSettings = require('./default.json').userDefined
57 // Spotify Files
58 const spotifySupport = fs.existsSync(localpaths.user+"authCredentials.js")
59 if (spotifySupport){
60         var authCredentials = require(localpaths.user+'authCredentials.js')
61         var Spotify = new spotifyApi(authCredentials)
62 }
63
64 // Setup the folders START
65 var mainFolder = defaultDownloadFolder
66
67 // See if all settings are there after update
68 var configFile = require(localpaths.user+path.sep+"config.json");
69 for (let x in defaultSettings){
70         if (typeof configFile.userDefined[x] != typeof defaultSettings[x]){
71                 configFile.userDefined[x] = defaultSettings[x]
72         }
73 }
74 // Set default download folder if not userDefined
75 if (configFile.userDefined.downloadLocation != "") {
76         mainFolder = configFile.userDefined.downloadLocation
77 }
78
79 initFolders();
80
81 // Route and create server
82 app.use('/', express.static(__dirname + '/public/'))
83 server.listen(configFile.serverPort)
84 logger.info('Server is running @ localhost:' + configFile.serverPort)
85
86 var dqueue = new stq.SequentialTaskQueue()
87 var downloadQueue = {}
88 var trackQueue = queue({
89         autostart: true
90 })
91 trackQueue.concurrency = configFile.userDefined.queueConcurrency
92
93 // START sockets clusterfuck
94 io.sockets.on('connection', function (s) {
95         logger.info("Connection received!")
96
97         // Check for updates
98         request({
99                 url: "https://notabug.org/RemixDevs/DeezloaderRemix/raw/master/update.json",
100                 json: true
101         })
102         .then(body=>{
103                 logger.info("Checking for updates")
104                 let [currentVersion_MAJOR, currentVersion_MINOR, currentVersion_PATCH] = package.version.split(".").map(x=>parseInt(x))
105                 let [lastVersion_MAJOR, lastVersion_MINOR, lastVersion_PATCH] = body.version.split(".").map(x=>parseInt(x))
106                 if (
107                         lastVersion_MAJOR>currentVersion_MAJOR ||
108                         lastVersion_MAJOR==currentVersion_MAJOR && lastVersion_MINOR>currentVersion_MINOR ||
109                         lastVersion_MAJOR==currentVersion_MAJOR && lastVersion_MINOR==currentVersion_MINOR && lastVersion_PATCH>currentVersion_PATCH
110                 ){
111                         logger.info("Update Available")
112                         s.emit("messageUpdate", {title: `Version ${lastVersion_MAJOR}.${lastVersion_MINOR}.${lastVersion_PATCH} is available!`, msg: body.changelog, lastVersion: body.version})
113                 }else{
114                         logger.info("Running the latest version!")
115                 }
116         })
117         .catch(error=>{
118                 logger.error(`UpdateCheck failed: ${error.stack ? error.stack : error}`)
119         })
120
121         // Connection dependet variables
122         s.Deezer = new deezerApi()
123
124         s.emit("checkAutologin")
125         s.emit("getDefaultSettings", defaultSettings, defaultDownloadFolder)
126         s.emit("populateDownloadQueue", downloadQueue)
127
128         // Function for logging in
129         s.on("login", async function (username, password, autologin) {
130                 try{
131                         logger.info("Logging in");
132                         await s.Deezer.login(username, password)
133                         s.emit("login", {user: s.Deezer.user})
134                         logger.info("Logged in successfully")
135                         if (autologin){
136                                 // Save session login so next time login is not needed
137                                 // This is the same method used by the official website
138                                 s.emit('getCookies', s.Deezer.getCookies())
139                         }
140                 }catch(err){
141                         s.emit("login", {error: err.message})
142                         logger.error(`Login failed: ${err.message}`)
143                 }
144         })
145
146         // Function for login with userToken
147         s.on("loginViaUserToken", async function (userToken) {
148                 try{
149                         logger.info("Logging in");
150                         await s.Deezer.loginViaUserToken(userToken)
151                         s.emit("login", {user: s.Deezer.user})
152                         logger.info("Logged in successfully")
153                         s.emit('getCookies', s.Deezer.getCookies())
154                 }catch(err){
155                         s.emit("login", {error: err.message})
156                         logger.error(`Login failed: ${err.message}`)
157                 }
158         });
159
160         // Function for autologin
161         s.on("autologin", async function(jar, email){
162                 try{
163       await s.Deezer.loginViaCookies(jar, email)
164                         s.emit('login', {user: s.Deezer.user})
165     }catch(err){
166       s.emit('login', {error: err.message})
167                         logger.error(`Autologin failed: ${err.message}`)
168       return
169     }
170         })
171
172         // Function for logout
173         s.on("logout", function(){
174                 logger.info("Logged out")
175                 // Creating new object to clear the cookies
176                 s.Deezer = new deezerApi()
177                 return
178         })
179
180         // Returns list of charts available
181         s.on("getChartsCountryList", async function (data) {
182                 try{
183                         let charts = await s.Deezer.legacyGetChartsTopCountry()
184                         charts = charts.data || []
185                         let countries = []
186                         for (let i = 0; i < charts.length; i++) {
187                                 let obj = {
188                                         country: charts[i].title.replace("Top ", ""),
189                                         picture_small: charts[i].picture_small,
190                                         picture_medium: charts[i].picture_medium,
191                                         picture_big: charts[i].picture_big,
192                                         playlistId: charts[i].id
193                                 }
194                                 countries.push(obj)
195                         }
196                         s.emit("getChartsCountryList", {countries: countries, selected: data.selected})
197                 }catch(err){
198                         logger.error(`getChartsCountryList failed: ${err.stack}`)
199                         return
200                 }
201         })
202
203         // Returns chart tracks from Playlist ID
204         async function getChartsTrackListById(playlistId){
205                 if (typeof playlistId === 'undefined') {
206                         s.emit("getChartsTrackListByCountry", {err: "Can't find that playlist"})
207                         return
208                 }
209                 try{
210                         let tracks = await s.Deezer.legacyGetPlaylistTracks(playlistId)
211                         s.emit("getChartsTrackListByCountry", {
212                                 playlistId: playlistId,
213                                 tracks: tracks.data
214                         })
215                 }catch(err){
216                         s.emit("getChartsTrackListByCountry", {err: err})
217                         logger.error(`getChartsTrackListById failed: ${err.stack}`)
218                         return
219                 }
220         }
221
222         // Returns chart tracks from country name
223         async function getChartsTrackListByCountry(country){
224                 if (typeof country === 'undefined') {
225                         s.emit("getChartsTrackListByCountry", {err: "No country passed"})
226                         return
227                 }
228                 try{
229                         let charts = await s.Deezer.legacyGetChartsTopCountry()
230                         charts = charts.data || []
231                         let countries = []
232                         for (let i = 0; i < charts.length; i++) {
233                                 countries.push(charts[i].title.replace("Top ", ""))
234                         }
235                         if (countries.indexOf(country) == -1) {
236                                 s.emit("getChartsTrackListByCountry", {err: "Country not found"})
237                                 return
238                         }
239                         let playlistId = charts[countries.indexOf(country)].id
240                         await getChartsTrackListById(playlistId)
241                 }catch(err){
242                         logger.error(`getChartsTrackListByCountry failed: ${err.stack}`)
243                         return
244                 }
245         }
246         s.on("getChartsTrackListByCountry", function (data) {getChartsTrackListByCountry(data.country)})
247
248         // Returns list of playlists
249         async function getMyPlaylistList(){
250                 try{
251                         logger.info("Loading Personal Playlists")
252                         let data = await s.Deezer.legacyGetUserPlaylists(s.Deezer.user.id)
253                         data = data.data || []
254                         let playlists = []
255                         for (let i = 0; i < data.length; i++) {
256                                 let obj = {
257                                         title: data[i].title,
258                                         image: data[i].picture_small,
259                                         songs: data[i].nb_tracks,
260                                         link: data[i].link
261                                 }
262                                 playlists.push(obj)
263                         }
264                         if (configFile.userDefined.spotifyUser && spotifySupport){
265                                 try{
266                                         let creds = await Spotify.clientCredentialsGrant()
267                                         Spotify.setAccessToken(creds.body['access_token'])
268                                         let first = true
269                                         let offset = 0
270                                         do{
271                                                 let data = await Spotify.getUserPlaylists(configFile.userDefined.spotifyUser, {fields: "items(images,name,owner.id,tracks.total,uri),total", offset: offset*20})
272                                                 if (first){
273                                                         var total = data.body.total
274                                                         var numPages=Math.floor((total-1)/20)
275                                                         var playlistList = new Array(total)
276                                                         first = false
277                                                 }
278                                                 data.body.items.forEach((playlist, i) => {
279                                                         playlistList[(offset*20)+i] = {
280                                                                 title: playlist.name,
281                                                                 image: (playlist.images[0] ? playlist.images[0].url : ""),
282                                                                 songs: playlist.tracks.total,
283                                                                 link: playlist.uri,
284                                                                 spotify: true
285                                                         }
286                                                 })
287                                                 offset++
288                                         }while(offset<=numPages)
289                                         playlists = playlists.concat(playlistList)
290                                 }catch(err){
291                                         logger.error(`Spotify playlist failed loading: ${err}`)
292                                 }
293                         }
294                         logger.info(`Loaded ${playlists.length} Playlist${playlists.length>1 ? "s" : ""}`)
295                         s.emit("getMyPlaylistList", {playlists: playlists})
296                 }catch(err){
297                         logger.error(`getMyPlaylistList failed: ${err}`)
298                         return
299                 }
300         }
301         s.on("getMyPlaylistList", function (d) {getMyPlaylistList()})
302
303         // Returns search results from a query
304         s.on("search", async function (data) {
305                 data.type = data.type || "track"
306                 if (["track", "playlist", "album", "artist"].indexOf(data.type) == -1) data.type = "track"
307
308                 // Remove "feat."  "ft." and "&" (causes only problems)
309                 data.text = data.text
310                         .replace(/ feat[\.]? /g, " ")
311                         .replace(/ ft[\.]? /g, " ")
312                         .replace(/\(feat[\.]? /g, " ")
313                         .replace(/\(ft[\.]? /g, " ")
314                         .replace(/\&/g, "")
315                         .replace(/–/g, "-")
316                         .replace(/—/g, "-")
317
318                 try {
319                         let searchObject = await s.Deezer.legacySearch(encodeURIComponent(data.text), data.type)
320                         s.emit("search", {type: data.type, items: searchObject.data})
321                 } catch (err) {
322                         s.emit("search", {type: data.type, items: []})
323                         logger.error(`search failed: ${err.stack}`)
324                         return
325                 }
326         })
327
328         // Returns list of tracks from an album/playlist or the list of albums from an artist
329         s.on("getTrackList", async function (data) {
330                 if (!data.type || (["playlist", "album", "artist", "spotifyplaylist"].indexOf(data.type) == -1) || !data.id) {
331                         s.emit("getTrackList", {err: -1, response: {}, id: data.id, reqType: data.type})
332                         return
333                 }
334                 if (data.type == 'artist') {
335                         try{
336                                 let response = await s.Deezer.legacyGetArtist(data.id)
337                                 let tracks = await s.Deezer.legacyGetArtistAlbums(data.id)
338                                 response.data = tracks.data
339                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
340                         }catch(err){
341                                 s.emit("getTrackList", {err: "wrong artist id", response: {}, id: data.id, reqType: data.type})
342                                 logger.error(`getTrackList failed: ${err.stack}`)
343                                 return
344                         }
345                 }else if(data.type == "spotifyplaylist" && spotifySupport){
346                         try{
347                                 let creds = await Spotify.clientCredentialsGrant()
348                                 Spotify.setAccessToken(creds.body.access_token)
349                                 let first = true
350                                 let offset = 0
351                                 var response = {}
352                                 let resp0 = await Spotify.getPlaylist(data.id, {fields: "images,name,owner"})
353                                 response.title = resp0.body.name
354                                 response.image = resp0.body.images[0].url
355                                 response.owner = resp0.body.owner.display_name
356                                 do{
357                                         let resp = await Spotify.getPlaylistTracks(data.id, {fields: "items(track(artists,name,duration_ms,preview_url,explicit)),total", offset: offset*100})
358                                         if (first){
359                                                 var numPages=Math.floor((resp.body.total-1)/100)
360                                                 response.data = new Array(resp.body.total)
361                                                 first = false
362                                         }
363                                         resp.body.items.forEach((t, index) => {
364                                                 response.data[index+offset*100]={
365                                                         explicit_lyrics: t.track.explicit,
366                                                         preview: t.track.preview_url,
367                                                         title: t.track.name,
368                                                         artist: {
369                                                                 name: t.track.artists[0].name
370                                                         },
371                                                         duration: Math.floor(t.track.duration_ms/1000)
372                                                 }
373                                         })
374                                         offset++
375                                 }while(offset<=numPages)
376                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
377                         }catch(err){
378                                 logger.error(`getTrackList failed: ${err.stack}`)
379                         }
380                 }else{
381                         let reqType = data.type.charAt(0).toUpperCase() + data.type.slice(1)
382                         try{
383                                 let response = await s.Deezer["legacyGet" + reqType](data.id)
384                                 let tracks = await s.Deezer["legacyGet" + reqType + "Tracks"](data.id)
385                                 response.data = tracks.data
386                                 s.emit("getTrackList", {response: response, id: data.id, reqType: data.type})
387                         }catch(err){
388                                 s.emit("getTrackList", {err: "wrong id "+reqType, response: {}, id: data.id, reqType: data.type})
389                                 logger.error(`getTrackList failed: ${err.stack}`)
390                                 return
391                         }
392                 }
393         })
394
395         // Sends settings saved by the user to the frontend
396         s.on("getUserSettings", function () {
397                 let settings = configFile.userDefined
398                 if (!settings.downloadLocation) {
399                         settings.downloadLocation = mainFolder
400                 }
401                 s.emit('getUserSettings', {settings: settings})
402         });
403
404         // Saves locally the settings comming from the frontend
405         s.on("saveSettings", function (settings) {
406                 if (settings.userDefined.downloadLocation == defaultDownloadFolder) {
407                         settings.userDefined.downloadLocation = ""
408                 } else {
409                         settings.userDefined.downloadLocation = path.resolve(settings.userDefined.downloadLocation + path.sep) + path.sep
410                         mainFolder = settings.userDefined.downloadLocation
411                 }
412
413                 if (settings.userDefined.queueConcurrency < 1) settings.userDefined.queueConcurrency = 1
414
415                 if (settings.userDefined.queueConcurrency != trackQueue.concurrency){
416                         trackQueue.concurrency = settings.userDefined.queueConcurrency
417                 }
418
419                 if (settings.userDefined.chartsCountry != configFile.userDefined.chartsCountry){
420                         s.emit("setChartsCountry", {selected: settings.userDefined.chartsCountry})
421                         getChartsTrackListByCountry(settings.userDefined.chartsCountry)
422                 }
423
424                 if (settings.userDefined.spotifyUser != configFile.userDefined.spotifyUser){
425                         getMyPlaylistList(settings.userDefined.spotifyUser)
426                 }
427
428                 configFile.userDefined = settings.userDefined;
429                 fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
430                         if (err) return
431                         logger.info("Settings updated")
432                         initFolders()
433                 });
434         });
435
436         /*
437          * Downloading section of the app
438         */
439
440         // Gets data from the frontend and creates the track object
441         async function downloadTrack(data){
442                 logger.info(`Added to Queue ${data.id}`)
443                 try{
444                         var track = await s.Deezer.getTrack(data.id)
445                         data.settings.filename = data.settings.trackNameTemplate
446                         data.settings.foldername = data.settings.albumNameTemplate
447                         let _track = {
448                                 name: track.title,
449                                 artist: track.artist.name,
450                                 size: 1,
451                                 downloaded: 0,
452                                 failed: 0,
453                                 queueId: `id${Math.random().toString(36).substring(2)}`,
454                                 id: `${track.id}:${data.bitrate}`,
455                                 bitrate: data.bitrate+"",
456                                 type: 'track',
457                                 settings: data.settings || {},
458                                 obj: track,
459                         }
460                         addToQueue(_track)
461                 }catch(err){
462                         logger.error(`downloadTrack failed: ${err.stack ? err.stack : err}`)
463                         return
464                 }
465         }
466         s.on("downloadtrack", async data=>{await downloadTrack(data)})
467
468         // Gets data from the frontend and creates the album object
469         async function downloadAlbum(data){
470                 logger.info(`Added to Queue ${data.id}`)
471                 try{
472                         var album = await s.Deezer.legacyGetAlbum(data.id)
473                         if (data.settings.tags.discTotal || data.settings.createCDFolder){
474                                 var discTotal = await s.Deezer.getAlbum(data.id)
475                                 album.discTotal = discTotal.discTotal
476                         }
477                         if (album.nb_tracks == 1 && data.settings.downloadSinglesAsTracks){
478                                 var track = await s.Deezer.getTrack(album.tracks.data[0].id)
479                                 data.settings.filename = data.settings.trackNameTemplate
480                                 data.settings.foldername = data.settings.albumNameTemplate
481                                 let _track = {
482                                         name: track.title,
483                                         artist: track.artist.name,
484                                         size: 1,
485                                         downloaded: 0,
486                                         failed: 0,
487                                         queueId: `id${Math.random().toString(36).substring(2)}`,
488                                         id: `${track.id}:${data.bitrate}`,
489                                         urlId: data.id,
490                                         bitrate: data.bitrate+"",
491                                         type: 'track',
492                                         settings: data.settings || {},
493                                         obj: track,
494                                 }
495                                 addToQueue(_track)
496                         }else{
497                                 album.tracks = await s.Deezer.getAlbumTracks(data.id)
498                                 data.settings.filename = data.settings.albumTrackNameTemplate
499                                 data.settings.foldername = data.settings.albumNameTemplate
500                                 let _album = {
501                                         name: album.title,
502                                         artist: album.artist.name,
503                                         size: album.tracks.length,
504                                         downloaded: 0,
505                                         failed: 0,
506                                         queueId: `id${Math.random().toString(36).substring(2)}`,
507                                         id: `${album.id}:${data.bitrate}`,
508                                         bitrate: data.bitrate+"",
509                                         type: 'album',
510                                         settings: data.settings || {},
511                                         obj: album,
512                                 }
513                                 addToQueue(_album)
514                         }
515                         return
516                 }catch(err){
517                         logger.error(`downloadAlbum failed: ${err.stack ? err.stack : err}`)
518                         return
519                 }
520         }
521         s.on("downloadalbum", async data=>{await downloadAlbum(data)});
522
523         // Gets data from the frontend and creates for each album an album object
524         async function downloadArtist(data){
525                 logger.info(`Added to Queue ${data.id}`)
526                 try{
527                         var albums = await s.Deezer.legacyGetArtistAlbums(data.id);
528                         (function sendAllAlbums(i) {
529                                 setTimeout(function () {
530                                         data.id = albums.data[albums.data.length-1-i].id
531                                         downloadAlbum(JSON.parse(JSON.stringify(data)))
532                                         if (--i+1) sendAllAlbums(i)
533                                 }, 100)
534                         })(albums.data.length-1)
535                 }catch(err){
536                         logger.error(`downloadArtist failed: ${err.stack ? err.stack : err}`)
537                         return
538                 }
539         }
540         s.on("downloadartist", async data=>{ await downloadArtist(data)});
541
542         // Gets data from the frontend and creates the playlist object
543         async function downloadPlaylist(data){
544                 logger.info(`Added to Queue ${data.id}`)
545                 try{
546                         var playlist = await s.Deezer.legacyGetPlaylist(data.id)
547                         data.settings.filename = data.settings.playlistTrackNameTemplate
548                         data.settings.foldername = data.settings.albumNameTemplate
549                         playlist.tracks = await s.Deezer.getPlaylistTracks(data.id)
550                         let _playlist = {
551                                 name: playlist.title,
552                                 artist: playlist.creator.name,
553                                 size: playlist.tracks.length,
554                                 downloaded: 0,
555                                 failed: 0,
556                                 queueId: `id${Math.random().toString(36).substring(2)}`,
557                                 id: `${playlist.id}:${data.bitrate}`,
558                                 bitrate: data.bitrate+"",
559                                 type: "playlist",
560                                 settings: data.settings || {},
561                                 obj: playlist,
562                         }
563                         addToQueue(_playlist)
564                 }catch(err){
565                         logger.error(`downloadPlaylist failed: ${err.stack ? err.stack : err}`)
566                         return
567                 }
568         }
569         s.on("downloadplaylist", data=>{downloadPlaylist(data)});
570
571         // Gets data from the frontend and creates the object fot the artist top tracks
572         async function downloadArtistTop(data){
573                 logger.info(`Added to Queue ${data.id}`)
574                 try{
575                         var artist = await s.Deezer.legacyGetArtist(data.id)
576                         data.settings.filename = data.settings.playlistTrackNameTemplate
577                         data.settings.foldername = data.settings.albumNameTemplate
578                         artist.tracks = await s.Deezer.getArtistTopTracks(data.id)
579                         let _playlist = {
580                                 name: artist.name + " Most played tracks",
581                                 artist: artist.name,
582                                 size: artist.tracks.length,
583                                 downloaded: 0,
584                                 failed: 0,
585                                 queueId: `id${Math.random().toString(36).substring(2)}`,
586                                 id: `${artist.id}:${data.bitrate}`,
587                                 bitrate: data.bitrate+"",
588                                 type: "playlist",
589                                 settings: data.settings || {},
590                                 obj: artist,
591                         }
592                         addToQueue(_playlist)
593                 }catch(err){
594                         logger.error(`downloadArtistTop failed: ${err.stack ? err.stack : err}`)
595                         return
596                 }
597         }
598         s.on("downloadartisttop", data=>{downloadArtistTop(data)});
599
600         // Gets data from the frontend and creates the spotify playlist object
601         async function downloadSpotifyPlaylist(data){
602                 logger.info(`Added to Queue ${data.id}`)
603                 if (spotifySupport){
604                         try{
605                                 let creds = await Spotify.clientCredentialsGrant()
606                                 Spotify.setAccessToken(creds.body['access_token'])
607                                 var offset = 0
608                                 data.settings.filename = data.settings.playlistTrackNameTemplate
609                                 data.settings.foldername = data.settings.albumNameTemplate
610                                 var resp = await Spotify.getPlaylist(data.id, {fields: "id,name,owner,images,tracks(total)"})
611                                 var _playlist = {
612                                         name: resp.body.name,
613                                         artist: (resp.body.owner.display_name ? resp.body.owner.display_name : resp.body.owner.id),
614                                         size: resp.body.tracks.total,
615                                         downloaded: 0,
616                                         failed: 0,
617                                         queueId: `id${Math.random().toString(36).substring(2)}`,
618                                         settings: data.settings || {},
619                                         id: `${resp.body.id}:${data.bitrate}`,
620                                         bitrate: data.bitrate+"",
621                                         type: "spotifyplaylist",
622                                         obj: resp.body
623                                 }
624                                 var numPages=Math.floor((_playlist.size-1)/100)
625                                 var trackList = new Array(_playlist.size)
626                                 do{
627                                         var resp = await Spotify.getPlaylistTracks(data.id, {fields: "items(track(artists,name,album,external_ids))", offset: offset*100})
628                                         resp.body.items.forEach((track, i) => {
629                                                 trackList[i+(offset*100)] = track.track
630                                         })
631                                         offset++
632                                 }while(offset<=numPages)
633                                 _playlist.obj.tracks = trackList
634                                 addToQueue(_playlist)
635                         }catch(err){
636                                 logger.error(`downloadSpotifyPlaylist failed: ${err.stack ? err.stack : err}`)
637                                 return
638                         }
639                 }else{
640                         s.emit("message", {title: "Spotify Support is not enabled", msg: "You should add authCredentials.js in your config files to use this feature<br>You can see how to do that in <a href=\"https://notabug.org/RemixDevs/DeezloaderRemix/wiki/Spotify+Features\">this guide</a>"})
641                 }
642         }
643         s.on("downloadspotifyplaylist", data=>{downloadSpotifyPlaylist(data)})
644
645         // Converts the spotify track to a deezer one
646         // It tries first with the isrc (best way of conversion)
647         // Fallbacks to the old way, using search
648         async function convertSpotify2Deezer(track){
649                 if (!track) return 0
650                 try{
651                         if (track.external_ids.isrc){
652                                 let resp = await s.Deezer.legacyGetTrackByISRC(track.external_ids.isrc)
653                                 return resp.id
654                         }
655                 }catch(err){
656                         logger.warn("ISRC not found, falling back to old method")
657                 }
658                 return convertMetadata2Deezer(track.artists[0].name, track.name, track.album.name)
659         }
660
661         // Tries to get track id from pure luck
662         async function convertMetadata2Deezer(artist, track, album){
663                 let resp
664                 artist = artist.replace(/–/g,"-").replace(/’/g, "'")
665                 track = track.replace(/–/g,"-").replace(/’/g, "'")
666                 album = album.replace(/–/g,"-").replace(/’/g, "'")
667                 try{
668                         resp = await s.Deezer.legacySearch(`artist:"${artist}" track:"${track}" album:"${album}"`, "track", 1)
669                 }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
670                 if (resp.data[0]) return resp.data[0].id
671                 try{
672                         resp = await s.Deezer.legacySearch(`artist:"${artist}" track:"${track}"`, "track", 1)
673                 }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
674                 if (resp.data[0]) return resp.data[0].id
675                 if (track.indexOf("(") < track.indexOf(")")){
676                         try{
677                                 resp = await s.Deezer.legacySearch(`artist:"${artist}" track:"${track.split("(")[0]}"`, "track", 1)
678                         }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
679                         if (resp.data[0]) return resp.data[0].id
680                 }else if (track.indexOf(" - ")>0){
681                         try{
682                                 resp = await s.Deezer.legacySearch(`artist:"${artist}" track:"${track.split(" - ")[0]}"`, "track", 1)
683                         }catch(err){logger.err(`ConvertFromMetadata: ${err.stack ? err.stack : err}`)}
684                         if (resp.data[0]) return resp.data[0].id
685                 }else{
686                         return 0
687                 }
688                 return 0
689         }
690
691         // All the above functions call this function
692         // It adds the object to an array and adds the promise for the download to the object itself
693         function addToQueue(object) {
694                 downloadQueue[object.queueId] = object
695                 io.sockets.emit('addToQueue', object)
696                 downloadQueue[object.queueId].downloadQueuePromise = dqueue.push(addNextDownload, { args: object })
697         }
698
699         // Wrapper for queue download
700         function addNextDownload(obj, token){
701                 return new Promise(async (resolve, reject) => {
702       await queueDownload(obj)
703                         resolve()
704     }).then(() => new Promise((resolve, reject) => {
705       if (token.cancelled)
706         reject()
707       else
708         resolve()
709     }))
710         }
711
712         // Cancels download
713         // TODO: Might check this one, could be a little buggy
714         function cancelDownload(queueId, cleanAll=false){
715                 if (!queueId) return
716                 let cancel = false
717                 let cancelSuccess
718                 if (downloadQueue[queueId]){
719                         cancel = true;
720                         if (downloadQueue[queueId].downloadQueuePromise) downloadQueue[queueId].downloadQueuePromise.cancel()
721                         if (downloadQueue[Object.keys(downloadQueue)[0]].queueId == queueId) {
722                                 trackQueue = queue({
723                                         autostart: true,
724                                         concurrency: trackQueue.concurrency
725                                 })
726                         }
727                         delete downloadQueue[queueId]
728                 }
729
730                 if (cancel) {
731                         io.sockets.emit("cancelDownload", {queueId: queueId, cleanAll: cleanAll});
732                 }
733         }
734         s.on("cancelDownload", function (data) {cancelDownload(data.queueId)});
735
736         s.on("cancelAllDownloads", function(data){
737                 data.queueList.forEach(x=>{
738                         cancelDownload(x, true);
739                 })
740                 io.sockets.emit("cancelAllDownloads")
741         })
742
743         /*function getNextDownload() {
744                 if (s.currentItem != null || Object.keys(downloadQueue).length == 0) {
745                         if (Object.keys(downloadQueue).length == 0 && s.currentItem == null) {
746                                 s.emit("emptyDownloadQueue", {})
747                         }
748                         return null
749                 }
750                 s.currentItem = downloadQueue[Object.keys(downloadQueue)[0]]
751                 return s.currentItem
752         }*/
753
754         //downloadQueue: the tracks in the queue to be downloaded
755         //queueId: random number generated when user clicks download on something
756         async function queueDownload(downloading) {
757                 if (!downloading) return
758
759                 if (downloading.type != "spotifyplaylist"){
760                         io.sockets.emit("downloadStarted", {queueId: downloading.queueId})
761                 }
762
763                 downloading.errorLog = "";
764                 downloading.searchedLog = "";
765
766                 let filePath;
767                 logger.info(`Registered ${downloading.type}: ${downloading.id} | ${downloading.artist} - ${downloading.name}`);
768                 switch(downloading.type){
769                         /*
770                         *  TRACK DOWNLOAD
771                         */
772                         case "track":
773                                 var downloadPromise = new Promise(async (resolve,reject)=>{
774                                         try{
775                                                 await downloadTrackObject(downloading.obj, downloading.queueId, downloading.settings)
776                                                 downloading.downloaded++
777                                         }catch(err){
778                                                 logger.error(`[${downloading.obj.artist.name} - ${downloading.obj.title}] ${err}`)
779                                                 downloading.failed++
780                                         }
781                                         io.sockets.emit("updateQueue", {
782                                                 name: downloading.name,
783                                                 artist: downloading.artist,
784                                                 size: downloading.size,
785                                                 downloaded: downloading.downloaded,
786                                                 failed: downloading.failed,
787                                                 queueId: downloading.queueId,
788                                                 id: downloading.id,
789                                                 type: downloading.type,
790                                         })
791                                         io.sockets.emit("downloadProgress", {
792                                                 queueId: downloading.queueId,
793                                                 percentage: 100
794                                         })
795                                         resolve()
796                                 })
797                                 try{
798                                         await downloadPromise
799                                 }catch(err){
800                                         if (err) logger.error(`queueDownload:track failed: ${err.stack ? err.stack : err}`)
801                                         logger.info("Downloading Stopped")
802                                 }
803                         break
804                         /*
805                         *  ALBUM DOWNLOAD
806                         */
807                         case "album":
808                                 downloading.settings.albName = downloading.name;
809                                 downloading.settings.artName = downloading.artist;
810                                 downloading.playlistArr = Array(downloading.size);
811                                 filePath = mainFolder;
812                                 downloading.obj.genresString = []
813                                 downloading.obj.genres.data.map((x)=>{
814                                         downloading.obj.genresString.push(x.name)
815                                 })
816                                 if (downloading.settings.createArtistFolder || downloading.settings.createAlbumFolder) {
817                                         if (downloading.settings.createArtistFolder) {
818                                                 filePath += antiDot(fixName(downloading.settings.artName)) + path.sep;
819                                         }
820                                         if (downloading.settings.createAlbumFolder) {
821                                                 filePath += antiDot(settingsRegexAlbum(downloading.settings.foldername,downloading.settings.artName,downloading.settings.albName,downloading.obj.release_date.slice(0, 4),downloading.obj.record_type,downloading.obj.explicit_lyrics,downloading.obj.label, downloading.obj.genresString)) + path.sep;
822                                         }
823                                 } else if (downloading.settings.artName) {
824                                         filePath += antiDot(settingsRegexAlbum(downloading.settings.foldername,downloading.settings.artName,downloading.settings.albName,downloading.obj.release_date.slice(0, 4),downloading.obj.record_type,downloading.obj.explicit_lyrics,downloading.obj.label, downloading.obj.genresString)) + path.sep;
825                                 }
826                                 let ajson = {
827                                         artist : downloading.obj.artist,
828                                         nb_tracks : downloading.obj.nb_tracks,
829                                         upc : downloading.obj.upc,
830                                         record_type : downloading.obj.record_type,
831                                         explicit_lyrics : downloading.obj.explicit_lyrics,
832                                         label : downloading.obj.label,
833                                         release_date : downloading.obj.release_date,
834                                         genres : downloading.obj.genres,
835                                         discTotal: downloading.obj.discTotal ? downloading.obj.discTotal : null
836                                 }
837                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
838                                         downloading.obj.tracks.every(function (t) {
839                                                 trackQueue.push(async cb=>{
840                                                         if (!downloadQueue[downloading.queueId]) {
841                                                                 reject()
842                                                                 return false
843                                                         }
844                                                         t.ajson = ajson
845                                                         logger.info(`Now downloading: ${t.artist.name} - ${t.title}`)
846                                                         try{
847                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
848                                                                 downloading.downloaded++
849                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(filePath)[1]
850                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
851                                                         }catch(err){
852                                                                 downloading.failed++
853                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
854                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
855                                                         }
856                                                         io.sockets.emit("downloadProgress", {
857                                                                 queueId: downloading.queueId,
858                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
859                                                         });
860                                                         io.sockets.emit("updateQueue", {
861                                                                 name: downloading.name,
862                                                                 artist: downloading.artist,
863                                                                 size: downloading.size,
864                                                                 downloaded: downloading.downloaded,
865                                                                 failed: downloading.failed,
866                                                                 queueId: downloading.queueId,
867                                                                 id: downloading.id,
868                                                                 type: downloading.type,
869                                                         })
870                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
871                                                         cb()
872                                                 })
873                                                 return true
874                                         })
875                                 })
876                                 try{
877                                         await downloading.downloadPromise
878                                         logger.info("Album finished downloading: "+downloading.name);
879                                         io.sockets.emit("downloadProgress", {
880                                                 queueId: downloading.queueId,
881                                                 percentage: 100
882                                         });
883                                         if (downloading.settings.logErrors){
884                                                 if (downloading.errorLog != ""){
885                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
886                                                         fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
887                                                 }else{
888                                                         if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
889                                                 }
890                                         }
891                                         if (downloading.settings.logSearched){
892                                                 if (downloading.searchedLog != ""){
893                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
894                                                         fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
895                                                 }else{
896                                                         if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
897                                                 }
898                                         }
899                                         if (downloading.settings.createM3UFile){
900                                                 fs.writeFileSync(filePath+"playlist.m3u", downloading.playlistArr.join("\r\n"));
901                                         }
902                                 }catch(err){
903                                         if (err) logger.error(`queueDownload:album failed: ${err.stack ? err.stack : err}`)
904                                         logger.info("Stopping the album queue");
905                                 }
906                         break
907                         /*
908                         *  PLAYLIST DOWNLOAD
909                         */
910                         case "playlist":
911                                 downloading.settings.plName = downloading.name;
912                                 downloading.playlistArr = Array(downloading.size);
913                                 downloading.settings.playlist = {
914                                         fullSize: downloading.obj.tracks.length
915                                 };
916                                 filePath = mainFolder+antiDot(fixName(downloading.settings.plName)) + path.sep
917                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
918                                         downloading.obj.tracks.every(function (t, index) {
919                                                 trackQueue.push(async cb=>{
920                                                         if (!downloadQueue[downloading.queueId]) {
921                                                                 reject()
922                                                                 return false
923                                                         }
924                                                         try{
925                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
926                                                                 downloading.downloaded++
927                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(filePath)[1]
928                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
929                                                         }catch(err){
930                                                                 downloading.failed++
931                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
932                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
933                                                         }
934                                                         io.sockets.emit("downloadProgress", {
935                                                                 queueId: downloading.queueId,
936                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
937                                                         });
938                                                         io.sockets.emit("updateQueue", {
939                                                                 name: downloading.name,
940                                                                 artist: downloading.artist,
941                                                                 size: downloading.size,
942                                                                 downloaded: downloading.downloaded,
943                                                                 failed: downloading.failed,
944                                                                 queueId: downloading.queueId,
945                                                                 id: downloading.id,
946                                                                 type: downloading.type,
947                                                         })
948                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
949                                                         cb()
950                                                 })
951                                                 return true
952                                         })
953                                 })
954                                 try{
955                                         await downloading.downloadPromise
956                                         logger.info("Playlist finished "+downloading.name);
957                                         io.sockets.emit("downloadProgress", {
958                                                 queueId: downloading.queueId,
959                                                 percentage: 100
960                                         });
961                                         if (downloading.settings.logErrors){
962                                                 if (downloading.errorLog != ""){
963                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
964                                                         fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
965                                                 }else{
966                                                         if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
967                                                 }
968                                         }
969                                         if (downloading.settings.logSearched){
970                                                 if (downloading.searchedLog != ""){
971                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
972                                                         fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
973                                                 }else{
974                                                         if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
975                                                 }
976                                         }
977                                         if (downloading.settings.createM3UFile){
978                                                 fs.writeFileSync(filePath + "playlist.m3u", downloading.playlistArr.join("\r\n"));
979                                         }
980                                         if (downloading.settings.saveArtwork){
981                                                 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
982                                                 let imgPath = filePath + antiDot(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
983                                                 if (downloading.cover){
984                                                         downloading.cover = downloading.cover.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`)
985                                                         request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
986                                                                 if(error){
987                                                                         logger.error(error.stack);
988                                                                         return;
989                                                                 }
990                                                                 fs.outputFile(imgPath,body,'binary',function(err){
991                                                                         if(err){
992                                                                                 logger.error(err.stack);
993                                                                                 return;
994                                                                         }
995                                                                         logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
996                                                                 })
997                                                         });
998                                                 }
999                                         }
1000                                 }catch(err){
1001                                         if (err) logger.error(`queueDownload:playlist failed: ${err.stack ? err.stack : err}`)
1002                                         logger.info("Stopping the playlist queue")
1003                                 }
1004                         break
1005                         /*
1006                         *  SPOTIFY PLAYLIST DOWNLOAD
1007                         */
1008                         case "spotifyplaylist":
1009                                 downloading.settings.plName = downloading.name
1010                                 downloading.playlistArr = Array(downloading.size)
1011                                 downloading.playlistContent = new Array(downloading.size)
1012                                 logger.info("Waiting for all tracks to be converted");
1013                                 const convert = async () =>{
1014                                         await asyncForEach(downloading.obj.tracks, async (t,i)=>{
1015                                                 try{
1016                                                         downloading.playlistContent[i] = await convertSpotify2Deezer(t)
1017                                                 }catch(err){
1018                                                         logger.error(`queueDownload:spotifyplaylist failed during conversion: ${err.stack ? err.stack : err}`)
1019                                                 }
1020                                         })
1021                                 }
1022                                 await convert()
1023                                 if (!downloadQueue[downloading.queueId]) {
1024                                         logger.info("Stopping the playlist queue")
1025                                         break
1026                                 }
1027                                 downloading.trackList = await s.Deezer.getTracks(downloading.playlistContent)
1028                                 logger.info("All tracks converted, starting download")
1029                                 io.sockets.emit("downloadStarted", {queueId: downloading.queueId})
1030                                 downloading.settings.playlist = {
1031                                         fullSize: downloading.trackList.length
1032                                 }
1033                                 filePath = `${mainFolder}${antiDot(fixName(downloading.settings.plName))}${path.sep}`
1034                                 downloading.downloadPromise = new Promise((resolve,reject)=>{
1035                                         downloading.trackList.every(function (t, index) {
1036                                                 trackQueue.push(async cb=>{
1037                                                         if (!downloadQueue[downloading.queueId]) {
1038                                                                 reject()
1039                                                                 return false
1040                                                         }
1041                                                         t.position = index
1042                                                         if (t.id==0){
1043                                                                 t.title = downloading.obj.tracks[t.position].name
1044                                               t.album = {id: 0, title: downloading.obj.tracks[t.position].album.name}
1045                                               t.artist = {id: 0, name: downloading.obj.tracks[t.position].artists[0].name}
1046                                                         }
1047                                                         try{
1048                                                                 await downloadTrackObject(t, downloading.queueId, downloading.settings)
1049                                                                 downloading.downloaded++
1050                                                                 downloading.playlistArr[t.playlistData[0]] = t.playlistData[1].split(filePath)[1]
1051                                                                 if (t.searched) downloading.searchedLog += `${t.artist.name} - ${t.title}\r\n`
1052                                                         }catch(err){
1053                                                                 downloading.failed++
1054                                                                 downloading.errorLog += `${t.id} | ${t.artist.name} - ${t.title} | ${err}\r\n`
1055                                                                 logger.error(`[${t.artist.name} - ${t.title}] ${err}`)
1056                                                         }
1057                                                         io.sockets.emit("downloadProgress", {
1058                                                                 queueId: downloading.queueId,
1059                                                                 percentage: ((downloading.downloaded+downloading.failed) / downloading.size) * 100
1060                                                         });
1061                                                         io.sockets.emit("updateQueue", {
1062                                                                 name: downloading.name,
1063                                                                 artist: downloading.artist,
1064                                                                 size: downloading.size,
1065                                                                 downloaded: downloading.downloaded,
1066                                                                 failed: downloading.failed,
1067                                                                 queueId: downloading.queueId,
1068                                                                 id: downloading.id,
1069                                                                 type: downloading.type,
1070                                                         })
1071                                                         if (downloading.downloaded + downloading.failed >= downloading.size) resolve()
1072                                                         cb()
1073                                                 })
1074                                                 return true
1075                                         })
1076                                 })
1077                                 try{
1078                                         await downloading.downloadPromise
1079                                         logger.info("Playlist finished "+downloading.name);
1080                                         io.sockets.emit("downloadProgress", {
1081                                                 queueId: downloading.queueId,
1082                                                 percentage: 100
1083                                         });
1084                                         if (downloading.settings.logErrors){
1085                                                 if (downloading.errorLog != ""){
1086                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1087                                                         fs.writeFileSync(filePath+"notFound.txt",downloading.errorLog)
1088                                                 }else{
1089                                                         if (fs.existsSync(filePath+"notFound.txt")) fs.unlinkSync(filePath+"notFound.txt");
1090                                                 }
1091                                         }
1092                                         if (downloading.settings.logSearched){
1093                                                 if (downloading.searchedLog != ""){
1094                                                         if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1095                                                         fs.writeFileSync(filePath+"alternativeSongs.txt",downloading.searchedLog)
1096                                                 }else{
1097                                                         if (fs.existsSync(filePath+"alternativeSongs.txt")) fs.unlinkSync(filePath+"alternativeSongs.txt");
1098                                                 }
1099                                         }
1100                                         if (downloading.settings.createM3UFile){
1101                                                 fs.writeFileSync(filePath + "playlist.m3u", downloading.playlistArr.join("\r\n"));
1102                                         }
1103                                         if (downloading.settings.saveArtwork){
1104                                                 if (!fs.existsSync(filePath)) fs.mkdirSync(filePath);
1105                                                 let imgPath = filePath + antiDot(settingsRegexCover(downloading.settings.coverImageTemplate,downloading.artist,downloading.name))+(downloading.settings.PNGcovers ? ".png" : ".jpg");
1106                                                 if (downloading.obj.images){
1107                                                         downloading.cover = downloading.obj.images[0].url.replace("56x56",`${downloading.settings.artworkSize}x${downloading.settings.artworkSize}`)
1108                                                         request.get(downloading.cover, {strictSSL: false,encoding: 'binary'}, function(error,response,body){
1109                                                                 if(error){
1110                                                                         logger.error(error.stack);
1111                                                                         return;
1112                                                                 }
1113                                                                 fs.outputFile(imgPath,body,'binary',function(err){
1114                                                                         if(err){
1115                                                                                 logger.error(err.stack);
1116                                                                                 return;
1117                                                                         }
1118                                                                         logger.info(`Cover downloaded for: ${downloading.settings.plName}`)
1119                                                                 })
1120                                                         });
1121                                                 }
1122                                         }
1123                                 }catch(err){
1124                                         if (err) logger.error(`queueDownload:spotifyplaylist failed: ${err.stack ? err.stack : err}`)
1125                                         logger.info("Stopping the playlist queue")
1126                                 }
1127                         break
1128                 }
1129                 if (downloading && downloadQueue[Object.keys(downloadQueue)[0]] && (Object.keys(downloadQueue)[0] == downloading.queueId)) delete downloadQueue[Object.keys(downloadQueue)[0]]
1130                 if (Object.keys(downloadQueue).length == 0) {
1131                         io.sockets.emit("emptyDownloadQueue", {})
1132                 }
1133         }
1134
1135         // This function takes the track object and does all the stuff to download it
1136         async function downloadTrackObject(track, queueId, settings) {
1137                 if (!downloadQueue[queueId]) {
1138                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Not in queue`)
1139                         throw new Error("Not in queue")
1140                         return false
1141                 }
1142                 if (parseInt(track.id) == 0){
1143                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Song not Found`)
1144                         throw new Error("Song not Found")
1145                         return false
1146                 }
1147
1148                 /* Album information is necessary for the following tags:
1149                  * album_artist
1150                  * album_artist_picture
1151                  * trackTotal
1152                  * recordType
1153                  * barcode
1154                  * explicit
1155                  * label
1156                  * genres
1157                  * date
1158                 */
1159                 if (parseInt(track.id)>0){
1160                         var ajson
1161                         if (!track.ajson){
1162                                 try{
1163                                         logger.info(`[${track.artist.name} - ${track.title}] Getting album info`)
1164                                         ajson = await s.Deezer.legacyGetAlbum(track.album.id)
1165                                 }catch(err){
1166                                         logger.warn(`[${track.artist.name} - ${track.title}] Album not found, trying to reach deeper`)
1167                                         try{
1168                                                 ajson = await s.Deezer.getAlbum(track.album.id)
1169                                                 ajson.fromNewAPI = true
1170                                         } catch(err){
1171                                                 if(track.fallbackId){
1172                                                         logger.warn(`[${track.artist.name} - ${track.title}] Failed to download track, falling on alternative`)
1173                                                         track = await s.Deezer.getTrack(track.fallbackId)
1174                                                         return downloadTrackObject(track, queueId, settings)
1175                                                 }else if(!track.searched){
1176                                                         logger.warn(`[${track.artist.name} - ${track.title}] Failed to download track, searching for alternative`)
1177                                                         var _trackID = await convertMetadata2Deezer(track.artist.name, track.title, track.album.title)
1178                                                         if (_trackID != "0"){
1179                                                                 track = await s.Deezer.getTrack()
1180                                                                 track.searched = true
1181                                                                 return downloadTrackObject(track, queueId, settings)
1182                                                         }else{
1183                                                                 logger.error(`[${track.artist.name} - ${track.title}] Failed to download: Alternative not found`)
1184                                                                 return
1185                                                         }
1186                                                 }else{
1187                                                         logger.error(`[${track.artist.name} - ${track.title}] Failed to download: ${err}`)
1188                                                         return
1189                                                 }
1190                                         }
1191                                 }
1192                         }else{
1193                                 ajson = track.ajson
1194                         }
1195                         if (!ajson.fromNewAPI){
1196                                 // Aquiring discTotal (only if necessary)
1197                                 if ((settings.tags.discTotal || settings.createCDFolder) && parseInt(track.id)>0){
1198                                         if (!ajson.discTotal){
1199                                                 logger.info(`[${track.artist.name} - ${track.title}] Getting total disc number`);
1200                                                 var discTotal = await s.Deezer.getAlbum(ajson.id)
1201                                                 track.discTotal = discTotal.discTotal
1202                                         }else{
1203                                                 track.discTotal = ajson.discTotal
1204                                         }
1205                                 }
1206                                 track.album.artist = {
1207                                         id: ajson.artist.id,
1208                                         name: ajson.artist.name,
1209                                         picture: ajson.artist.picture_small.split("/56x56-000000-80-0-0.jpg")[0].split(s.Deezer.artistPictureHost)[1],
1210                                 }
1211                                 track.trackTotal = ajson.nb_tracks
1212                                 track.album.barcode = ajson.upc
1213                                 if (!ajson.record_type){
1214                                         track.recordType = swichReleaseType(track.recordType)
1215                                 }else{
1216                                         track.recordType = ajson.record_type
1217                                 }
1218                                 if (ajson.explicit_lyrics){
1219                                         track.album.explicit = ajson.explicit_lyrics;
1220                                 }
1221                                 if(ajson.label){
1222                                         track.publisher = ajson.label;
1223                                 }
1224                                 if (ajson.release_date) {
1225                                         track.date = {
1226                                                 day: ajson.release_date.slice(8,10),
1227                                                 month: ajson.release_date.slice(5,7),
1228                                                 year: ajson.release_date.slice(0, 4),
1229                                                 slicedYear: (settings.dateFormatYear == "2" ? ajson.release_date.slice(2, 4) : ajson.release_date.slice(0, 4))
1230                                         }
1231                                 }else if(!track.date){
1232                                         track.date = {
1233                                                 day: 0,
1234                                                 month: 0,
1235                                                 year: 0,
1236                                                 slicedYear: 0
1237                                         }
1238                                 }
1239                                 if(ajson.genres && ajson.genres.data[0] && ajson.genres.data[0].name){
1240                                         track.genre = [];
1241                                         genreArray = [];
1242                                         ajson.genres.data.forEach(function(genre){
1243                                                 genreArray.push(genre.name);
1244                                         });
1245                                         track.genre = uniqueArray(genreArray, false)
1246                                 }
1247                         }else{
1248                                 // Missing barcode, genre
1249                                 track.album = ajson
1250                                 track.date = track.album.date
1251                                 track.trackTotal = track.album.trackTotal
1252                                 track.recordType = swichReleaseType(track.recordType)
1253                                 // TODO: Make a loop for each artist
1254                         }
1255
1256                         if (!track.date.slicedYear){
1257                                 track.date.slicedYear = settings.dateFormatYear == "2" ? track.date.year.slice(2, 4) : track.date.year.slice(0, 4)
1258                         }
1259
1260                         // Acquiring bpm (only if necessary)
1261                         if (settings.tags.bpm){
1262                                 logger.info(`[${track.artist.name} - ${track.title}] Getting BPM`);
1263                                 track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1264                                 try{
1265                                         track.bpm = track.legacyTrack.bpm
1266                                 }catch(err){
1267                                         track.bpm = 0
1268                                 }
1269                         }else{
1270                                 track.bpm = 0
1271                         }
1272
1273                         // Acquiring ReplayGain value (only if necessary)
1274                         if (settings.tags.replayGain){
1275                                 logger.info(`[${track.artist.name} - ${track.title}] Getting track gain`);
1276                                 if (!track.legacyTrack) track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1277                                 try{
1278                                         track.replayGain = gain.gain
1279                                 }catch(err){
1280                                         track.replayGain = 0
1281                                 }
1282                         }else{
1283                                 track.replayGain = 0
1284                         }
1285
1286                         // Acquiring discNumber value (only if necessary)
1287                         if (settings.tags.discNumber && !track.discNumber){
1288                                 logger.info(`[${track.artist.name} - ${track.title}] Getting disc number`);
1289                                 if (!track.legacyTrack) track.legacyTrack = await s.Deezer.legacyGetTrack(track.id)
1290                                 track.discNumber = track.legacyTrack.disk_number
1291                         }
1292
1293                         let separator = settings.multitagSeparator
1294                         if (separator == "null") separator = String.fromCharCode(parseInt("\u0000",16))
1295
1296                         // Autoremoves (Album Version) from the title
1297                         if (settings.removeAlbumVersion){
1298                                 if(track.title.indexOf("Album Version")>-1){
1299                                         track.title = track.title.replace(/\(Album Version\)/g,"")
1300                                         track.title.trim()
1301                                 }
1302                         }
1303                         if (!track.album.artist.picture && !settings.plName){
1304                                 if (track.artist.name == track.album.artist.name && !track.album.artist.picture){
1305                                         track.album.artist.picture = track.artist.picture
1306                                 }else{
1307                                         let found = false
1308                                         if (track.artists){
1309                                                 track.artists.forEach(x=>{
1310                                                         if(!found && x.name == track.album.artist.name){
1311                                                                 track.album.artist.picture = x.picture
1312                                                                 found = true
1313                                                         }
1314                                                 })
1315                                         }
1316                                         if(settings.saveArtworkArtist && !found){
1317                                                 artist = await s.Deezer.legacyGetArtist(track.album.artist.id)
1318                                                 track.album.artist.picture = artist.picture_small.split("/56x56-000000-80-0-0.jpg")[0].split(s.Deezer.artistPictureHost)[1]
1319                                         }
1320                                 }
1321                         }
1322                         if (!track.album.artist.picture) track.album.artist.picture = ""
1323                         track.album.artist.pictureUrl = `${s.Deezer.artistPictureHost}${track.album.artist.picture}/${settings.artworkSize}x${settings.artworkSize}-000000-80-0-0${(settings.PNGcovers ? ".png" : ".jpg")}`
1324                         track.album.pictureUrl = `${s.Deezer.albumPicturesHost}${track.album.picture}/${settings.artworkSize}x${settings.artworkSize}-000000-80-0-0${(settings.PNGcovers ? ".png" : ".jpg")}`
1325
1326                         // Auto detect aviable track format from settings
1327                         if (parseInt(track.id)>0){
1328                                 switch(downloadQueue[queueId].bitrate.toString()){
1329                                         case "9":
1330                                                 track.selectedFormat = 9
1331                                                 track.selectedFilesize = track.filesize.flac
1332                                                 if (track.filesize.flac>0) break
1333                                                 if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1334                                         case "3":
1335                                                 track.selectedFormat = 3
1336                                                 track.selectedFilesize = track.filesize.mp3_320
1337                                                 if (track.filesize.mp3_320>0) break
1338                                                 if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1339                                         case "1":
1340                                                 track.selectedFormat = 1
1341                                                 track.selectedFilesize = track.filesize.mp3_128
1342                                                 if (track.filesize.mp3_128>0) break
1343                                                 if (!settings.fallbackBitrate) throw new Error("Song not found at desired bitrate.")
1344                                         default:
1345                                                 track.selectedFormat = 8
1346                                                 track.selectedFilesize = track.filesize.default
1347                                 }
1348                         }else{
1349                                 track.selectedFilesize = track.filesize
1350                                 track.selectedFormat = 3
1351                         }
1352
1353                         if(track.contributor){
1354                                 if(track.contributor.composer){
1355                                         track.composerString = []
1356                                         track.composerString = uniqueArray(track.contributor.composer)
1357                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.composerString = track.composerString.join(separator)
1358                                 }
1359                                 if(track.contributor.musicpublisher){
1360                                         track.musicpublisherString = []
1361                                         track.musicpublisherString = uniqueArray(track.contributor.musicpublisher)
1362                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.musicpublisherString = track.musicpublisherString.join(separator)
1363                                 }
1364                                 if(track.contributor.producer){
1365                                         track.producerString = []
1366                                         track.producerString = uniqueArray(track.contributor.producer)
1367                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.producerString = track.producerString.join(separator)
1368                                 }
1369                                 if(track.contributor.engineer){
1370                                         track.engineerString = []
1371                                         track.engineerString = uniqueArray(track.contributor.engineer)
1372                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.engineerString = track.engineerString.join(separator)
1373                                 }
1374                                 if(track.contributor.writer){
1375                                         track.writerString = []
1376                                         track.writerString = uniqueArray(track.contributor.writer)
1377                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.writerString = track.writerString.join(separator)
1378                                 }
1379                                 if(track.contributor.author){
1380                                         track.authorString = []
1381                                         track.authorString = uniqueArray(track.contributor.author)
1382                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.authorString = track.authorString.join(separator)
1383                                 }
1384                                 if(track.contributor.mixer){
1385                                         track.mixerString = [];
1386                                         track.mixerString = uniqueArray(track.contributor.mixer)
1387                                         if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.mixerString = track.mixerString.join(separator)
1388                                 }
1389                         }
1390
1391                         if(track.artists || track.artistsString){
1392                                 if (!track.artistsString){
1393                                         track.artistsString = []
1394                                         artistArray = []
1395                                         track.artists.forEach(function(artist){
1396                                                 artistArray.push(artist.name)
1397                                         })
1398                                 }else{
1399                                         if (! Array.isArray(track.artistsString)){
1400                                                 track.artistsString = [track.artistsString,]
1401                                         }
1402                                         artistArray = track.artistsString
1403                                 }
1404                                 track.artistsString = uniqueArray(artistArray)
1405                                 let posMainArtist = track.artistsString.indexOf(track.album.artist.name)
1406                                 if (posMainArtist !== -1 && posMainArtist !== 0){
1407                                         let element = track.artistsString[posMainArtist]
1408                                 track.artistsString.splice(posMainArtist, 1)
1409                                 track.artistsString.splice(0, 0, element)
1410                                 }
1411                                 if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16)))) track.artistsString = track.artistsString.join(separator)
1412                         }
1413                         if (track.genre){
1414                                 if (!(track.selectedFormat == 9 && separator==String.fromCharCode(parseInt("\u0000",16))))
1415                                         track.genreString = track.genre.join(separator)
1416                                 else
1417                                         track.genreString = track.genre
1418                         }
1419
1420                         if (track.date){
1421                                 let date
1422                                 switch (settings.dateFormat){
1423                                         case "0": date = `${track.date.slicedYear}-${track.date.month}-${track.date.day}`; break;
1424                                         case "1": date = `${track.date.day}-${track.date.month}-${track.date.slicedYear}`; break;
1425                                         case "2": date = `${track.date.month}-${track.date.day}-${track.date.slicedYear}`; break;
1426                                         case "3": date = `${track.date.slicedYear}-${track.date.day}-${track.date.month}`; break;
1427                                         case "4": date = `${track.date.day}${track.date.month}`; break;
1428                                         default: date = `${track.date.day}${track.date.month}`; break;
1429                                 }
1430                                 track.dateString = date;
1431                         }
1432                 }else{
1433                         track.date = {year: 0,day: 0,month: 0}
1434                 }
1435
1436                 // TODO: Move to a separate function
1437                 // Generating file name
1438                 if (settings.saveFullArtists && settings.multitagSeparator != null){
1439                         let filename = fixName(`${track.artistsString} - ${track.title}`);
1440                 }else{
1441                         let filename = fixName(`${track.artist.name} - ${track.title}`);
1442                 }
1443                 if (settings.filename) {
1444                         filename = settingsRegex(track, settings.filename, settings.playlist, settings.saveFullArtists && settings.multitagSeparator != null, settings.paddingSize, settings.plName && !settings.numplaylistbyalbum);
1445                 }
1446
1447                 // TODO: Move to a separate function
1448                 // Generating file path
1449                 let filepath = mainFolder;
1450                 let artistPath;
1451                 if ((settings.createArtistFolder || settings.createAlbumFolder) && !settings.plName) {
1452
1453                         if(settings.plName){
1454                                 filepath += antiDot(fixName(settings.plName)) + path.sep;
1455                         }
1456
1457                         if (settings.createArtistFolder) {
1458                                 if(settings.artName){
1459                                         filepath += antiDot(fixName(settings.artName)) + path.sep;
1460                                 }else{
1461                                         filepath += antiDot(fixName(track.album.artist.name)) + path.sep;
1462                                 }
1463                                 artistPath = filepath;
1464                         }
1465
1466                         if (settings.createAlbumFolder) {
1467                                 if(settings.artName){
1468                                         filepath += antiDot(settingsRegexAlbum(settings.foldername,settings.artName,settings.albName,track.date.year,track.recordType,track.album.explicit,track.publisher,track.genre)) + path.sep;
1469                                 }else{
1470                                         filepath += antiDot(settingsRegexAlbum(settings.foldername,track.album.artist.name,track.album.title,track.date.year,track.recordType,track.album.explicit,track.publisher,track.genre)) + path.sep;
1471                                 }
1472                         }
1473                 } else if (settings.plName) {
1474                         filepath += antiDot(fixName(settings.plName)) + path.sep;
1475                 } else if (settings.artName) {
1476                         filepath += antiDot(settingsRegexAlbum(settings.foldername,settings.artName,settings.albName,track.date.year,track.recordType,track.album.explicit,track.publisher,track.genre)) + path.sep;
1477                 }
1478                 let coverpath = filepath;
1479                 if (track.discTotal > 1 && (settings.artName || settings.createAlbumFolder) && settings.createCDFolder){
1480                         filepath += `CD${track.discNumber +  path.sep}`
1481                 }
1482                 let writePath;
1483
1484                 if(track.selectedFormat == 9){
1485                         writePath = filepath + filename + '.flac';
1486                 }else{
1487                         writePath = filepath + filename + '.mp3';
1488                 }
1489
1490                 if ((settings.syncedlyrics || settings.tags.unsynchronisedLyrics) && track.lyricsId>0){
1491                         let lyr = await s.Deezer.getLyrics(track.id)
1492                         track.syncLyrics = lyr.syncLyrics
1493                         track.unsyncLyrics = lyr.unsyncLyrics
1494                 }
1495
1496                 if(track.syncLyrics && settings.syncedlyrics){
1497                         fs.outputFile(writePath.substring(0,writePath.lastIndexOf('.'))+".lrc",track.syncLyrics,function(){});
1498                 }
1499
1500                 track.playlistData = [0,""]
1501                 if (settings.createM3UFile && (settings.plName || settings.albName)) {
1502                         if (track.position){
1503                                 track.playlistData = [parseInt(track.position), writePath];
1504                         }else{
1505                                 track.playlistData = [track.trackNumber-1, writePath];
1506                         }
1507                 }
1508                 if (fs.existsSync(writePath)) {
1509                         logger.info(`[${track.artist.name} - ${track.title}] Already downloaded`);
1510                         return;
1511                 }else{
1512                         logger.info(`[${track.artist.name} - ${track.title}] Downloading file to ${writePath}`);
1513                 }
1514                 // Get cover image
1515                 if (track.album.pictureUrl) {
1516                         let imgPath;
1517                         //If its not from an album but a playlist.
1518                         if(settings.albName || settings.createAlbumFolder){
1519                                 if (settings.saveArtwork && ! settings.plName)
1520                                         imgPath = coverpath + settingsRegexCover(settings.coverImageTemplate,track.album.artist.name,track.album.title)+(settings.PNGcovers ? ".png" : ".jpg")
1521                                 else
1522                                         imgPath = coverArtFolder + fixName(track.album.barcode ? fixName(track.album.barcode) : fixName(`${track.album.artist.name} - ${track.album.title}`))+(settings.PNGcovers ? ".png" : ".jpg")
1523                         }else{
1524                                 imgPath = coverArtFolder + (track.album.barcode ? fixName(track.album.barcode) : fixName(`${track.album.artist.name} - ${track.album.title}`))+(settings.PNGcovers ? ".png" : ".jpg")
1525                         }
1526                         if(fs.existsSync(imgPath)){
1527                                 track.album.picturePath = (imgPath).replace(/\\/g, "/")
1528                                 logger.info(`[${track.artist.name} - ${track.title}] Starting the download process CODE:1`)
1529                         }else{
1530                                 try{
1531                                         var body = await request.get(track.album.pictureUrl, {strictSSL: false,encoding: 'binary'})
1532                                         fs.outputFileSync(imgPath,body,'binary')
1533                                         track.album.picturePath = (imgPath).replace(/\\/g, "/")
1534                                         logger.info(`[${track.artist.name} - ${track.title}] Starting the download process CODE:2`)
1535                                 }catch(error){
1536                                         logger.error(`[${track.artist.name} - ${track.title}] Cannot download Album Image: ${error}`)
1537                                         logger.error(`Album art link: ${track.album.pictureUrl}`)
1538                                         track.album.pictureUrl = undefined
1539                                         track.album.picturePath = undefined
1540                                 }
1541                         }
1542                 }else{
1543                         track.album.pictureUrl = undefined
1544                         logger.info(`[${track.artist.name} - ${track.title}] Starting the download process CODE:3`)
1545                 }
1546
1547                 // Get Artist Image
1548                 if (parseInt(track.id)>0 && track.album.artist.pictureUrl && settings.saveArtworkArtist) {
1549                         let imgPath;
1550                         if(settings.createArtistFolder && artistPath){
1551                                 imgPath = artistPath + antiDot(settingsRegexArtistCover(settings.artistImageTemplate,track.album.artist.name))+(settings.PNGcovers ? ".png" : ".jpg");
1552                                 if(!fs.existsSync(imgPath)){
1553                                         try{
1554                                                 var body = await request.get(track.album.artist.pictureUrl, {strictSSL: false,encoding: 'binary'})
1555                                                 if (body.indexOf("unauthorized")>-1) throw new Error("Unauthorized")
1556                                                 fs.outputFileSync(imgPath,body,'binary')
1557                                                 logger.info(`[${track.artist.name} - ${track.title}] Saved Artist Image`)
1558                                         }catch(err){
1559                                                 logger.error(`[${track.artist.name} - ${track.title}] Cannot download Artist Image: ${err}`)
1560                                         }
1561                                 }
1562                         }
1563                 }
1564
1565                 let tempPath
1566                 if(parseInt(track.id)>0)
1567                         tempPath = `${filepath}${track.id}_${track.selectedFormat}.temp`
1568                 else
1569                         tempPath = writePath
1570
1571                 logger.info(`[${track.artist.name} - ${track.title}] Downloading track`)
1572                 var downloadingPromise = new Promise((resolve, reject)=>{
1573                         let req = requestOld.get({url: track.getDownloadUrl(track.selectedFormat), strictSSL: false, headers: s.Deezer.httpHeaders, encoding: 'binary'}, function (error, response, body) {
1574                                 if (error){
1575                                         logger.error(`[${track.artist.name} - ${track.title}] Downloading error: ${error}`)
1576                                         reject("Downloading error: "+error)
1577                                         return false
1578                                 }
1579                                 if (!downloadQueue[queueId]){
1580                                         fs.remove(tempPath)
1581                                         reject("Not in Queue")
1582                                         return false
1583                                 }
1584                                 if (body.length == 0){
1585                                         fs.remove(tempPath)
1586                                         reject("Track is Empty")
1587                                         return false
1588                                 }
1589                                 logger.info(`[${track.artist.name} - ${track.title}] Decrypting track`)
1590                                 var decryptedSource = s.Deezer.decryptDownload(Buffer.from(body, 'binary'), track.id)
1591                                 try{
1592                                         fs.outputFileSync(tempPath,decryptedSource)
1593                                         resolve()
1594                                 }catch(err){
1595                                         logger.error(`[${track.artist.name} - ${track.title}] Decryption error: ${err}`)
1596                                         reject(err)
1597                                         return false
1598                                 }
1599                         }).on("data", function(data) {
1600                                 if (!downloadQueue[queueId]){
1601                                         reject("Not in Queue")
1602                                         return false
1603                                 }
1604                         })
1605                         if((downloadQueue[queueId]) && downloadQueue[queueId].type == "track"){
1606                                 let chunkLength = 0
1607                                 req.on("data", function(data) {
1608                                         if (!downloadQueue[queueId]){
1609                                                 reject("Not in Queue")
1610                                         }
1611                                         chunkLength += data.length
1612                                         try{
1613                                                 if (!downloadQueue[queueId].percentage) {
1614                                                         downloadQueue[queueId].percentage = 0
1615                                                 }
1616                                                 let complete = track.selectedFilesize
1617                                                 let percentage = (chunkLength / complete) * 100;
1618                                                 if ((percentage - downloadQueue[queueId].percentage > 1) || (chunkLength == complete)) {
1619                                                         downloadQueue[queueId].percentage = percentage
1620                                                         io.sockets.emit("downloadProgress", {
1621                                                                 queueId: queueId,
1622                                                                 percentage: downloadQueue[queueId].percentage-5
1623                                                         })
1624                                                 }
1625                                         }catch(err){}
1626                                 })
1627                         }
1628                 })
1629
1630                 try{
1631                         await downloadingPromise
1632                 }catch(err){
1633                         if (err==="Track is Empty"){
1634                                 if(track.fallbackId && track.fallbackId != "0"){
1635                                         logger.warn(`[${track.artist.name} - ${track.title}] Track is empty, falling on alternative`)
1636                                         var _track = await s.Deezer.getTrack(track.fallbackId)
1637                                         track.id = _track.id
1638                                         track.fallbackId = _track.fallbackId
1639                                         track.filesize = _track.filesize
1640                                         track.duration = _track.duration
1641                                         track.MD5 = _track.MD5
1642                                         track.mediaVersion = _track.mediaVersion
1643                                         return downloadTrackObject(track, queueId, settings)
1644                                 }else if(!track.searched){
1645                                         logger.warn(`[${track.artist.name} - ${track.title}] Track is empty, searching for alternative`)
1646                                         _trackId = await convertMetadata2Deezer(track.artist.name, track.title, track.album.title)
1647                                         if (_trackId != "0"){
1648                                                 _track = await s.Deezer.getTrack(_trackId)
1649                                                 track.id = _track.id
1650                                                 track.fallbackId = _track.fallbackId
1651                                                 track.filesize = _track.filesize
1652                                                 track.duration = _track.duration
1653                                                 track.MD5 = _track.MD5
1654                                                 track.mediaVersion = _track.mediaVersion
1655                                                 track.searched = true
1656                                                 return downloadTrackObject(track, queueId, settings)
1657                                         }else{
1658                                                 logger.error(`[${track.artist.name} - ${track.title}] No alternative found`)
1659                                                 throw new Error("No Alternative Found")
1660                                                 return
1661                                         }
1662                                 }else{
1663                                         logger.error(`[${track.artist.name} - ${track.title}] Downloading error: Track is Empty`)
1664                                         throw new Error("Track is Empty")
1665                                         return
1666                                 }
1667                         }else{
1668                                 throw new Error(err)
1669                                 return
1670                         }
1671                 }
1672
1673                 logger.info(`[${track.artist.name} - ${track.title}] Adding Tags`)
1674                 if (parseInt(track.id)>0){
1675                         if(track.selectedFormat == 9){
1676                                 let flacComments = [];
1677                                 if (settings.tags.title)
1678                                         flacComments.push('TITLE=' + track.title);
1679                                 if (settings.tags.album)
1680                                         flacComments.push('ALBUM=' + track.album.title);
1681                                 if (settings.tags.albumArtist)
1682                                         flacComments.push('ALBUMARTIST=' + track.album.artist.name);
1683                                 if (settings.tags.trackNumber)
1684                                         flacComments.push('TRACKNUMBER=' + track.trackNumber);
1685                                 if (settings.tags.discNumber)
1686                                         flacComments.push('DISCNUMBER=' + track.discNumber);
1687                                 if (settings.tags.trackTotal)
1688                                         flacComments.push('TRACKTOTAL=' + track.trackTotal);
1689                                 if (settings.tags.explicit)
1690                                         flacComments.push('ITUNESADVISORY=' + track.explicit);
1691                                 if (settings.tags.isrc)
1692                                         flacComments.push('ISRC=' + track.ISRC);
1693                                 if (settings.tags.artist && track.artistsString)
1694                                         if (Array.isArray(track.artistsString)){
1695                                                 track.artistsString.forEach(x=>{
1696                                                         flacComments.push('ARTIST=' + x);
1697                                                 });
1698                                         }else{
1699                                                 flacComments.push('ARTIST=' + track.artistsString);
1700                                         }
1701                                 if (settings.tags.discTotal)
1702                                         flacComments.push('DISCTOTAL='+track.discTotal);
1703                                 if (settings.tags.length)
1704                                         flacComments.push('LENGTH=' + track.duration);
1705                                 if (settings.tags.barcode && track.album.barcode)
1706                                         flacComments.push('BARCODE=' + track.album.barcode);
1707                                 if (track.unsyncLyrics && settings.tags.unsynchronisedLyrics)
1708                                         flacComments.push('LYRICS='+track.unsyncLyrics.lyrics);
1709                                 if (track.genreString && settings.tags.genre)
1710                                         if (Array.isArray(track.genreString)){
1711                                                 track.genreString.forEach(x=>{
1712                                                         flacComments.push('GENRE=' + x);
1713                                                 });
1714                                         }else{
1715                                                 flacComments.push('GENRE=' + track.genreString);
1716                                         }
1717                                 if (track.copyright && settings.tags.copyright)
1718                                         flacComments.push('COPYRIGHT=' + track.copyright);
1719                                 if (0 < parseInt(track.date.year)){
1720                                         if (settings.tags.date)
1721                                                 flacComments.push('DATE=' + track.dateString);
1722                                         if (settings.tags.year)
1723                                                 flacComments.push('YEAR=' + track.date.year);
1724                                 }
1725                                 if (0 < parseInt(track.bpm) && settings.tags.bpm)
1726                                         flacComments.push('BPM=' + track.bpm);
1727                                 if(track.publisher && settings.tags.publisher)
1728                                         flacComments.push('PUBLISHER=' + track.publisher);
1729                                 if(track.composerString && settings.tags.composer)
1730                                         if (Array.isArray(track.composerString)){
1731                                                 track.composerString.forEach(x=>{
1732                                                         flacComments.push('COMPOSER=' + x);
1733                                                 });
1734                                         }else{
1735                                                 flacComments.push('COMPOSER=' + track.composerString);
1736                                         }
1737                                 if(track.musicpublisherString && settings.tags.musicpublisher)
1738                                         if (Array.isArray(track.musicpublisherString)){
1739                                                 track.musicpublisherString.forEach(x=>{
1740                                                         flacComments.push('ORGANIZATION=' + x);
1741                                                 });
1742                                         }else{
1743                                                 flacComments.push('ORGANIZATION=' + track.musicpublisherString);
1744                                         }
1745                                 if(track.mixerString && settings.tags.mixer)
1746                                         if (Array.isArray(track.mixerString)){
1747                                                 track.mixerString.forEach(x=>{
1748                                                         flacComments.push('MIXER=' + x);
1749                                                 });
1750                                         }else{
1751                                                 flacComments.push('MIXER=' + track.mixerString);
1752                                         }
1753                                 if(track.authorString && settings.tags.author)
1754                                         if (Array.isArray(track.authorString)){
1755                                                 track.authorString.forEach(x=>{
1756                                                         flacComments.push('AUTHOR=' + x);
1757                                                 });
1758                                         }else{
1759                                                 flacComments.push('AUTHOR=' + track.authorString);
1760                                         }
1761                                 if(track.writerString && settings.tags.writer)
1762                                         if (Array.isArray(track.writerString)){
1763                                                 track.writerString.forEach(x=>{
1764                                                         flacComments.push('WRITER=' + x);
1765                                                 });
1766                                         }else{
1767                                                 flacComments.push('WRITER=' + track.writerString);
1768                                         }
1769                                 if(track.engineerString && settings.tags.engineer)
1770                                         if (Array.isArray(track.engineerString)){
1771                                                 track.engineerString.forEach(x=>{
1772                                                         flacComments.push('ENGINEER=' + x);
1773                                                 });
1774                                         }else{
1775                                                 flacComments.push('ENGINEER=' + track.engineerString);
1776                                         }
1777                                 if(track.producerString && settings.tags.producer)
1778                                         if (Array.isArray(track.producerString)){
1779                                                 track.producerString.forEach(x=>{
1780                                                         flacComments.push('PRODUCER=' + x);
1781                                                 });
1782                                         }else{
1783                                                 flacComments.push('PRODUCER=' + track.producerString);
1784                                         }
1785                                 if(track.replayGain && settings.tags.replayGain)
1786                                         flacComments.push('REPLAYGAIN_TRACK_GAIN=' + track.replayGain);
1787
1788                                 const reader = fs.createReadStream(tempPath);
1789                                 const writer = fs.createWriteStream(writePath);
1790                                 let processor = new mflac.Processor({parseMetaDataBlocks: true});
1791                                 let vendor = 'reference libFLAC 1.2.1 20070917';
1792                                 let cover = null;
1793                                 if(track.album.picturePath && settings.tags.cover){
1794                                         cover = fs.readFileSync(track.album.picturePath)
1795                                 }
1796                                 let mdbVorbisPicture
1797                                 let mdbVorbisComment
1798                                 processor.on('preprocess', (mdb) => {
1799                                         // Remove existing VORBIS_COMMENT and PICTURE blocks, if any.
1800                                         if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type) {
1801                                                 mdb.remove();
1802                                         } else if (mflac.Processor.MDB_TYPE_PICTURE === mdb.type) {
1803                                                 mdb.remove();
1804                                         }
1805                                         if (mdb.isLast) {
1806                                                 if(cover){
1807                                                         mdbVorbisPicture = mflac.data.MetaDataBlockPicture.create(true, 3, `image/${(settings.PNGcovers ? "png" : "jpeg")}`, '', settings.artworkSize, settings.artworkSize, 24, 0, cover);
1808                                                 }
1809                                                 mdbVorbisComment = mflac.data.MetaDataBlockVorbisComment.create(!cover, vendor, flacComments);
1810                                                 mdb.isLast = false;
1811                                         }
1812                                 });
1813                                 processor.on('postprocess', (mdb) => {
1814                                         if (mflac.Processor.MDB_TYPE_VORBIS_COMMENT === mdb.type && null !== mdb.vendor) {
1815                                                 vendor = mdb.vendor;
1816                                         }
1817                                         if (mdbVorbisPicture && mdbVorbisComment) {
1818                                                         processor.push(mdbVorbisComment.publish());
1819                                                         processor.push(mdbVorbisPicture.publish());
1820                                                 }else if(mdbVorbisComment){
1821                                                         processor.push(mdbVorbisComment.publish());
1822                                         }
1823                                 });
1824                                 reader.on('end', () => {
1825                                         fs.remove(tempPath);
1826                                 });
1827                                 await reader.pipe(processor).pipe(writer);
1828                         }else{
1829                                 const songBuffer = fs.readFileSync(tempPath);
1830                                 const writer = new ID3Writer(songBuffer);
1831                                 if (settings.tags.title)
1832                                         writer.setFrame('TIT2', track.title)
1833                                 if (settings.tags.artist)
1834                                         writer.setFrame('TPE1', [track.artistsString])
1835                                 if (settings.tags.album)
1836                                         writer.setFrame('TALB', track.album.title)
1837                                 if (settings.tags.albumArtist && track.album.artist)
1838                                         writer.setFrame('TPE2', track.album.artist.name)
1839                                 if (settings.tags.trackNumber)
1840                                         writer.setFrame('TRCK', (settings.tags.trackTotal ? track.trackNumber+"/"+track.trackTotal : track.trackNumber))
1841                                 if (settings.tags.discNumber)
1842                                         writer.setFrame('TPOS', (settings.tags.discTotal ? track.discNumber+"/"+track.discTotal : track.discNumber))
1843                                 if (settings.tags.isrc)
1844                                         writer.setFrame('TSRC', track.ISRC);
1845
1846                                 if (settings.tags.length)
1847                                         writer.setFrame('TLEN', track.duration);
1848                                 if (settings.tags.barcode && track.album.barcode)
1849                                         writer.setFrame('TXXX', {
1850                                                 description: 'BARCODE',
1851                                                 value: track.album.barcode
1852                                         });
1853                                 if(track.album.picturePath && settings.tags.cover){
1854                                         const coverBuffer = fs.readFileSync(track.album.picturePath);
1855                                         writer.setFrame('APIC', {
1856                                                 type: 3,
1857                                                 data: coverBuffer,
1858                                                 description: ''
1859                                         });
1860                                 }
1861                                 if(track.unsyncLyrics && settings.tags.unsynchronisedLyrics)
1862                                         writer.setFrame('USLT', track.unsyncLyrics);
1863                                 if(track.publisher && settings.tags.publisher)
1864                                         writer.setFrame('TPUB', track.publisher);
1865                                 if(track.genreString && settings.tags.genre)
1866                                         writer.setFrame('TCON', [track.genreString]);
1867                                 if(track.copyright && settings.tags.copyright)
1868                                         writer.setFrame('TCOP', track.copyright);
1869                                 if (0 < parseInt(track.date.year)) {
1870                                         if (settings.tags.date)
1871                                                 writer.setFrame('TDAT', track.dateString);
1872                                         if (settings.tags.year)
1873                                                 writer.setFrame('TYER', track.date.year);
1874                                 }
1875                                 if (0 < parseInt(track.bpm) && settings.tags.bpm)
1876                                         writer.setFrame('TBPM', track.bpm);
1877                                 if(track.composerString && settings.tags.composer)
1878                                         writer.setFrame('TCOM', [track.composerString]);
1879                                 if(track.replayGain && settings.tags.replayGain)
1880                                         writer.setFrame('TXXX', {
1881                                                 description: 'REPLAYGAIN_TRACK_GAIN',
1882                                                 value: track.replayGain
1883                                         });
1884                                 writer.addTag();
1885                                 const taggedSongBuffer = Buffer.from(writer.arrayBuffer);
1886                                 fs.writeFileSync(writePath, taggedSongBuffer);
1887                                 fs.remove(tempPath);
1888                         }
1889                 }
1890                 logger.info(`[${track.artist.name} - ${track.title}] Downloaded`)
1891         }
1892 })
1893
1894 // Helper functions
1895
1896 /**
1897  * Updates individual parameters in the settings file
1898  * @param config
1899  * @param value
1900  */
1901 function updateSettingsFile(config, value) {
1902         configFile.userDefined[config] = value;
1903
1904         fs.outputFile(configFileLocation, JSON.stringify(configFile, null, 2), function (err) {
1905                 if (err) return;
1906                 logger.info("Settings updated");
1907
1908                 // FIXME: Endless Loop, due to call from initFolders()...crashes soon after startup
1909                 // initFolders();
1910         });
1911 }
1912
1913 function fixName (txt) {
1914         txt = txt+""
1915   const regEx = /[\0\/\\:*?"<>|]/g;
1916   txt = txt.replace(regEx, '_');
1917   txt = txt.slice(0,200);
1918   return txt;
1919 }
1920
1921 function antiDot(str){
1922         while(str[str.length-1] == "." || str[str.length-1] == " " || str[str.length-1] == "\n"){
1923                 str = str.substring(0,str.length-1);
1924         }
1925         if(str.length < 1){
1926                 str = "dot";
1927         }
1928         return str;
1929 }
1930
1931 /**
1932  * Initialize the temp folder for covers and main folder for downloads
1933  */
1934 function initFolders() {
1935         // Check if main folder exists
1936         if (!fs.existsSync(mainFolder)) {
1937                 mainFolder = defaultDownloadFolder;
1938                 updateSettingsFile('downloadLocation', defaultDownloadFolder);
1939         }
1940         //fs.removeSync(coverArtFolder);
1941         //fs.ensureFolderSync(coverArtFolder);
1942 }
1943
1944 /**
1945  * Creates the name of the tracks replacing wildcards to correct metadata
1946  * @param track
1947  * @param filename
1948  * @param playlist
1949  * @returns {XML|string|*}
1950  */
1951 function settingsRegex(track, filename, playlist, saveFullArtists, paddingSize, playlistNumbering) {
1952         try{
1953                 filename = filename.replace(/%title%/g, fixName(track.title));
1954                 filename = filename.replace(/%album%/g, fixName(track.album.title));
1955                 filename = filename.replace(/%artist%/g, fixName((saveFullArtists ? track.artistsString : track.artist.name)));
1956                 filename = filename.replace(/%year%/g, fixName(track.date.year));
1957                 filename = filename.replace(/%label%/g, fixName(track.publisher));
1958                 let tNumber = playlistNumbering ? track.position+1 : track.trackNumber
1959                 let tTotal = playlistNumbering ? playlist.fullSize : track.trackTotal
1960                 if(typeof tNumber != 'undefined'){
1961                         if(configFile.userDefined.padtrck){
1962                                  filename = filename.replace(/%number%/g, fixName(pad(tNumber, (parseInt(paddingSize)>0 ? parseInt(paddingSize) : tTotal))));
1963                         }else{
1964                                 filename = filename.replace(/%number%/g, fixName(tNumber));
1965                         }
1966                 } else {
1967                         filename = filename.replace(/%number%/g, '');
1968                 }
1969                 filename = filename.replace(/%explicit%/g, fixName((track.explicit==="1" ? (filename.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit Version)") : "")));
1970                 filename = filename.replace(/%label%/g, fixName(track.genre ? (Array.isArray(track.genre) ? track.genre[0] : track.genre) : "Unknown"));
1971                 filename = filename.replace(/[/\\]/g, path.sep)
1972                 return filename.trim();
1973         }catch(e){
1974                 logger.error("settingsRegex failed: "+e)
1975         }
1976 }
1977
1978 /**
1979  * Creates the name of the albums folder replacing wildcards to correct metadata
1980  * @param metadata
1981  * @param foldername
1982  * @returns {XML|string|*}
1983  */
1984 function settingsRegexAlbum(foldername, artist, album, year, rtype, explicit, publisher, genres) {
1985         try{
1986                 foldername = foldername.replace(/%album%/g, fixName(album))
1987                 foldername = foldername.replace(/%artist%/g, fixName(artist))
1988                 foldername = foldername.replace(/%year%/g, fixName(year))
1989                 if (rtype){
1990                         foldername = foldername.replace(/%type%/g, fixName(rtype[0].toUpperCase() + rtype.substring(1)))
1991                 }else{
1992                         foldername = foldername.replace(/%type%/g, "")
1993                 }
1994                 foldername = foldername.replace(/%label%/g, fixName(publisher))
1995                 foldername = foldername.replace(/%explicit%/g, fixName((explicit ? (foldername.indexOf(/[^%]explicit/g)>-1 ? "" : "(Explicit) ") : "")))
1996                 foldername = foldername.replace(/%genre%/g, fixName(genres ? (Array.isArray(genres) ? genres[0] : genres) : "Unknown"))
1997                 foldername = foldername.replace(/[/\\]/g, path.sep)
1998                 return foldername.trim();
1999         }catch(e){
2000                 logger.error("settingsRegexAlbum failed: "+e)
2001         }
2002
2003 }
2004
2005 function settingsRegexCover(foldername, artist, name) {
2006         foldername = foldername.replace(/%name%/g, fixName(name));
2007         foldername = foldername.replace(/%artist%/g, fixName(artist));
2008         foldername = foldername.replace(/[/\\]/g, path.sep)
2009         return foldername;
2010 }
2011
2012 function settingsRegexArtistCover(foldername, artist) {
2013         foldername = foldername.replace(/%artist%/g, fixName(artist));
2014         foldername = foldername.replace(/[/\\]/g, path.sep)
2015         return foldername;
2016 }
2017
2018 /**
2019  * Pad number with 0s so max and str have the same nuber of characters
2020  * @param str
2021  * @param max
2022  * @returns {String|string|*}
2023  */
2024 function pad(str, max) {
2025         str = str.toString();
2026         max = max.toString();
2027         return str.length < max.length || str.length == 1 ? pad("0" + str, max) : str;
2028 }
2029
2030 /**
2031  * Splits the %number%
2032  * @param string str
2033  * @return string
2034  */
2035 function splitNumber(str,total){
2036         str = str.toString();
2037         let i = str.indexOf("/");
2038         if(total && i > 0){
2039                 return str.slice(i+1, str.length);
2040         }else if(i > 0){
2041                 return str.slice(0, i);
2042         }else{
2043                 return str;
2044         }
2045         return i > 0 ? str.slice(0, i) : str;
2046 }
2047
2048 function swichReleaseType(id){
2049         switch (id.toString()) {
2050                 case "0":
2051                         return "Album";
2052                 case "1":
2053                         return "Single";
2054                 case "3":
2055                         return "EP";
2056                 default:
2057                         return "Album";
2058         }
2059 }
2060
2061 function uniqueArray(origin, removeDupes=true){
2062         destination = []
2063         Array.from(new Set(origin)).forEach(function(x){
2064                 if(destination.indexOf(x) == -1)
2065                         destination.push(x);
2066         })
2067         if (removeDupes){
2068                 destination.forEach((name,index)=>{
2069                         destination.forEach((name2,index2)=>{
2070                                 if(!(index===index2) && (name.toLowerCase().indexOf(name2.toLowerCase())!== -1)){
2071                                         destination.splice(index, 1);
2072                                 }
2073                         })
2074                 })
2075         }
2076         return destination
2077 }
2078
2079 async function asyncForEach(array, callback) {
2080   for (let index = 0; index < array.length; index++) {
2081     await callback(array[index], index, array);
2082   }
2083 }
2084
2085 // Show crash error in console for debugging
2086 process.on('unhandledRejection', function (err) {
2087         if (err) logger.error(err.stack ? err.stack : err)
2088
2089 })
2090 process.on('uncaughtException', function (err) {
2091         if (err) logger.error(err.stack ? err.stack : err)
2092 })
2093
2094 // Exporting vars
2095 module.exports.mainFolder = mainFolder
2096 module.exports.defaultSettings = defaultSettings
2097 module.exports.defaultDownloadFolder = defaultDownloadFolder