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