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