NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name GoogleMusicEnhancer (GME) // @version 0.7.2 // @namespace http://www.tobsch.org/ // @author Tobias Schneider // @homepage http://www.tobsch.org/ // @licence http://www.opensource.org/licenses/mit-license.php // @description Improvements and better usability for google music // // @include https://play.google.com/music/listen* // // @require http://code.jquery.com/jquery-2.1.3.min.js // // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_info // // ==/UserScript== GM_addStyle('.lyrics-clip {background-color: #FB8521;color: #FFFFFF;font-size: 16px;height: 25px;left: -63px;margin-top: -10px;position: absolute;text-align: center;top: 47px;transform: rotate(-90deg);transform-origin: 50% 50% 0;width: 100px;}.lyrics-clip:hover {cursor: pointer;}.lyrics-panel {color: #333333;background-color: white;border: 1px solid #D1D1D1;position: fixed;right: -370px;height: 70%;width: 350px;z-index: 1000000;padding: 10px;}.lyrics-header {font-size: 16px;position: relative;min-height: 30px;width: 100%;text-align: center;border-bottom: 1px solid #D1D1D1;}.lyrics-body {color: #707070;font-size: 13px;position: relative;height: 90%;width: 100%;margin-top: 15px;padding-left: 5px;overflow-y: auto;}#lyrics-panel.clicked {right: 0;transition: 0.5s;}#lyrics-panel.un-clicked {right: -370px;transition: 0.3s;}.lyrics-not-found-text {text-align: center;}.lyrics-not-found-link {text-align: center;}.space-top {margin-top: 10px;}.update-box {z-index: 102;border: 1px solid;border-radius: 5px;font-size: 1.1em;width: 400px;height: 400px;position: absolute;top: 15%;background-color: white;}.update-body {text-align: center;}.update-header {text-align: center;}.update-header h2 {margin: 20px auto;}'); $(window).load(function () { 'use strict'; var persist = new Persist(); var build = new Build(); new Update(persist, build) .registerUpdateButtonEvent($('.menu-logo')) .check(false); new LyricContainer($('#main'), build) .registerToggler() .registerEvents(); new Lyric($('#lyrics-panel'), persist) .registerSongChangeListener('#playerSongInfo', '#player-song-title', '#player-artist'); }); function Build() { 'use strict'; return { div: function (options) { return merge($('<div></div>'), options); }, link: function (options) { return merge($('<a></a>'), options); } }; function merge($element, options) { if (typeof options === 'undefined') { return $element.clone(); } if (options.text) { $element.html(options.text); } if (options.attr) { $element.attr(options.attr); } if (options.css) { $element.css(options.css); } return $element.clone(); } } function Lyric($panel, persist) { 'use strict'; if (typeof $panel === 'undefined' || typeof persist === 'undefined') { throw 'Panel and/or persist object undefined'; } var lastSearchParameter; var $lyricsPanel = $panel; var Persist = persist; return { search: function (parameter, strategy) { if (typeof parameter === 'undefined' || typeof strategy !== 'object') { throw 'Wrong parameters or strategy object'; } findBy(strategy, parameter); return this; }, registerSongChangeListener: function (changeListenerSelector, titleSelector, artistSelector) { var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; var myObserver = new MutationObserver(mutationHandler); myObserver.observe(document.querySelector(changeListenerSelector), { childList: true }); function mutationHandler() { var searchParameter = {artist: $(artistSelector).text(), title: $(titleSelector).text()}; if (!lastSearchParameter || (lastSearchParameter && JSON.stringify(lastSearchParameter) !== JSON.stringify(searchParameter))) { try { findBy(new LyricsWiki(new Build()), searchParameter); lastSearchParameter = searchParameter; } catch (e) { console.log('GME: ' + e.message); } } } return this; } }; function findBy(strategy, parameter) { if (typeof parameter === 'undefined' || !parameter.artist || !parameter.title || typeof strategy !== 'object') { throw 'Wrong parameters or strategy object'; } $lyricsPanel.trigger('update', {artist: parameter.artist, title: parameter.title}); var persistLyric = Persist.findBy('lyric:' + parameter.artist + '-' + parameter.title); if (persistLyric && persistLyric.lyric) { $lyricsPanel.trigger('update', {lyric: persistLyric.lyric}); } else { $lyricsPanel.trigger('add-loading-overlay'); strategy.execute(parameter.artist, parameter.title, function (lyric) { Persist.persist('lyric:' + parameter.artist + '-' + parameter.title, {lyric: lyric}); $lyricsPanel.trigger('update', {lyric: lyric}); }, function ($object) { $lyricsPanel.trigger('update', {lyric: $object}); } ); } } } function LyricContainer($parent, build) { 'use strict'; var $lyricsClip, $lyricsPanel, $lyricsHeader, $lyricsBody; var $overlayLoading; if (typeof build === 'undefined') { throw 'Build object undefined'; } if (typeof $parent === 'undefined') { throw 'Please define a selector'; } var Build = build; init($parent); return { registerToggler: function () { if ($lyricsClip) { $lyricsClip.on('click', function () { $lyricsPanel.toggleClass('clicked un-clicked'); }); } return this; }, registerEvents: function () { if ($lyricsPanel) { $lyricsPanel .on('add-loading-overlay', function () { if ($overlayLoading) { $lyricsBody.append($overlayLoading); } }) .on('update', function (event, parameter) { if (parameter) { if (parameter.title && parameter.artist) { $lyricsHeader.html(parameter.artist + ' - ' + parameter.title); } if (parameter.lyric) { $lyricsBody.html(parameter.lyric); $lyricsBody.scrollTop(0); } } }); } return this; } }; function init(){ $lyricsPanel = Build.div({attr: {class: 'lyrics-panel un-clicked', id: 'lyrics-panel'}}); $lyricsClip = Build.div({attr: {class: 'lyrics-clip', id: 'lyrics-clip'}, text: 'Lyric'}); $lyricsHeader = Build.div({attr: {class: 'lyrics-header', id: 'lyrics-header'}, text: 'Lyrics Panel'}); $lyricsBody = Build.div({attr: {class: 'lyrics-body', id: 'lyrics-body'}, text: 'I can not hear a sound. Play something loud!' }); $parent.append($lyricsPanel.append($lyricsClip).append($lyricsHeader).append($lyricsBody)); $overlayLoading = $('<div id="loading-overlay" data-type="regular-loading-overlay"></div>'); } } function LyricsWiki(build) { 'use strict'; var name = 'LyricsWiki'; var baseLyricsWikiUrl = 'https://lyrics.wikia.com/api.php?action=lyrics&fmt=realjson'; var baseGoogleUrl = 'http://www.google.com/'; if (typeof build === 'undefined') { throw 'Build object undefined'; } var Build = build; return { execute: function (artist, title, success, error) { if (typeof artist === 'undefined' || typeof title === 'undefined' || typeof success !== 'function' || typeof error !== 'function') { throw 'Wrong or undefined arguments'; } var songObject; get(prepareUrl(artist, title), function (response) { songObject = JSON.parse(response.responseText); if (songObject && songObject.lyrics !== 'Not found' && songObject.url) { get(songObject.url, function (responseLyric) { success(extractLyric(responseLyric)); }); } else { error(getErrorMessage(artist, title, songObject.url)); } }); } }; function getErrorMessage(artist, title, url) { var googleSearchString = encodeURI(baseGoogleUrl + '?q=lyrics+"' + artist + ' ' + title + '"#q=lyrics+"' + artist + ' ' + title + '"'); return $('<div></div>') .append( Build.div({text: '<b>Oh No. No lyric found.<b>', attr: {class: 'lyrics-not-found-text space-top'}}) ).append( Build.div({text: 'Add one and make this experience a little bit better!', attr: {class: 'lyrics-not-found-text'}}) ).append( Build.div({text: '<b>First</b> search for the correct lyric. YippieYeah!', attr: {class: 'lyrics-not-found-text space-top'}}) ).append( Build.div({attr: {class: 'lyrics-not-found-link'}}) .append(Build.link({attr: {href: googleSearchString}, text: 'Search for \'' + artist + ' - ' + title + '\''})) ).append( Build.div({text: '<b>Then</b> add it to ' + name + '. Awesome!', attr: {class: 'lyrics-not-found-text space-top'}}) ).append( Build.div({attr: {class: 'lyrics-not-found-link'}}) .append(Build.link({attr: {href: url}, text: 'Add this great song!'})) ).append( Build.div({text: '<b>At Last</b> refresh to load the lyric you provided', attr: { class: 'lyrics-not-found-text space-top'}}) ); } function get(url, callback) { GM_xmlhttpRequest({ method: 'GET', url: url, onload: callback }); } function extractLyric(response) { var lyricWithComment = $(response.responseText).find('.lyricbox').clone().children(':not(br)').remove().end().html(); return lyricWithComment.substr(0, lyricWithComment.indexOf('<!--')); } function prepareUrl(artist, title) { return encodeURI(baseLyricsWikiUrl + '&artist=' + toTitleCase(artist) + '&song=' + toTitleCase(title)); } function toTitleCase(str) { return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1); }); } } function Persist() { 'use strict'; var prefix = 'gme:'; return { persist: function (key, value) { if (typeof key === 'undefined') { return false; } key = prefix + '' + key; return GM_setValue(key, JSON.stringify(value)); }, findBy: function (key) { if (typeof key === 'undefined') { return undefined; } key = prefix + '' + key; var storedObject = GM_getValue(key); if (storedObject) { try { return JSON.parse(storedObject); } catch (e) { return undefined; } } return undefined; }, remove: function (key) { if (typeof key === 'undefined') { return undefined; } key = prefix + '' + key; return GM_deleteValue(key); } }; } function Update(persist, build) { 'use strict'; if (typeof persist === 'undefined') { throw 'Persist object undefined'; } if (typeof build === 'undefined') { throw 'Build object undefined'; } var version = GM_info.script.version; var updateString = 'last-update'; var name = 'GoogleMusicEnhancer (GME)'; var linkToNewVersion = 'http://www.tobsch.org/downloads/GoogleMusicEnhancer.user.js'; var newVersionCheckUrl = 'http://tobsch.org/?site=GoogleMusicEnhancer'; var Persist = persist; var Build = build; return { registerUpdateButtonEvent: function ($updateButton) { if (typeof $updateButton === 'undefined' ) { throw 'Update button object undefined'; } if ($updateButton) { var that = this; $updateButton.on('click', function () { that.check(true); }); } return this; }, check: function (force) { var lastUpdateTime = Persist.findBy(updateString); if (force === true || lastUpdateTime === undefined || parseInt(lastUpdateTime) + parseInt(24 * 60 * 60 * 1000) < parseInt(String(new Date().getTime()))) { var newVersion = this.getNewVersion(); if (newVersion) { var actVersionStripped = parseInt(version.replace(/\./g, '')); var newVersionStripped = parseInt(newVersion.replace(/\./g, '')); if (!!newVersionStripped && newVersionStripped > actVersionStripped) { draw({'newVersion': newVersion}); } else { console.log('No new version of the ' + name + ' available ' + newVersion + ' <= ' + version); } Persist.persist(updateString, String(new Date().getTime())); } } return this; }, getNewVersion: function() { var response = GM_xmlhttpRequest({ method: 'GET', synchronous: 'true', url: newVersionCheckUrl }); if (response.status === 200) { return $(response.responseText).find('#projectVersion').text(); } return undefined; } }; function draw(options) { var newVersionText = 'A new version of the ' + name + ' is available.<br><br>'; newVersionText += 'Your version: ' + version + '<br>'; newVersionText += 'Brand new version: ' + options.newVersion + '<br><br><br>'; blackOut({classesToHide: '.update-box'}); var updateDiv = $('<div></div>') .css({ left: ($(document).width() / 2) - 250, 'z-index': '500' }) .attr({ id: 'update-box', class: 'update-box' }) .append($('<div><h2>New version available</h2></div>').addClass('update-header')) .append($('<div>' + newVersionText + '</div>') .addClass('update-body') .append( $('<a>' + name + ' ' + options.newVersion + '</a>') .attr({href: linkToNewVersion, title: name + ' ' + version}) ) ); $('body').append(updateDiv); } function blackOut(options) { var blackCurtain = Build.div( { css: { 'width': $(document).width(), 'height': $(document).height(), 'top': '0', 'left': '0', 'position': 'absolute', 'z-index': '499', 'background-color': 'rgb(0, 0, 0)' }, attr: { id: 'black-curtain' } }) .fadeTo('slow', 0.7) .click(function () { $(this).hide(); $(options.classesToHide).hide(); }); $('body').prepend(blackCurtain); } }