TheOpenUserNut Author

Thank you very much for the continued update of the script.

Please update the code to the following to allow for more control of the filename creation. It will allow for the filtering out of invalid characters and give more control over what replaces the invalid characters.

The following is a unified diff of the changes I propose.


diff --git a/Pandora_music_replay_and_download.user.js b/Pandora_music_replay_and_download.user.js
index 172b988..79ee36c 100644
--- a/Pandora_music_replay_and_download.user.js
+++ b/Pandora_music_replay_and_download.user.js
@@ -1,6 +1,6 @@
 // ==UserScript==
 // @name         Pandora music replay and download
-// @version      1.2.1
+// @version      1.2.2
 // @require https://code.jquery.com/jquery-3.2.1.min.js
 // @author       Thesunfei
 // @grant        none
@@ -10,6 +10,7 @@
 // ==/UserScript==

 // ==Revision History==
+// 1.2.2 Added additional control over filename creation
 // 1.0 Added playmode switch and now-playing title
 // 0.8 Added album name between Title and Artist for reference
 // ==/Revision History==
@@ -345,7 +346,7 @@ padding:0;
 <div class="audiotrack">
 <div class="audioposition"></div>
 </div>
-<a class="audiodownload" download="${artist+' - '+(album?(album+' - '):'')+title}.m4a"></a>
+<a class="audiodownload" download="${getFormatedSongFilename()}"></a>
 </div>
 </div>
 <audio preload class='audiocloned'>
