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