1 // Starting area, boot up the API and proceed to eat memory
3 // Variables & constants
4 const socket = io.connect(window.location.href);
5 if(typeof mainApp !== "undefined"){
6 var defaultUserSettings = mainApp.defaultSettings;
7 var defaultDownloadLocation = mainApp.defaultDownloadDir;
11 let preview_track = document.getElementById('preview-track');
12 let preview_stopped = true;
14 socket.emit("autologin");
16 socket.on("message", function(title, msg){
21 $('#modal_login_btn_login').click(function () {
22 $('#modal_login_btn_login').attr("disabled", true);
23 $('#modal_login_btn_login').html("Logging in...");
24 var username = $('#modal_login_input_username').val();
25 var password = $('#modal_login_input_password').val();
26 var autologin = $('#modal_login_input_autologin').prop("checked");
27 //Send to the software
28 socket.emit('login', username, password,autologin);
31 socket.on("autologin",function(username,password){
32 $('#modal_login_input_autologin').prop('checked', true);
33 $('#modal_login_btn_login').attr("disabled", true);
34 $('#modal_login_btn_login').html("Logging in...");
35 $('#modal_login_input_username').val(username);
36 $('#modal_login_input_password').val(password);
38 socket.emit('login', username, password,false);
41 socket.on("login", function (data) {
43 $("#modal_settings_username").html(data.username);
44 $("#modal_settings_picture").attr("src",data.picture);
45 $("#side_user").text(data.username);
46 $("#side_avatar").attr("src",data.picture);
47 $("#side_email").text(data.email);
48 $('#initializing').addClass('animated fadeOut').on('webkitAnimationEnd', function () {
49 $(this).css('display', 'none');
50 $(this).removeClass('animated fadeOut');
52 // Load top charts list for countries
53 socket.emit("getChartsCountryList", {selected: userSettings.chartsCountry});
54 socket.emit("getChartsTrackListByCountry", {country: userSettings.chartsCountry});
55 // Load personal pubblic playlists
56 socket.emit("getMePlaylistList", {});
58 $('#login-res-text').text(data.error);
59 setTimeout(function(){$('#login-res-text').text("");},3000);
61 $('#modal_login_btn_login').attr("disabled", false);
62 $('#modal_login_btn_login').html("Login");
65 // Open downloads folder
66 $('#openDownloadsFolder').on('click', function () {
67 if(typeof shell !== "undefined"){
68 shell.showItemInFolder(userSettings.downloadLocation + path.sep + '.');
70 alert("For security reasons, this button will do nothing.");
74 // Do misc stuff on page load
75 $(document).ready(function () {
77 preview_track.volume = 0;
79 $('.sidenav').sidenav({
83 var tabs = M.Tabs.getInstance(document.getElementById("tab-nav"));
85 $('.sidenav_tab').click((e)=>{
87 $(e.currentTarget).addClass("active");
88 tabs.select($(e.currentTarget).attr('tab-id'));
89 tabs.updateTabIndicator();
92 $(window).scroll(function () {
93 if ($(this).scrollTop() > 100) {
94 $('#btn_scrollToTop a').removeClass('scale-out').addClass('scale-in');
96 $('#btn_scrollToTop a').removeClass('scale-in').addClass('scale-out');
100 $('#btn_scrollToTop').click(function () {
101 $('html, body').animate({scrollTop: 0}, 800);
105 $("#button_refresh_playlist_tab").click(function(){
106 $("table_personal_playlists").html("");
107 socket.emit("getMePlaylistList", {});
110 $(preview_track).on('canplay', ()=>{
111 preview_track.play();
112 preview_stopped = false;
113 $(preview_track).animate({volume: 1}, 500);
116 $(preview_track).on('timeupdate', ()=>{
117 if (preview_track.currentTime > preview_track.duration-1){
118 $(preview_track).animate({volume: 0}, 800);
119 preview_stopped = true;
120 $("*").removeAttr("playing");
121 $('.preview_controls').text("play_arrow");
122 $('.preview_playlist_controls').text("play_arrow");
126 $('#nightTimeSwitcher').change(function(){
128 document.getElementsByTagName('link')[4].disabled = false;
129 $("#nightModeSwitch2").html(`<i class="material-icons">brightness_7</i>Disable Night Mode`)
131 document.getElementsByTagName('link')[4].disabled = true;
132 $("#nightModeSwitch2").html(`<i class="material-icons">brightness_2</i>Enable Night Mode`)
134 localStorage.darkMode = this.checked;
137 $('#nightModeSwitch2').click(()=>{
138 $('#nightTimeSwitcher').prop('checked', !$('#nightTimeSwitcher').prop('checked'))
139 $('#nightTimeSwitcher').change();
142 if (eval(localStorage.darkMode)){
143 $('#nightTimeSwitcher').prop('checked', true);
144 $('#nightTimeSwitcher').change();
146 $('#nightTimeSwitcher').prop('checked', false);
147 $('#nightTimeSwitcher').change();
150 $('#downloadChartPlaylist').click(function(){
151 addToQueue(`https://www.deezer.com/playlist/${$(this).data("id")}`);
155 socket.emit("getUserSettings");
157 $('#modal_trackList, #modal_trackListSelective').modal({
159 if ($('.preview_playlist_controls').filter(function(){return $(this).attr("playing")}).length > 0){
160 $(preview_track).animate({volume: 0}, 800);
161 preview_stopped = true;
162 $(".preview_playlist_controls").removeAttr("playing");
163 $('.preview_playlist_controls').text("play_arrow");
168 $('input[name=searchMode][type=radio]').change(()=>{
169 $('#tab_search_form_search').submit();
174 socket.on('getUserSettings', function (data) {
175 userSettings = data.settings;
176 console.log('Settings refreshed');
183 // Prevent default behavior of closing button
184 $('.modal-close').click(function (e) {
188 // Settings Modal START
189 const $settingsAreaParent = $('#modal_settings');
191 // Open settings panel
192 $('#nav_btn_openSettingsModal').click(function () {
193 fillSettingsModal(userSettings);
196 // Save settings button
197 $('#modal_settings_btn_saveSettings').click(function () {
200 settings.userDefined = {
201 trackNameTemplate: $('#modal_settings_input_trackNameTemplate').val(),
202 playlistTrackNameTemplate: $('#modal_settings_input_playlistTrackNameTemplate').val(),
203 albumTrackNameTemplate: $('#modal_settings_input_albumTrackNameTemplate').val(),
204 albumNameTemplate: $('#modal_settings_input_albumNameTemplate').val(),
205 coverImageTemplate: $('#modal_settings_input_coverImageTemplate').val(),
206 artistImageTemplate: $('#modal_settings_input_artistImageTemplate').val(),
207 createM3UFile: $('#modal_settings_cbox_createM3UFile').is(':checked'),
208 createArtistFolder: $('#modal_settings_cbox_createArtistFolder').is(':checked'),
209 createAlbumFolder: $('#modal_settings_cbox_createAlbumFolder').is(':checked'),
210 createCDFolder: $('#modal_settings_cbox_createCDFolder').is(':checked'),
211 downloadLocation: $('#modal_settings_input_downloadTracksLocation').val(),
212 artworkSize: parseInt($('#modal_settings_select_artworkSize').val()),
213 hifi: $('#modal_settings_cbox_hifi').is(':checked'),
214 padtrck: $('#modal_settings_cbox_padtrck').is(':checked'),
215 syncedlyrics: $('#modal_settings_cbox_syncedlyrics').is(':checked'),
216 numplaylistbyalbum: $('#modal_settings_cbox_numplaylistbyalbum').is(':checked'),
217 chartsCountry: $('#modal_settings_select_chartsCounrty').val(),
218 spotifyUser: $('#modal_settings_input_spotifyUser').val(),
219 saveArtwork: $('#modal_settings_cbox_saveArtwork').is(':checked'),
220 saveArtworkArtist: $('#modal_settings_cbox_saveArtworkArtist').is(':checked'),
221 logErrors: $('#modal_settings_cbox_logErrors').is(':checked'),
222 logSearched: $('#modal_settings_cbox_logSearched').is(':checked'),
223 queueConcurrency: parseInt($('#modal_settings_number_queueConcurrency').val()),
224 multitagSeparator: $('#modal_settings_select_multitagSeparator').val(),
225 maxBitrate: $('#modal_settings_select_maxBitrate').val(),
226 PNGcovers: $('#modal_settings_cbox_PNGcovers').is(':checked'),
227 removeAlbumVersion : $('#modal_settings_cbox_removeAlbumVersion').is(':checked'),
228 dateFormat: $('#modal_settings_select_dateFormat').val(),
229 dateFormatYear: $('#modal_settings_select_dateFormatYear').val(),
230 fallbackBitrate : $('#modal_settings_cbox_fallbackBitrate').is(':checked'),
232 title: $('#modal_tags_title').is(':checked'),
233 artist: $('#modal_tags_artist').is(':checked'),
234 album: $('#modal_tags_album').is(':checked'),
235 cover: $('#modal_tags_cover').is(':checked'),
236 trackNumber: $('#modal_tags_trackNumber').is(':checked'),
237 trackTotal: $('#modal_tags_trackTotal').is(':checked'),
238 discNumber: $('#modal_tags_discNumber').is(':checked'),
239 discTotal: $('#modal_tags_discTotal').is(':checked'),
240 albumArtist: $('#modal_tags_albumArtist').is(':checked'),
241 genre: $('#modal_tags_genre').is(':checked'),
242 year: $('#modal_tags_year').is(':checked'),
243 date: $('#modal_tags_date').is(':checked'),
244 explicit: $('#modal_tags_explicit').is(':checked'),
245 isrc: $('#modal_tags_isrc').is(':checked'),
246 length: $('#modal_tags_length').is(':checked'),
247 barcode: $('#modal_tags_barcode').is(':checked'),
248 bpm: $('#modal_tags_bpm').is(':checked'),
249 replayGain: $('#modal_tags_replayGain').is(':checked'),
250 publisher: $('#modal_tags_publisher').is(':checked'),
251 unsynchronisedLyrics: $('#modal_tags_unsynchronisedLyrics').is(':checked'),
252 copyright: $('#modal_tags_copyright').is(':checked'),
253 musicpublisher: $('#modal_tags_musicpublisher').is(':checked'),
254 composer: $('#modal_tags_composer').is(':checked'),
255 mixer: $('#modal_tags_mixer').is(':checked'),
256 author: $('#modal_tags_author').is(':checked'),
257 writer: $('#modal_tags_writer').is(':checked'),
258 engineer: $('#modal_tags_engineer').is(':checked'),
259 producer: $('#modal_tags_producer').is(':checked')
262 // Send updated settings to be saved into config file
263 socket.emit('saveSettings', settings);
264 socket.emit('getUserSettings');
267 // Reset defaults button
268 $('#modal_settings_btn_defaultSettings').click(function () {
269 if(typeof defaultDownloadLocation !== 'undefined'){
270 defaultUserSettings.downloadLocation = defaultDownloadLocation;
271 fillSettingsModal(defaultUserSettings);
275 $('#modal_login_btn_signup').click(function(){
276 if(typeof shell != 'undefined'){
277 shell.openExternal("https://www.deezer.com/register");
279 window.open("https://www.deezer.com/register");
283 $('#modal_settings_btn_logout').click(function () {
284 $('#initializing').css('display', '');
285 $('#initializing').addClass('animated fadeIn').on('webkitAnimationEnd', function () {
286 $(this).removeClass('animated fadeIn');
287 $(this).css('display', '');
289 socket.emit('logout');
290 $('#modal_login_input_username').val("");
291 $('#modal_login_input_password').val("");
292 $('#modal_login_input_autologin').prop("checked",false);
295 // Populate settings fields
296 function fillSettingsModal(settings) {
297 $('#modal_settings_input_trackNameTemplate').val(settings.trackNameTemplate);
298 $('#modal_settings_input_playlistTrackNameTemplate').val(settings.playlistTrackNameTemplate);
299 $('#modal_settings_input_albumTrackNameTemplate').val(settings.albumTrackNameTemplate);
300 $('#modal_settings_input_albumNameTemplate').val(settings.albumNameTemplate);
301 $('#modal_settings_input_coverImageTemplate').val(settings.coverImageTemplate);
302 $('#modal_settings_input_artistImageTemplate').val(settings.artistImageTemplate);
303 $('#modal_settings_cbox_createM3UFile').prop('checked', settings.createM3UFile);
304 $('#modal_settings_cbox_createArtistFolder').prop('checked', settings.createArtistFolder);
305 $('#modal_settings_cbox_createAlbumFolder').prop('checked', settings.createAlbumFolder);
306 $('#modal_settings_cbox_createCDFolder').prop('checked', settings.createCDFolder);
307 $('#modal_settings_cbox_hifi').prop('checked', settings.hifi);
308 $('#modal_settings_cbox_padtrck').prop('checked', settings.padtrck);
309 $('#modal_settings_cbox_syncedlyrics').prop('checked', settings.syncedlyrics);
310 $('#modal_settings_cbox_numplaylistbyalbum').prop('checked', settings.numplaylistbyalbum);
311 $('#modal_settings_input_downloadTracksLocation').val(settings.downloadLocation);
312 $('#modal_settings_select_artworkSize').val(settings.artworkSize).formSelect();
313 $('#modal_settings_select_chartsCounrty').val(settings.chartsCountry).formSelect();
314 $('#modal_settings_input_spotifyUser').val(settings.spotifyUser);
315 $('#modal_settings_cbox_saveArtwork').prop('checked', settings.saveArtwork);
316 $('#modal_settings_cbox_saveArtworkArtist').prop('checked', settings.saveArtworkArtist);
317 $('#modal_settings_cbox_logErrors').prop('checked', settings.logErrors);
318 $('#modal_settings_cbox_logSearched').prop('checked', settings.logSearched);
319 $('#modal_settings_number_queueConcurrency').val(settings.queueConcurrency);
320 $('#modal_settings_select_multitagSeparator').val(settings.multitagSeparator).formSelect();
321 $('#modal_settings_select_maxBitrate').val(settings.maxBitrate).formSelect();
322 $('#modal_settings_cbox_PNGcovers').prop('checked', settings.PNGcovers);
323 $('#modal_settings_cbox_removeAlbumVersion').prop('checked', settings.removeAlbumVersion);
324 $('#modal_settings_select_dateFormat').val(settings.dateFormat).formSelect();
325 $('#modal_settings_select_dateFormatYear').val(settings.dateFormatYear).formSelect();
326 $('#modal_settings_cbox_fallbackBitrate').prop('checked', settings.fallbackBitrate);
328 $('#modal_tags_title').prop('checked', settings.tags.title);
329 $('#modal_tags_artist').prop('checked', settings.tags.artist);
330 $('#modal_tags_album').prop('checked', settings.tags.album);
331 $('#modal_tags_cover').prop('checked', settings.tags.cover);
332 $('#modal_tags_trackNumber').prop('checked', settings.tags.trackNumber);
333 $('#modal_tags_trackTotal').prop('checked', settings.tags.trackTotal);
334 $('#modal_tags_discNumber').prop('checked', settings.tags.discNumber);
335 $('#modal_tags_discTotal').prop('checked', settings.tags.discTotal);
336 $('#modal_tags_albumArtist').prop('checked', settings.tags.albumArtist);
337 $('#modal_tags_genre').prop('checked', settings.tags.genre);
338 $('#modal_tags_year').prop('checked', settings.tags.year);
339 $('#modal_tags_date').prop('checked', settings.tags.date);
340 $('#modal_tags_explicit').prop('checked', settings.tags.explicit);
341 $('#modal_tags_isrc').prop('checked', settings.tags.isrc);
342 $('#modal_tags_length').prop('checked', settings.tags.length);
343 $('#modal_tags_barcode').prop('checked', settings.tags.barcode);
344 $('#modal_tags_bpm').prop('checked', settings.tags.bpm);
345 $('#modal_tags_replayGain').prop('checked', settings.tags.replayGain);
346 $('#modal_tags_publisher').prop('checked', settings.tags.publisher);
347 $('#modal_tags_unsynchronisedLyrics').prop('checked', settings.tags.unsynchronisedLyrics);
348 $('#modal_tags_copyright').prop('checked', settings.tags.copyright);
349 $('#modal_tags_musicpublisher').prop('checked', settings.tags.musicpublisher);
350 $('#modal_tags_composer').prop('checked', settings.tags.composer);
351 $('#modal_tags_mixer').prop('checked', settings.tags.mixer);
352 $('#modal_tags_author').prop('checked', settings.tags.author);
353 $('#modal_tags_writer').prop('checked', settings.tags.writer);
354 $('#modal_tags_engineer').prop('checked', settings.tags.engineer);
355 $('#modal_tags_producer').prop('checked', settings.tags.producer);
361 //#############################################MODAL_MSG##############################################\\
362 function message(title, message) {
363 $('#modal_msg_title').html(title);
364 $('#modal_msg_message').html(message);
365 $('#modal_msg').modal('open');
368 //****************************************************************************************************\\
369 //************************************************TABS************************************************\\
370 //****************************************************************************************************\\
372 //#############################################TAB_SEARCH#############################################\\
373 $('#tab_search_form_search').submit(function (ev) {
377 var searchString = $('#tab_search_form_search_input_searchString').val().trim();
378 var mode = $('#tab_search_form_search').find('input[name=searchMode]:checked').val();
380 if (searchString.length == 0) {
384 $('#tab_search_table_results').find('thead').find('tr').addClass('hide');
385 $('#tab_search_table_results_tbody_results').addClass('hide');
386 $('#tab_search_table_results_tbody_noResults').addClass('hide');
387 $('#tab_search_table_results_tbody_loadingIndicator').removeClass('hide');
389 socket.emit("search", {type: mode, text: searchString});
393 socket.on('search', function (data) {
395 $('#tab_search_table_results_tbody_loadingIndicator').addClass('hide');
397 if (data.items.length == 0) {
398 $('#tab_search_table_results_tbody_noResults').removeClass('hide');
402 if (data.type == 'track') {
403 showResults_table_track(data.items);
404 } else if (data.type == 'album') {
405 showResults_table_album(data.items);
406 } else if (data.type == 'artist') {
407 showResults_table_artist(data.items);
408 } else if (data.type == 'playlist') {
409 showResults_table_playlist(data.items);
411 $('#tab_search_table_results_tbody_results').removeClass('hide');
414 function showResults_table_track(tracks) {
415 var tableBody = $('#tab_search_table_results_tbody_results');
416 $(tableBody).html('');
417 $('#tab_search_table_results_thead_track').removeClass('hide');
418 for (var i = 0; i < tracks.length; i++) {
419 var currentResultTrack = tracks[i];
422 <td><a href="#" class="circle ${(currentResultTrack.preview ? `single-cover" preview="${currentResultTrack.preview}"><i class="material-icons preview_controls white-text">play_arrow</i>` : '">')}<img style="width:56px" class="circle" src="${(currentResultTrack.album.cover_small ? currentResultTrack.album.cover_small : "img/noCover.jpg" )}"/></a></td>
423 <td>${(currentResultTrack.explicit_lyrics ? ' <i class="material-icons valignicon tiny materialize-red-text">error_outline</i>' : '')} ${currentResultTrack.title}</td>
424 <td>${currentResultTrack.artist.name}</td>
425 <td>${currentResultTrack.album.title}</td>
426 <td>${convertDuration(currentResultTrack.duration)}</td>
428 generateDownloadLink(currentResultTrack.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
429 addPreviewControlsHover(tableBody.children('tr:last').find('.preview_controls'))
430 addPreviewControlsClick(tableBody.children('tr:last').find('.single-cover'))
434 function showResults_table_album(albums) {
435 var tableBody = $('#tab_search_table_results_tbody_results');
436 $(tableBody).html('');
437 $('#tab_search_table_results_thead_album').removeClass('hide');
438 for (var i = 0; i < albums.length; i++) {
439 var currentResultAlbum = albums[i];
442 <td><img style="width:56px" src="${(currentResultAlbum.cover_small ? currentResultAlbum.cover_small : "img/noCover.jpg")}" class="circle" /></td>
443 <td>${(currentResultAlbum.explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i>' : '')} ${currentResultAlbum.title}</td>
444 <td>${currentResultAlbum.artist.name}</td>
445 <td>${currentResultAlbum.nb_tracks}</td>
446 <td>${currentResultAlbum.record_type[0].toUpperCase() + currentResultAlbum.record_type.substring(1)}</td>
448 generateShowTracklistSelectiveButton(currentResultAlbum.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
449 generateDownloadLink(currentResultAlbum.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
451 $('.tooltipped').tooltip({delay: 100});
454 function showResults_table_artist(artists) {
455 var tableBody = $('#tab_search_table_results_tbody_results');
456 $(tableBody).html('');
457 $('#tab_search_table_results_thead_artist').removeClass('hide');
458 for (var i = 0; i < artists.length; i++) {
459 var currentResultArtist = artists[i];
462 <td><img style="width:56px" src="${(currentResultArtist.picture_small ? currentResultArtist.picture_small : "img/noCover.jpg")}" class="circle" /></td>
463 <td>${currentResultArtist.name}</td>
464 <td>${currentResultArtist.nb_album}</td>
466 generateShowTracklistButton(currentResultArtist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
467 generateDownloadLink(currentResultArtist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
471 function showResults_table_playlist(playlists) {
472 var tableBody = $('#tab_search_table_results_tbody_results');
473 $(tableBody).html('');
474 $('#tab_search_table_results_thead_playlist').removeClass('hide');
475 for (var i = 0; i < playlists.length; i++) {
476 var currentResultPlaylist = playlists[i];
479 <td><img style="width:56px" src="${(currentResultPlaylist.picture_small ? currentResultPlaylist.picture_small : "img/noCover.jpg")}" class="circle" /></td>
480 <td>${currentResultPlaylist.title}</td>
481 <td>${currentResultPlaylist.nb_tracks}</td>
483 generateShowTracklistSelectiveButton(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
484 generateDownloadLink(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
486 $('.tooltipped').tooltip({delay: 100});
489 function generateShowTracklistSelectiveButton(link) {
490 var btn_showTrackListSelective = $('<a href="#" class="waves-effect btn-flat"><i class="material-icons">list</i></a>');
491 $(btn_showTrackListSelective).click(function (ev){
493 showTrackListSelective(link);
495 return btn_showTrackListSelective;
498 function generateShowTracklistButton(link) {
499 var btn_showTrackList = $('<a href="#" class="waves-effect btn-flat"><i class="material-icons">list</i></a>');
500 $(btn_showTrackList).click(function (ev) {
504 return btn_showTrackList;
507 var trackListSelectiveModalApp = new Vue({
508 el: '#modal_trackListSelective',
516 var trackListModalApp = new Vue({
517 el: '#modal_trackList',
525 function showTrackListSelective(link) {
526 $('#modal_trackListSelective_table_trackListSelective_tbody_trackListSelective').addClass('hide');
527 $('#modal_trackListSelective_table_trackListSelective_tbody_loadingIndicator').removeClass('hide');
528 $('#modal_trackListSelective').modal('open');
529 socket.emit('getTrackList', {id: getIDFromLink(link), type: getTypeFromLink(link)});
532 $('#download_track_selection').click(function(e){
535 $("input:checkbox.trackCheckbox:checked").each(function(){
536 urls.push($(this).val());
538 if(urls.length != 0){
539 for (var ia = 0; ia < urls.length; ia++) {
540 addToQueue(urls[ia]);
543 $('#modal_trackListSelective').modal('close');
546 function showTrackList(link) {
547 $('#modal_trackList_table_trackList_tbody_trackList').addClass('hide');
548 $('#modal_trackList_table_trackList_tbody_loadingIndicator').removeClass('hide');
549 $('#modal_trackList').modal('open');
550 socket.emit("getTrackList", {id: getIDFromLink(link), type: getTypeFromLink(link)});
553 socket.on("getTrackList", function (data) {
554 //data.err -> undefined/err
555 //data.id -> passed id
556 //data.response -> API response
558 trackListSelectiveModalApp.title = "Can't get data"
562 var trackList = data.response.data, content = '';
563 var trackListSelective = data.response.data, content = '';
564 if (typeof trackList == 'undefined') {
565 alert('Well, there seems to be a problem with this part of the app. Please notify the developer.');
569 // ########################################
570 if(data.reqType == 'album' || data.reqType == 'playlist'){
571 var tableBody = $('#modal_trackListSelective_table_trackListSelective_tbody_trackListSelective');
573 var tableBody = $('#modal_trackList_table_trackList_tbody_trackList');
575 $(tableBody).html('');
576 //############################################
577 if (data.reqType == 'artist') {
578 trackListModalApp.title = 'Album List';
579 trackListModalApp.head = [
582 {title: 'Album Title'},
583 {title: 'Release Date'},
584 {title: 'Record Type'},
585 {title: 'Download Album'}
587 for (var i = 0; i < trackList.length; i++) {
591 <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i>' : '')}</td>
592 <td><a href="#" class="album_chip" data-link="${trackList[i].link}"><div class="chip"><img src="${trackList[i].cover_small}"/>${trackList[i].title}</div></a></td>
593 <td>${trackList[i].release_date}</td>
594 <td>${trackList[i].record_type[0].toUpperCase() + trackList[i].record_type.substring(1)}</td>
597 generateDownloadLink(trackList[i].link).appendTo(tableBody.children('tr:last')).wrap('<td>');
599 $('.album_chip').click(function(e){
600 showTrackListSelective($(this).data('link'), true);
602 } else if(data.reqType == 'playlist') {
603 trackListSelectiveModalApp.title = 'Playlist';
604 trackListSelectiveModalApp.head = [
605 {title: '<i class="material-icons">music_note</i>'},
609 {title: '<i class="material-icons">timer</i>'},
610 {title: '<div class="valign-wrapper"><label><input class="selectAll" type="checkbox" id="selectAll"><span></span></label></div>'}
612 $('.selectAll').prop('checked', false);
613 for (var i = 0; i < trackList.length; i++) {
616 <td><i class="material-icons ${(trackList[i].preview ? `preview_playlist_controls" preview="${trackList[i].preview}"` : 'grey-text"')}>play_arrow</i></td>
618 <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${trackList[i].title}</td>
619 <td>${trackList[i].artist.name}</td>
620 <td>${convertDuration(trackList[i].duration)}</td>
622 <div class="valign-wrapper">
624 <input class="trackCheckbox valign" type="checkbox" id="trackChk${i}" value="${trackList[i].link}"><span></span>
630 addPreviewControlsClick(tableBody.children('tr:last').find('.preview_playlist_controls'));
632 } else if(data.reqType == 'album') {
633 trackListSelectiveModalApp.title = 'Tracklist';
634 trackListSelectiveModalApp.head = [
635 {title: '<i class="material-icons">music_note</i>'},
639 {title: '<i class="material-icons">timer</i>'},
640 {title: '<div class="valign-wrapper"><label><input class="selectAll" type="checkbox" id="selectAll"><span></span></label></div>'}
642 $('.selectAll').prop('checked', false);
643 if (trackList[trackList.length-1].disk_number != 1){
648 for (var i = 0; i < trackList.length; i++) {
649 discNum = trackList[i].disk_number
650 if (discNum != baseDisc){
651 $(tableBody).append(`<tr><td colspan="4" style="opacity: 0.54;"><i class="material-icons valignicon tiny">album</i>${discNum}</td></tr>`);
656 <td><i class="material-icons ${(trackList[i].preview ? `preview_playlist_controls" preview="${trackList[i].preview}"` : 'grey-text"')}>play_arrow</i></td>
657 <td>${trackList[i].track_position}</td>
658 <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${trackList[i].title}</td>
659 <td>${trackList[i].artist.name}</td>
660 <td>${convertDuration(trackList[i].duration)}</td>
662 <div class="valign-wrapper">
664 <input class="trackCheckbox valign" type="checkbox" id="trackChk${i}" value="${trackList[i].link}"><span></span>
670 addPreviewControlsClick(tableBody.children('tr:last').find('.preview_playlist_controls'));
673 trackListModalApp.title = 'Tracklist';
674 trackListModalApp.head = [
675 {title: '<i class="material-icons">music_note</i>'},
679 {title: '<i class="material-icons">timer</i>'}
681 for (var i = 0; i < trackList.length; i++) {
684 <td><i class="material-icons ${(trackList[i].preview ? `preview_playlist_controls" preview="${trackList[i].preview}"` : 'grey-text"')}>play_arrow</i></td>
686 <td>${(trackList[i].explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${trackList[i].title}</td>
687 <td>${trackList[i].artist.name}</td>
688 <td>${convertDuration(trackList[i].duration)}</td>
691 addPreviewControlsClick(tableBody.children('tr:last').find('.preview_playlist_controls'));
694 if(data.reqType == 'album' || data.reqType == 'playlist'){
695 $('#modal_trackListSelective_table_trackListSelective_tbody_loadingIndicator').addClass('hide');
696 $('#modal_trackListSelective_table_trackListSelective_tbody_trackListSelective').removeClass('hide');
698 $('#modal_trackList_table_trackList_tbody_loadingIndicator').addClass('hide');
699 $('#modal_trackList_table_trackList_tbody_trackList').removeClass('hide');
701 //$('#modal_trackList_table_trackList_tbody_trackList').html(content);
705 //#############################################TAB_CHARTS#############################################\\
706 socket.on("getChartsCountryList", function (data) {
707 //data.countries -> Array
708 //data.countries[0].country -> String (country name)
709 //data.countries[0].picture_small/picture_medium/picture_big -> url to cover
710 for (var i = 0; i < data.countries.length; i++) {
711 $('#tab_charts_select_country').append('<option value="' + data.countries[i]['country'] + '" data-icon="' + data.countries[i]['picture_small'] + '" class="left circle">' + data.countries[i]['country'] + '</option>');
712 $('#modal_settings_select_chartsCounrty').append('<option value="' + data.countries[i]['country'] + '" data-icon="' + data.countries[i]['picture_small'] + '" class="left circle">' + data.countries[i]['country'] + '</option>');
714 $('#tab_charts_select_country').find('option[value="' + data.selected + '"]').attr("selected", true);
715 $('#modal_settings_select_chartsCounrty').find('option[value="' + data.selected + '"]').attr("selected", true);
716 $('select').formSelect();
719 socket.on("setChartsCountry", function (data) {
720 $('#tab_charts_select_country').find('option[value="' + data.selected + '"]').attr("selected", true);
721 $('#modal_settings_select_chartsCounrty').find('option[value="' + data.selected + '"]').attr("selected", true);
722 $('select').formSelect();
725 $('#tab_charts_select_country').on('change', function () {
726 var country = $(this).find('option:selected').val();
727 $('#tab_charts_table_charts_tbody_charts').addClass('hide');
728 $('#tab_charts_table_charts_tbody_loadingIndicator').removeClass('hide');
729 socket.emit("getChartsTrackListByCountry", {country: country});
732 socket.on("getChartsTrackListByCountry", function (data) {
733 //data.playlist -> Object with Playlist information
734 //data.tracks -> Array
735 //data.tracks[0] -> Object of track 0
736 $("#downloadChartPlaylist").data("id", data.playlist.id)
737 var chartsTableBody = $('#tab_charts_table_charts_tbody_charts'), currentChartTrack;
738 chartsTableBody.html('');
739 for (var i = 0; i < data.tracks.length; i++) {
740 currentChartTrack = data.tracks[i];
741 $(chartsTableBody).append(
744 <td><a href="#" class="circle ${(currentChartTrack.preview ? `single-cover" preview="${currentChartTrack.preview}"><i class="material-icons preview_controls white-text">play_arrow</i>` : '">')}<img style="width:56px" src="${(currentChartTrack.album.cover_small ? currentChartTrack.album.cover_small : "img/noCover.jpg")}" class="circle" /></a></td>
745 <td>${(currentChartTrack.explicit_lyrics ? '<i class="material-icons valignicon tiny materialize-red-text tooltipped" data-tooltip="Explicit">error_outline</i> ' : '')}${currentChartTrack.title}</td>
746 <td>${currentChartTrack.artist.name}</td>
747 <td>${currentChartTrack.album.title}</td>
748 <td>${convertDuration(currentChartTrack.duration)}</td>
750 generateDownloadLink(currentChartTrack.link).appendTo(chartsTableBody.children('tr:last')).wrap('<td>');
751 addPreviewControlsHover(chartsTableBody.children('tr:last').find('.preview_controls'))
752 addPreviewControlsClick(chartsTableBody.children('tr:last').find('.single-cover'))
754 $('#tab_charts_table_charts_tbody_loadingIndicator').addClass('hide');
755 chartsTableBody.removeClass('hide');
758 //#############################################TAB_PLAYLISTS############################################\\
759 socket.on("getMePlaylistList", function (data) {
760 var tableBody = $('#table_personal_playlists');
761 $(tableBody).html('');
762 for (var i = 0; i < data.playlists.length; i++) {
763 var currentResultPlaylist = data.playlists[i];
766 <td><img src="${currentResultPlaylist.image}" class="circle" width="56px" /></td>
767 <td>${currentResultPlaylist.title}</td>
768 <td>${currentResultPlaylist.songs}</td>
770 if (currentResultPlaylist.spotify)
771 generateShowTracklistButton(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>')
773 generateShowTracklistSelectiveButton(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>')
775 generateDownloadLink(currentResultPlaylist.link).appendTo(tableBody.children('tr:last')).wrap('<td>');
777 $('.tooltipped').tooltip({delay: 100});
780 //###############################################TAB_URL##############################################\\
781 $('#tab_url_form_url').submit(function (ev) {
784 var urls = $("#song_url").val().split(";");
785 for(var i = 0; i < urls.length; i++){
787 if (url.length == 0) {
788 message('Blank URL Field', 'You need to insert an URL to download it!');
792 if (url.indexOf('deezer.com/') < 0 && url.indexOf('open.spotify.com/') < 0 && url.indexOf('spotify:') < 0) {
793 message('Wrong URL', 'The URL seems to be wrong. Please check it and try it again.');
796 if (url.indexOf('?') > -1) {
797 url = url.substring(0, url.indexOf("?"));
799 if (url.indexOf('open.spotify.com/') >= 0 || url.indexOf('spotify:') >= 0){
800 if (url.indexOf('user') < 0 || url.indexOf('playlist') < 0){
801 message('Playlist not found', 'Deezloader for now can only download Spotify playlists.');
809 //############################################TAB_DOWNLOADS###########################################\\
810 function addToQueue(url) {
811 var type = getTypeFromLink(url), id = getIDFromLink(url);
812 if (type == 'spotifyplaylist'){
813 [user, id] = getPlayUserFromURI(url)
814 userSettings.currentSpotifyUser = user;
816 if (type == 'track') {
817 userSettings.filename = userSettings.trackNameTemplate;
818 userSettings.foldername = userSettings.albumNameTemplate;
819 } else if (type == 'playlist' || type == 'spotifyplaylist') {
820 userSettings.filename = userSettings.playlistTrackNameTemplate;
821 userSettings.foldername = userSettings.albumNameTemplate;
822 } else if (type == 'album' || type == 'artist'){
823 userSettings.filename = userSettings.albumTrackNameTemplate;
824 userSettings.foldername = userSettings.albumNameTemplate;
826 M.toast({html: '<i class="material-icons left">error</i> Wrong Type!', displayLength: 5000, classes: 'rounded'});
829 if (alreadyInQueue(id)) {
830 M.toast({html: '<i class="material-icons left">playlist_add_check</i> Already in download-queue!', displayLength: 5000, classes: 'rounded'});
833 if (id.match(/^[0-9]+$/) == null && type != 'spotifyplaylist' && parseInt(id)>0) {
834 M.toast({html: '<i class="material-icons left">error</i> Wrong ID!', displayLength: 5000, classes: 'rounded'});
837 socket.emit("download" + type, {id: id, settings: userSettings});
838 M.toast({html: '<i class="material-icons left">add</i>Added to download-queue', displayLength: 5000, classes: 'rounded'});
841 function alreadyInQueue(id) {
842 var alreadyInQueue = false;
843 $('#tab_downloads_table_downloads').find('tbody').find('tr').each(function () {
844 if ($(this).data('deezerid') == id) {
845 alreadyInQueue = true;
849 return alreadyInQueue;
852 socket.on('addToQueue', function (data) {
854 var tableBody = $('#tab_downloads_table_downloads').find('tbody');
857 `<tr id="${data.queueId}" data-deezerid="${data.id}">
858 <td class="queueTitle">${data.name}</td>
859 <td class="queueSize">${data.size}</td>
860 <td class="queueDownloaded">${data.downloaded}</td>
861 <td class="queueFailed">${data.failed}</td>
862 <td><div class="progress"><div class="indeterminate"></div></div></td>
865 var btn_remove = $('<a href="#" class="btn-flat waves-effect"><i class="material-icons">remove</i></a>');
867 $(btn_remove).click(function (ev) {
871 socket.emit("cancelDownload", {queueId: data.queueId});
875 btn_remove.appendTo(tableBody.children('tr:last')).wrap('<td class="eventBtn center">');
879 socket.on("downloadStarted", function (data) {
880 //data.queueId -> queueId of started download
882 //Switch progress type indeterminate to determinate
883 $('#' + data.queueId).find('.indeterminate').removeClass('indeterminate').addClass('determinate');
884 $('#' + data.queueId).find('.eventBtn').find('a').html('<i class="material-icons">clear</i>');
888 socket.on('updateQueue', function (data) {
890 if (data.cancelFlag) {
894 $('#' + data.queueId).find('.queueDownloaded').html(data.downloaded);
895 $('#' + data.queueId).find('.queueFailed').html(data.failed);
897 if (data.failed == 0 && ((data.downloaded + data.failed) >= data.size)) {
898 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">done</i>');
899 $('#' + data.queueId).addClass('finished');
900 M.toast({html: `<i class="material-icons left">done</i>${quoteattr(data.name)} - Completed!`, displayLength: 5000, classes: 'rounded'})
901 } else if (data.downloaded == 0 && ((data.downloaded + data.failed) >= data.size)) {
902 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">error</i>');
903 $('#' + data.queueId).addClass('error');
904 M.toast({html: `<i class="material-icons left">error</i>${quoteattr(data.name)} - Failed!`, displayLength: 5000, classes: 'rounded'})
905 } else if ((data.downloaded + data.failed) >= data.size) {
906 $('#' + data.queueId).find('.eventBtn').html('<i class="material-icons">warning</i>');
907 $('#' + data.queueId).addClass('error');
908 M.toast({html: `<i class="material-icons left">warning</i>${quoteattr(data.name)} - Completed with errors!`, displayLength: 5000, classes: 'rounded'})
912 socket.on("downloadProgress", function (data) {
913 //data.queueId -> id (string)
914 //data.percentage -> float/double, percentage
915 //updated in 1% steps
916 $('#' + data.queueId).find('.determinate').css('width', data.percentage + '%');
920 socket.on("emptyDownloadQueue", function () {
921 M.toast({html: '<i class="material-icons left">done_all</i>All downloads completed!', displayLength: 5000, classes: 'rounded'});
924 socket.on("cancelDownload", function (data) {
925 //data.queueId -> queueId of item which was canceled
926 $('#' + data.queueId).addClass('animated fadeOutRight').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
928 M.toast({html: '<i class="material-icons left">clear</i>One download removed!', displayLength: 5000, classes: 'rounded'})
932 $('#clearTracksTable').click(function (ev) {
933 $('#tab_downloads_table_downloads').find('tbody').find('.finished, .error').addClass('animated fadeOutRight').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
939 $('#cancelAllTable').click(function (ev) {
940 let listOfIDs = $('#tab_downloads_table_downloads').find('tbody').find('tr').map((x,i)=>{
941 return $(i).attr('id')
943 socket.emit('cancelAllDownloads', {queueList: listOfIDs})
946 //****************************************************************************************************\\
947 //******************************************HELPER-FUNCTIONS******************************************\\
948 //****************************************************************************************************\\
950 * Replaces special characters with HTML friendly counterparts
952 * @param preserveCR preserves the new line character
955 function quoteattr(s, preserveCR) {
956 preserveCR = preserveCR ? ' ' : '\n';
957 return ('' + s) /* Forces the conversion to string. */
958 .replace(/&/g, '&') /* This MUST be the 1st replacement. */
959 .replace(/'/g, ''') /* The 4 other predefined entities, required. */
960 .replace(/"/g, '"')
961 .replace(/</g, '<')
962 .replace(/>/g, '>')
964 You may add other replacements here for HTML only
965 (but it's not necessary).
966 Or for XML, only if the named entities are defined in its DTD.
968 .replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */
969 .replace(/[\r\n]/g, preserveCR);
973 * Given a spotify playlist URL or URI it returns the username of the owner of the playlist and the ID of the playlist
974 * @param url URL or URI
975 * @return string[] Array containing user and playlist id
977 function getPlayUserFromURI(url){
978 var spotyUser, playlistID;
979 if ((url.startsWith("http") && url.indexOf('open.spotify.com/') >= 0)){
980 if (url.indexOf('user') < 0 || url.indexOf('playlist') < 0){
981 message('Playlist not found', 'The URL seems to be wrong. Please check it and try it again.');
982 return [false,false];
984 if (url.indexOf('?') > -1) {
985 url = url.substring(0, url.indexOf("?"));
987 spotyUser = url.slice(url.indexOf("/user/")+6);
988 spotyUser = spotyUser.substring(0, spotyUser.indexOf("/"));
989 playlistID = url.slice(url.indexOf("/playlist/")+10);
990 } else if (url.startsWith("spotify:")){
991 spotyUser = url.slice(url.indexOf("user:")+5);
992 spotyUser = spotyUser.substring(0, spotyUser.indexOf(":"));
993 playlistID = url.slice(url.indexOf("playlist:")+9);
995 return [false,false];
997 return [spotyUser, playlistID]
1000 function getIDFromLink(link) {
1001 return link.substring(link.lastIndexOf("/") + 1);
1004 function getTypeFromLink(link) {
1006 if (link.indexOf('spotify') > -1){
1007 type = "spotifyplaylist";
1008 } else if (link.indexOf('track') > -1) {
1010 } else if (link.indexOf('playlist') > -1) {
1012 } else if (link.indexOf('album') > -1) {
1014 } else if (link.indexOf('artist')) {
1020 function generateDownloadLink(url) {
1021 var btn_download = $('<a href="#" class="waves-effect btn-flat"><i class="material-icons">file_download</i></a>');
1022 $(btn_download).click(function (ev) {
1023 ev.preventDefault();
1026 return btn_download;
1029 function addPreviewControlsHover(el){
1030 el.hover( function () {
1031 $(this).css({opacity: 1});
1033 if (($(this).parent().attr("playing") && preview_stopped) || !$(this).parent().attr("playing")){
1034 $(this).css({opacity: 0}, 200);
1039 function addPreviewControlsClick(el){
1040 el.click(function (e) {
1042 var icon = (this.tagName == "I" ? $(this) : $(this).children('i'))
1043 if ($(this).attr("playing")){
1044 if (preview_track.paused){
1045 preview_track.play();
1046 preview_stopped = false;
1048 $(preview_track).animate({volume: 1}, 500);
1050 preview_stopped = true;
1051 icon.text("play_arrow");
1052 $(preview_track).animate({volume: 0}, 250, "swing", ()=>{ preview_track.pause() });
1055 $("*").removeAttr("playing");
1056 $(this).attr("playing",true);
1057 $('.preview_controls').text("play_arrow");
1058 $('.preview_playlist_controls').text("play_arrow");
1059 $('.preview_controls').css({opacity:0});
1061 icon.css({opacity: 1});
1062 preview_stopped = false;
1063 $(preview_track).animate({volume: 0}, 250, "swing", ()=>{
1064 preview_track.pause();
1065 $('#preview-track_source').prop("src", $(this).attr("preview"));
1066 preview_track.load();
1072 function convertDuration(duration) {
1073 //convert from seconds only to mm:ss format
1075 mm = Math.floor(duration / 60);
1076 ss = duration - (mm * 60);
1077 //add leading zero if ss < 0
1081 return mm + ":" + ss;
1084 function sleep(milliseconds) {
1085 var start = new Date().getTime();
1086 for (var i = 0; i < 1e7; i++) {
1087 if ((new Date().getTime() - start) > milliseconds){