@@ -407,8 +408,11 @@ padding:0;
       xhr.onreadystatechange = function () {
         if (this.status == 200 && this.readyState == 4) {
           audio = this.response;
+          //Get the url of the audio object
           audiourl = URL.createObjectURL(audio);
+          //Set the audio element with the url to get it
           $("audio", ele).prop("src", audiourl);
+          //Set the download link
           $("a", ele).prop("href", audiourl);
         }else if (this.status!=200){
             ele.remove();
@@ -417,6 +421,39 @@ padding:0;
       xhr.send();
     }

+    //Get the filename used for downloading based on the variables that exist
+    function getFormatedSongFilename() {
+      //What separates artist, album, and track in the filename
+      var downloadElementSeparator = " - ";
+      //Include a spot for an album, if missing, in the download filename.
+      var includeAlbumPlaceholder = true;
+
+      var filename = sanitizeString(downloadElementSeparator, artist) + downloadElementSeparator;           //Add the artist
+      if (album) {                                                                                          //See if we have an album to add
+        filename = filename + sanitizeString(downloadElementSeparator, album) + downloadElementSeparator;   //  Album object exists so add it
+      }
+      else if (includeAlbumPlaceholder == true) {                                                           //  Album object does not exist, see if we need to add an album placeholder
+        filename = filename + downloadElementSeparator;                                                     //    Add album placeholder by just adding another separator
+      }
+      filename = filename + sanitizeString(downloadElementSeparator, title) + ".m4a";                       //Add title and extension
+
+      return filename;
+    }
+
+    //make sure the string does not contain the downloadElementSeparator, nor any OS filename restrictions
+    function sanitizeString(dirtyString) {
+      //Remove any illegal characters based on the operating system.
+      dirtyString = dirtyString.replace(/[*?"|]/g, ""  );         //windows filename restrictions -> replace with space        * ? |
+      dirtyString = dirtyString.replace(/["]/g,    "''");         //windows filename restrictions -> replace with ''           "
+      dirtyString = dirtyString.replace(/[<>]/g,   "_" );         //windows filename restrictions -> replace with underscore   < >
+      dirtyString = dirtyString.replace(/[\\\/]/g, "," );         //windows filename restrictions -> replace with comma        \ /
+      dirtyString = dirtyString.replace(/[:]/g,    ";" );         //windows filename restrictions -> replace with semicolon    :
+
+      var sepRegEx = new RegExp(downloadElementSeparator, "g");   //create RegExp object to find downloadElementSeparator
+      dirtyString = dirtyString.replace(sepRegEx, "-");           //downloadElementSeparator      -> replace with dash         -
+      return dirtyString;
+    }
+
     function updateView() {
       $(".audioposition", ele).css("left", currenttime / totaltime * 100 + "%");
       $(ele).removeClass("onplaying onpaused onloading");


OK... I will post the diff here instead.
Since the first post, I have added some additional error checking and dup checking to make sure the output is as clean as possible.
I am very happy with the result of this.

diff --git a/Pandora_music_replay_and_download.user.js b/Pandora_music_replay_and_download.user.js
index 8f9864e..c5f3147 100644
--- a/Pandora_music_replay_and_download.user.js
+++ b/Pandora_music_replay_and_download.user.js
@@ -1,12 +1,18 @@
 // ==UserScript==
 // @name         Pandora music replay and download
-// @version      0.7
+// @version      0.8
 // @require https://code.jquery.com/jquery-3.2.1.min.js
 // @author       Thesunfei
 // @grant        none
+// @license      MIT; https://opensource.org/licenses/MIT
 // @include      http://*.pandora.com/*
 // @include      https://*.pandora.com/*
 // ==/UserScript==
+
+// ==Revision History==
+// 0.8 Added album name between Title and Artist for reference
+// ==/Revision History==
+
 /*jshint multistr: true */
 $(function(){
     var styleele=$("<style></style>");
@@ -203,11 +209,25 @@ to {transform:rotate(360deg)}
         $.each(audios,function(index,item){
             if (!audioURLs.contains(item.src)) {
                 audioURLs.push(item.src);
-                renderAudioHTML(item.src,$("[data-qa='mini_track_title']").text(),$("[data-qa='mini_track_artist_name']").text(),$("[data-qa='mini_track_image']")[0].src);
+                //Pull the album information
+                album = $("[data-qa='playing_album_name']");
+                //Make sure only the current album is passed on.
+                //  Only take the first in the array to avoid extras that can come in because of timing issues
+                //    The additional one is from the previous song
+                if(album.length > 1) {
+                    album = album[0];
+                }
+                album = album.text();
+                //Do not add album if it is advertisement
+                if ($("[data-qa='mini_track_title']").text() == "Advertisement")
+                {
+                    album = "";
+                }
+                renderAudioHTML(item.src,$("[data-qa='mini_track_title']").text(),album,$("[data-qa='mini_track_artist_name']").text(),$("[data-qa='mini_track_image']")[0].src);
             }
         });
     }
-    function renderAudioHTML (src,title,artist,image){
+    function renderAudioHTML (src,title,album,artist,image){
         var ele=$(`<div class='audiowrap onloading'>
 <div class='imgwrap'>
 <img class='audioimg' src='${image}'>
@@ -217,6 +237,7 @@ to {transform:rotate(360deg)}
 </div>
 <div class='audioinfo'>
 <div class='audiotitle'>${title}</div>
+<div class='audioalbum'>${album}</div>
 <div class='audioartist'>${artist}</div>
 <div class="audiofns">
 <div class="audiotrack">

@Marti,
Thanks for the correction in my post, it is much appreciated.

I am actually just going to fork the git repository and do a formal pull request as that is probably preferred.

Nut


Please update the code to the following to allow for the album names to be displayed in the info window with each song.

Line 206
Original:
renderAudioHTML(item.src,$("[data-qa='mini_track_title']").text(),$("[data-qa='mini_track_artist_name']").text(),$("[data-qa='mini_track_image']")[0].src);
Proposed:
renderAudioHTML(item.src,$("[data-qa='mini_track_title']").text(),$("[data-qa='mini_track_artist_name']").text(),$("[data-qa='mini_track_image']")[0].src,$("[data-qa='playing_album_name']").text());

Line 220
Original:
<div class='audioartist'>${artist}</div>
Proposed:
<div class='audioalbum'>${album}</div>
<div class='audioartist'>${artist}</div>