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