Last active
November 12, 2023 15:22
-
-
Save avillp/29137b5abf7be44dd46e29ad8ff39e2e to your computer and use it in GitHub Desktop.
Unportify helps you export your Google Play Music playlists.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Unportify is a script that exports your Google Play music to text. | |
Copyright (C) 2016 Arnau Villoslada | |
This program is free software; you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation; either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License along | |
with this program; if not, write to the Free Software Foundation, Inc., | |
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
Copy of License: | |
https://www.gnu.org/licenses/gpl-3.0.html | |
*/ | |
/* | |
HOW TO USE (Chrome): | |
1. Go to Google Play Music | |
2. Once loaded, right click anywhere in the page and left click on "Inspect" (or press CTRL + SHIFT + I) | |
3. Go to the "Console" tab. | |
4. There's a little dropdown, select "top". | |
5. Paste this script next to the ">" and press enter. | |
6. You can now close the console. (Click the X at the right or press CTRL + SHIFT + I). | |
7. Go to the playlist you want to export. | |
8. Press 0 to start the export process. | |
If pressing 0 does nothing, reload the page and make sure you you inject the code on "top" (step #4). | |
9. Follow on-screen instructions. | |
10. Once it's done, a file containing your playlist will download, paste it here: | |
https://labs.villoslada.me/unportify. | |
NOTE: If the number of catched songs is lower than the number of songs in your playlist, | |
you might have duplicates, if not, try lowering the scroll speed. | |
*/ | |
/* Pre-start variables */ | |
unportify_url = 'https://labs.villoslada.me/unportify'; | |
message = {}; | |
message['startup-1'] = 'Unportify v1.4.3 injected, you can now close this window.'; | |
message['startup-2'] = 'Press 0 at any time to start exporting your playlist.'; | |
message['albums'] = 'Would you like to also export the albums? \n(Not needed for Unportify importer)'; | |
message['scroll'] = 'How fast would you like me to scroll? (Leave at default if you\'re not sure).\n\ | |
Lower if it\'s not catching the entire playlist', '100'; | |
message['scroll-error'] = 'Please, only use numbers.\nDefault should work fine on a average speed Internet connection'; | |
message['scroll-startup'] = 'I\'m going to start scrolling, I\'ll warn you when I\'m done.\nPlease, don\'t switch tabs.'; | |
message['open-unportify'] = 'Would you like to import this to Spotify? \n(Opens ' + unportify_url + ')'; | |
console.log(message['startup-1']); | |
alert(message['startup-2']); | |
// Sets the starting variables | |
function startingVariables() { | |
// JSON containing playlist | |
playlist_string = '{"tracks": ['; | |
// Google Music uses a main container to scroll instead of the browser window, get it. | |
main_container = document.querySelector("#mainContainer"); | |
// Ask if the user would like to export albums | |
export_albums = confirm(message['albums']); | |
// This function keeps asking for the scroll speed until a number is given | |
function askAutoScrollSpeed() { | |
auto_scroll_speed = prompt(message['scroll'], '100'); | |
if (isNaN(parseInt(auto_scroll_speed))) { | |
alert(message['scroll-error']); | |
askAutoScrollSpeed(); | |
} | |
} | |
askAutoScrollSpeed(); | |
// Scroll margin. Used to make sure the script detects that the bottom of the page has been reached. | |
scroll_margin = 50; | |
alert(message['scroll-startup']); | |
// Set the scroll speed to the user's desired speed | |
auto_scroll_speed = parseInt(auto_scroll_speed); | |
// Go to the top of the playlist | |
main_container.scrollTop = 0; | |
// Set the starting auto scroll position | |
auto_scroll_pos = main_container.scrollTop; | |
} | |
// Gets all of the loaded songs from the playlist | |
function getSongs() { | |
var i, j, playlist_div = document.querySelectorAll('.song-row'); | |
for (i = 0, j = playlist_div.length; i < j; i++) { | |
var track = playlist_div[i]; | |
var title = encodeURI(track.querySelector('[data-col="title"] span.column-content').textContent); | |
var artist = encodeURI(track.querySelector('[data-col="artist"] span.column-content').textContent); | |
var album = encodeURI(track.querySelector('[data-col="album"] span.column-content').textContent); | |
// Clean possible characters that could mess up the JSON | |
title = title.replace(/"/g, '\\"'); | |
artist = artist.replace(/"/g, '\\"'); | |
album = album.replace(/"/g, '\\"'); | |
// Checks if the album needs to be exported | |
if (export_albums) { | |
var song = ('{"artist": "' + artist + '", "title": "' + title + '", "album": "' + album + '"},'); | |
} else { | |
var song = ('{"artist": "' + artist + '", "title": "' + title + '"},'); | |
} | |
// Only add if the song isn't already added the list | |
if (playlist_string.includes(song) != true) { | |
playlist_string += song; | |
} | |
// Will contain the last processed song | |
last = track; | |
} | |
} | |
// As Trump would say, we will do many many things, and it will be great. | |
function scrollAction() { | |
var last_song_pos = last.offsetTop; | |
var window_scroll_pos = main_container.scrollTop; | |
// Checks if we reached the last loaded song | |
if(window_scroll_pos >= last_song_pos) { | |
getSongs(); | |
} | |
var limit = main_container.scrollHeight - main_container.offsetHeight - scroll_margin; | |
// Checks if we arrived at the bottom of the playlist | |
if (window_scroll_pos >= limit) { | |
// Stops the page scroll | |
clearTimeout(scroll_delay); | |
// Get the playlist name | |
var playlist_title = document.querySelector('h2[slot="title"]').textContent; | |
// Remove theh last , | |
playlist_string = playlist_string.replace(/.$/, ''); | |
// Close JSON. | |
playlist_string += '], "playlist": "' + playlist_title + '", "exported_by": "Unportify"}'; | |
// Convert to human readable JSON | |
playlist_string = JSON.stringify(JSON.parse(playlist_string), null, 4); | |
// Exports the playlist | |
alert('Catched ' + (playlist_string.match(/"artist":/g) || []).length + ' songs.'); | |
var blob = new Blob([playlist_string], {type: 'text/plain;charset=utf-8'}); | |
saveAs(blob, playlist_title + '.txt'); | |
if (confirm(message['open-unportify'])) { | |
window.open(unportify_url, '_blank'); | |
} | |
// Awaits for the user to press the start key again | |
window.onkeypress = function(event){startExport(event)} | |
} | |
} | |
// This funcion makes the page scroll automatically. | |
function pageScroll() { | |
main_container.scrollTop = auto_scroll_pos; | |
scroll_delay = setTimeout(pageScroll,10); | |
scrollAction(); | |
auto_scroll_pos = auto_scroll_pos + auto_scroll_speed; | |
} | |
// Starts everything when the user presses the start key. | |
function startExport(event) { | |
if (event.keyCode == 48) { | |
window.onkeypress = null; | |
startingVariables(); | |
getSongs(); | |
pageScroll(); | |
} | |
} | |
window.onkeypress = function(event){startExport(event)}; | |
/* Everything below is not my code */ | |
// Used to save the playlists in a file. | |
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ | |
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/constructor/i.test(e.HTMLElement),f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},d="application/octet-stream",s=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,s)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(i){u(i)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,s){if(!s){t=p(t)}var v=this,w=t.type,m=w===d,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&a)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;i(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define([],function(){return saveAs})} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Does it still work having some issues.