// ==UserScript== // @name Spotify Genius Lyrics // @description Show lyrics from genius.com on the Spotify web player // @namespace https://greasyfork.org/users/20068 // @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt // @copyright 2019, cuzi (https://github.com/cvzi) // @supportURL https://github.com/cvzi/Spotify-Genius-Lyrics-userscript/issues // @version 12 // @include https://open.spotify.com/* // @grant GM.xmlHttpRequest // @grant GM.setValue // @grant GM.getValue // @connect genius.com // @downloadURL none // ==/UserScript== /* global GM, Reflect */ const scriptName = 'SpotifyGeniusScript' const emptySpotifyURL = 'https://open.spotify.com/robots.txt' var requestCache = {} var selectionCache = {} var currentTitle = '' var currentArtists = '' var resizeLeftContainer var resizeContainer var optionCurrentSize = 30.0 var optionAutoShow = true var mainIv var themeKey var theme var annotationsEnabled = true var onMessage = [] function getHostname (url) { const a = document.createElement('a') a.href = url return a.hostname } function removeIfExists (e) { if (e && e.remove) { e.remove() } } function removeTagsKeepText (node) { while (node.firstChild) { if ('tagName' in node.firstChild && node.firstChild.tagName !== 'BR') { removeTagsKeepText(node.firstChild) } else { node.parentNode.insertBefore(node.firstChild, node) } } node.remove() } function decodeHTML (s) { return ('' + s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') } function metricPrefix (n, decimals, k) { // http://stackoverflow.com/a/18650828 if (n <= 0) { return String(n) } k = k || 1000 const dm = decimals <= 0 ? 0 : decimals || 2 const sizes = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] const i = Math.floor(Math.log(n) / Math.log(k)) return parseFloat((n / Math.pow(k, i)).toFixed(dm)) + sizes[i] } function parsePreloadedStateData (obj, parent) { // Convert genius' JSON represenation of lyrics to DOM object if ('children' in obj) { obj.children.forEach(function (child) { if (typeof (child) === 'string') { if (child) { parent.appendChild(document.createTextNode(child)) } } else { const node = parent.appendChild(document.createElement(child.tag)) if ('data' in child) { for (const key in child.data) { node.dataset[key] = child.data[key] } } if ('attributes' in child) { for (const attr in child.attributes) { let value = child.attributes[attr] if ((attr === 'href' || attr === 'src') && (!value.startsWith('http') && !value.startsWith('#'))) { value = 'https://genius.com' + value } node.setAttribute(attr, value) } } parsePreloadedStateData(child, node) } }) } return parent } function loadCache () { Promise.all([ GM.getValue('selectioncache', '{}'), GM.getValue('requestcache', '{}'), GM.getValue('optioncurrentsize', 30.0), GM.getValue('optionautoshow', true) ]).then(function (values) { selectionCache = JSON.parse(values[0]) requestCache = JSON.parse(values[1]) optionCurrentSize = values[2] optionAutoShow = values[3] /* requestCache = { "cachekey0": "121648565.5\njsondata123", ... } */ const now = (new Date()).getTime() const exp = 2 * 60 * 60 * 1000 for (const prop in requestCache) { // Delete cached values, that are older than 2 hours const time = requestCache[prop].split('\n')[0] if ((now - (new Date(time)).getTime()) > exp) { delete requestCache[prop] } } }) } function invalidateRequestCache (obj) { const cachekey = JSON.stringify(obj) if (cachekey in requestCache) { delete requestCache[cachekey] } } function request (obj) { const cachekey = JSON.stringify(obj) if (cachekey in requestCache) { return obj.load(JSON.parse(requestCache[cachekey].split('\n')[1])) } let headers = { Referer: obj.url, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Host: getHostname(obj.url), 'User-Agent': navigator.userAgent } if (obj.headers) { headers = Object.assign(headers, obj.headers) } return GM.xmlHttpRequest({ url: obj.url, method: obj.method ? obj.method : 'GET', data: obj.data, headers: headers, onerror: obj.error ? obj.error : function xmlHttpRequestGenericOnError (response) { console.log(response) }, onload: function xmlHttpRequestOnLoad (response) { const time = (new Date()).toJSON() // Chrome fix: Otherwise JSON.stringify(requestCache) omits responseText var newobj = {} for (var key in response) { newobj[key] = response[key] } newobj.responseText = response.responseText requestCache[cachekey] = time + '\n' + JSON.stringify(newobj) GM.setValue('requestcache', JSON.stringify(requestCache)) obj.load(response) } }) } function rememberLyricsSelection (title, artists, jsonHit) { const cachekey = title + '--' + artists selectionCache[cachekey] = jsonHit GM.setValue('selectioncache', JSON.stringify(selectionCache)) } function forgetLyricsSelection (title, artists) { const cachekey = title + '--' + artists if (cachekey in selectionCache) { delete selectionCache[cachekey] GM.setValue('selectioncache', JSON.stringify(selectionCache)) } } function getLyricsSelection (title, artists) { const cachekey = title + '--' + artists if (cachekey in selectionCache) { return JSON.parse(selectionCache[cachekey]) } else { return false } } function geniusSearch (query, cb) { const requestObj = { url: 'https://genius.com/api/search/song?page=1&q=' + encodeURIComponent(query), headers: { 'X-Requested-With': 'XMLHttpRequest' }, error: function geniusSearchOnError (response) { window.alert(scriptName + '\n\nError geniusSearch(' + JSON.stringify(query) + ', ' + ('name' in cb ? cb.name : 'cb') + '):\n' + response) invalidateRequestCache(requestObj) }, load: function geniusSearchOnLoad (response) { let jsonData = null try { jsonData = JSON.parse(response.responseText) } catch (e) { window.alert(scriptName + '\n\n' + e + ' in geniusSearch(' + JSON.stringify(query) + ', ' + ('name' in cb ? cb.name : 'cb') + '):\n\n' + response.responseText) invalidateRequestCache(requestObj) } if (jsonData !== null) { cb(jsonData) } } } request(requestObj) } function loadGeniusSong (song, cb) { request({ url: song.result.url, error: function loadGeniusSongOnError (response) { window.alert(scriptName + '\n\nError loadGeniusSong(' + JSON.stringify(song) + ', cb):\n' + response) }, load: function loadGeniusSongOnLoad (response) { cb(response.responseText) } }) } function loadGeniusAnnotations (song, html, annotationsEnabled, cb) { if (!annotationsEnabled) { return cb(song, html, {}) } const regex = /annotation-fragment="\d+"/g let m = html.match(regex) if (!m) { m = html.match(/href="\/\d+\//g) if (!m) { // No annotations in source -> skip loading annoations from API return cb(song, html, {}) } } m = m.map((s) => s.match(/\d+/)[0]) const ids = m.map((id) => 'ids[]=' + id) const apiurl = 'https://genius.com/api/referents/multi?text_format=html%2Cplain&' + ids.join('&') request({ url: apiurl, headers: { 'X-Requested-With': 'XMLHttpRequest' }, error: function loadGeniusAnnotationsOnError (response) { window.alert(scriptName + '\n\nError loadGeniusAnnotations(' + JSON.stringify(song) + ', cb):\n' + response) cb(song, html, {}) }, load: function loadGeniusAnnotationsOnLoad (response) { const r = JSON.parse(response.responseText).response const annotations = {} if (r.referents.forEach) { r.referents.forEach(function forEachReferent (referent) { referent.annotations.forEach(function forEachAnnotation (annotation) { if (annotation.referent_id in annotations) { annotations[annotation.referent_id].push(annotation) } else { annotations[annotation.referent_id] = [annotation] } }) }) } else { for (const refId in r.referents) { const referent = r.referents[refId] referent.annotations.forEach(function forEachAnnotation (annotation) { if (annotation.referent_id in annotations) { annotations[annotation.referent_id].push(annotation) } else { annotations[annotation.referent_id] = [annotation] } }) } } cb(song, html, annotations) } }) } const themes = { genius: { name: 'Genius (Default)', scripts: function themeGeniusScripts () { const onload = [] // Define globals var annotations1234 // Hide footer function hideFooter895 () { const f = document.querySelectorAll('.footer div') if (f.length) { removeIfExists(f[0]) removeIfExists(f[1]) } } function hideSecondaryFooter895 () { if (document.querySelector('.footer.footer--secondary')) { document.querySelector('.footer.footer--secondary').parentNode.removeChild(document.querySelector('.footer.footer--secondary')) } } onload.push(hideFooter895) onload.push(hideSecondaryFooter895) // Hide other stuff function hideStuff235 () { const grayBox = document.querySelector('.column_layout-column_span-initial_content>.dfp_unit.u-x_large_bottom_margin.dfp_unit--in_read') removeIfExists(grayBox) removeIfExists(document.querySelector('.header .header-expand_nav_menu')) } onload.push(hideStuff235) // Maked header wider onload.push(function () { document.querySelector('.header_with_cover_art-inner.column_layout .column_layout-column_span--primary').style.width = '100%' }) // Show annotations function function checkAnnotationHeight458 () { const annot = document.querySelector('.song_body.column_layout .column_layout-column_span.column_layout-column_span--secondary .column_layout-flex_column-fill_column') const arrow = annot.querySelector('.annotation_sidebar_arrow') if (arrow.offsetTop > arrow.nextElementSibling.clientHeight) { arrow.nextElementSibling.style.paddingTop = (10 + parseInt(arrow.nextElementSibling.style.paddingTop) + arrow.offsetTop - arrow.nextElementSibling.clientHeight) + 'px' } } function showAnnotation1234 (ev) { ev.preventDefault() const id = this.dataset.annotationid document.querySelectorAll('.song_body-lyrics .referent--yellow.referent--highlighted').forEach(function (e) { e.className = e.className.replace(/\breferent--yellow\b/, '').replace(/\breferent--highlighted\b/, '') }) this.className += ' referent--yellow referent--highlighted' if (typeof annotations1234 === 'undefined') { if (document.getElementById('annotationsdata1234')) { annotations1234 = JSON.parse(document.getElementById('annotationsdata1234').innerHTML) } else { annotations1234 = {} console.log('No annotation data found #annotationsdata1234') } } if (id in annotations1234) { const annotation = annotations1234[id][0] const main = document.querySelector('.song_body.column_layout .column_layout-column_span.column_layout-column_span--secondary') main.style.paddingRight = 0 main.innerHTML = '' const div0 = document.createElement('div') div0.className = 'column_layout-flex_column-fill_column' main.appendChild(div0) const arrowTop = this.offsetTop const paddingTop = window.scrollY - main.offsetTop - main.parentNode.offsetTop let html = '
' html += '\n
$author
$body
' html = html.replace(/\$body/g, decodeHTML(annotation.body.html)).replace(/\$author/g, decodeHTML(annotation.created_by.name)) div0.innerHTML = html targetBlankLinks145() // Change link target to _blank window.setTimeout(checkAnnotationHeight458, 200) // Change link target to _blank } } onload.push(function () { if (document.getElementById('annotationsdata1234')) { annotations1234 = JSON.parse(document.getElementById('annotationsdata1234').innerHTML) } }) // Make song title clickable function clickableTitle037 () { const url = document.querySelector('meta[property="og:url"]').content const h1 = document.querySelector('.header_with_cover_art-primary_info-title') h1.innerHTML = '' + h1.innerHTML + '' const div = document.querySelector('.header_with_cover_art-cover_art .cover_art') div.innerHTML = '' + div.innerHTML + '' } onload.push(clickableTitle037) // Change links to target=_blank function targetBlankLinks145 () { const as = document.querySelectorAll('body a:not([href|="#"]):not([target=_blank])') as.forEach(function (a) { a.target = '_blank' }) } onload.push(() => window.setTimeout(targetBlankLinks145, 1000)) if (!annotationsEnabled) { // Remove all annotations onload.push(function removeAnnotations135 () { document.querySelectorAll('.song_body-lyrics .referent').forEach(function (a) { while (a.firstChild) { a.parentNode.insertBefore(a.firstChild, a) } a.remove() }) // Remove right column document.querySelector('.song_body.column_layout .column_layout-column_span--secondary').remove() document.querySelector('.song_body.column_layout .column_layout-column_span--primary').style.width = '100%' }) } else { // Add click handler to annotations document.querySelectorAll('*[data-annotationid]').forEach((a) => a.addEventListener('click', showAnnotation1234)) } // Open real page if not in frame onload.push(function () { if (window.top === window) { document.location.href = document.querySelector('meta[property="og:url"]').content } }) return onload }, combine: function themeGeniusCombineGeniusResources (song, html, annotations, cb) { let headhtml = '' // Make annotations clickable const regex = /annotation-fragment="(\d+)"/g html = html.replace(regex, '$0 data-annotationid="$1"') // Change design html = html.split('
').join('
') // Remove cookie consent html = html.replace(/' // Scrollbar colors headhtml += '\n' // Add to const parts = html.split('') html = parts[0] + '\n' + headhtml + '\n' + parts.slice(1).join('') return cb(html) } }, geniusReact: { name: 'Genius React', scripts: function themeGeniusReactScripts () { const onload = [] // Define globals var annotations1234 function hideStuff () { // Hide "This is a work in progress" removeIfExists(document.getElementById('top')) // Header leaderboard/nav removeIfExists(document.querySelector('div[class^="Leaderboard"]')) removeIfExists(document.querySelector('div[class^="StickyNav"]')) // Footer except copyright hint let not = false document.querySelectorAll('div[class^="PageGriddesktop"] div[class^="PageFooterdesktop"]').forEach(function (div) { if (!not && div.innerHTML.indexOf('©') === -1) { div.remove() } else { not = true } }) document.querySelectorAll('div[class^="PageGriddesktop"]').forEach(function (div) { div.className = '' }) // Ads document.querySelectorAll('div[class^="InreadAd__Container"]').forEach(function (div) { div.parentNode.remove() }) document.querySelectorAll('div[class^="SidebarAd__Container"]').forEach(function (div) { div.parentNode.remove() }) } // Make song title clickable function clickableTitle037 () { const url = document.querySelector('meta[property="og:url"]').content const h1 = document.querySelector('h1[class^="SongHeader"]') h1.innerHTML = '' + h1.innerHTML + '' const div = document.querySelector('div[class^=SongHeader__CoverArt]') div.innerHTML = '' + div.innerHTML + '' } onload.push(clickableTitle037) // Show artwork onload.push(function showArtwork () { document.querySelectorAll('div[class^="SizedImage__Container"] noscript').forEach(function noScriptImage (noscript) { const div = noscript.parentNode div.innerHTML = noscript.innerHTML div.querySelector('img').style.left = '0px' }) }) onload.push(hideStuff) // Goto lyrics onload.push(function () { document.location.hash = '#lyrics' }) // Make expandable content buttons work function expandContent () { const button = this const content = button.parentNode.querySelector('div[class*="__Content"]') || button.parentNode.parentNode.querySelector('div[class*="__Expandable"]') content.classList.forEach(function (className) { if (className.indexOf('__Content') === -1 && className.indexOf('__Expandable') === -1) { content.classList.remove(className) } }) button.remove() } onload.push(function makeExpandablesWork () { document.querySelectorAll('div[class*="__Container"]').forEach(function (div) { const button = div.querySelector('button[class^="Button"]') if (button) { button.addEventListener('click', expandContent) } }) }) // Show annotations function function getAnnotationsContainer (a) { let c = document.getElementById('annotationcontainer958') if (!c) { c = document.body.appendChild(document.createElement('div')) c.setAttribute('id', 'annotationcontainer958') document.head.appendChild(document.createElement('style')).innerHTML = ` #annotationcontainer958 { opacity:0.0; display:none; transition:opacity 500ms; position:absolute; background:linear-gradient(to bottom, #FFF1, 5px, white); color:black; font: 100 1.125rem / 1.5 "Programme", sans-serif; max-width:95%; margin:10px; } #annotationcontainer958 .arrow { height:30px; } #annotationcontainer958 .arrow:before { content: ""; position: absolute; width: 0px; height: 0px; margin-top: 20px; inset: -1rem 0px 0px 50%; margin-left: calc(-15px); border-style: solid; border-width: 0px 25px 20px; border-color: transparent transparent rgb(170, 170, 170); } #annotationcontainer958 .annotationcontent { background-color:#E9E9E9; padding:5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; border-top-right-radius: 0px; border-top-left-radius: 0px; box-shadow: #646464 5px 5px 5px; } #annotationcontainer958 .annotationtab { display:none } #annotationcontainer958 .annotationtab.selected { display:block } #annotationcontainer958 .annotationtabbar .tabbutton { background-color:#d0cece; cursor:pointer; user-select:none; padding: 1px 7px; margin: 0px 3px; border-radius: 5px 5px 0px 0px; box-shadow: #0000004f 2px -2px 3px; float:left } #annotationcontainer958 .annotationtabbar .tabbutton.selected { background-color:#E9E9E9; } #annotationcontainer958 .annotationcontent .annotationfooter { user-select: none; } #annotationcontainer958 .annotationcontent .annotationfooter > div { float: right; width: 20%; text-align: center; } #annotationcontainer958 .annotationcontent a:link,#annotationcontainer958 .annotationcontent a:visited, #annotationcontainer958 .annotationcontent a:hover { text-decoration: underline; color: black; cursor:pointer; } ` } c.innerHTML = '' c.style.display = 'block' c.style.opacity = 1.0 const rect = a.getBoundingClientRect() c.style.top = (window.scrollY + rect.top + rect.height + 3) + 'px' const arrow = c.querySelector('.arrow') || c.appendChild(document.createElement('div')) arrow.className = 'arrow' let annotationTabBar = c.querySelector('.annotationtabbar') if (!annotationTabBar) { annotationTabBar = c.appendChild(document.createElement('div')) annotationTabBar.classList.add('annotationtabbar') } annotationTabBar.innerHTML = '' annotationTabBar.style.display = 'block' let annotationContent = c.querySelector('.annotationcontent') if (!annotationContent) { annotationContent = c.appendChild(document.createElement('div')) annotationContent.classList.add('annotationcontent') } annotationContent.style.display = 'block' annotationContent.innerHTML = '' return [annotationTabBar, annotationContent] } function switchTab (ev) { const id = this.dataset.annotid document.querySelectorAll('#annotationcontainer958 .annotationtabbar .tabbutton').forEach((e) => e.classList.remove('selected')) document.querySelectorAll('#annotationcontainer958 .annotationtab').forEach((e) => e.classList.remove('selected')) this.classList.add('selected') document.querySelector(`#annotationcontainer958 .annotationtab[id="annottab_${id}"]`).classList.add('selected') } function showAnnotation4956 (ev) { ev.preventDefault() // Annotation id const m = this.href.match(/\/(\d+)\//) if (!m) { return } const id = m[1] // Highlight document.querySelectorAll('.annotated').forEach((e) => e.classList.remove('highlighted')) this.classList.add('highlighted') // Load all annotations if (typeof annotations1234 === 'undefined') { if (document.getElementById('annotationsdata1234')) { annotations1234 = JSON.parse(document.getElementById('annotationsdata1234').innerHTML) } else { annotations1234 = {} console.log('No annotation data found #annotationsdata1234') } } if (id in annotations1234) { const [annotationTabBar, annotationContent] = getAnnotationsContainer(this) annotations1234[id].forEach(function (annotation) { // Example for multiple annoations: https://genius.com/72796/ const tabButton = annotationTabBar.appendChild(document.createElement('div')) tabButton.dataset.annotid = annotation.id tabButton.classList.add('tabbutton') tabButton.addEventListener('click', switchTab) if (annotation.state === 'verified') { tabButton.appendChild(document.createTextNode('Verified annotation')) } else { tabButton.appendChild(document.createTextNode('Genius annotation')) } let header = '
' if (annotation.authors.length === 1) { if (annotation.authors[0].name) { header += `${decodeHTML(annotation.authors[0].name)}` } else { header += `${decodeHTML(annotation.created_by.name)}` } } else { header += `${annotation.authors.length} Contributors` } header += '

' let footer = '
' footer += `
🔗 Share
` if (annotation.pyongs_count) { footer += `
⚡ ${annotation.pyongs_count}
` } if (annotation.comment_count) { footer += `
💬 ${annotation.comment_count}
` } footer += '
' if (annotation.votes_total > 0) { footer += '+' footer += annotation.votes_total footer += '👍' } else if (annotation.votes_total < 0) { footer += '-' footer += annotation.votes_total footer += '👎' } else { footer += annotation.votes_total + '👍 👎' } footer += '
' footer += '
' annotationContent.innerHTML += `
${header} ${decodeHTML(annotation.body.html)} ${footer}
` }) annotationTabBar.appendChild(document.createElement('br')).style.clear = 'left' if (annotations1234[id].length === 1) { annotationTabBar.style.display = 'none' } annotationTabBar.querySelector('.tabbutton').classList.add('selected') annotationContent.querySelector('.annotationtab').classList.add('selected') // Resize iframes and images in frame window.setTimeout(function () { const maxWidth = (document.body.clientWidth - 40) + 'px' annotationContent.querySelectorAll('iframe,img').forEach(function (e) { e.style.maxWidth = maxWidth }) targetBlankLinks145() // Change link target to _blank }, 100) } } onload.push(function () { if (document.getElementById('annotationsdata1234')) { annotations1234 = JSON.parse(document.getElementById('annotationsdata1234').innerHTML) } }) // Change links to target=_blank function targetBlankLinks145 () { const as = document.querySelectorAll('body a:not([href|="#"]):not([target=_blank])') as.forEach(function (a) { const href = a.getAttribute('href') if (!href) { return } if (!href.startsWith('#')) { a.target = '_blank' if (!href.startsWith('http')) { a.href = 'https://genius.com' + href } else if (href.startsWith('https://open.spotify.com')) { a.href = href.replace('https://open.spotify.com', 'https://genius.com') } } }) } onload.push(() => window.setTimeout(targetBlankLinks145, 1000)) if (!annotationsEnabled) { // Remove all annotations onload.push(function removeAnnotations135 () { document.querySelectorAll('div[class^="SongPage__Section"] a[class^="ReferentFragment"]').forEach(removeTagsKeepText) }) } else { // Add click handler to annotations document.querySelectorAll('div[class^="SongPage__Section"] a[class^="ReferentFragment"]').forEach(function (a) { a.classList.add('annotated') a.addEventListener('click', showAnnotation4956) }) document.body.addEventListener('click', function (e) { // Hide annotation container on click outside of it const annotationcontainer = document.getElementById('annotationcontainer958') if (annotationcontainer && !e.target.classList.contains('.annotated') && e.target.closest('.annotated') === null) { if (e.target.closest('#annotationcontainer958') === null) { annotationcontainer.style.display = 'none' annotationcontainer.style.opacity = 0.0 document.querySelectorAll('.annotated').forEach((e) => e.classList.remove('highlighted')) } } }) } // Open real page if not in frame onload.push(function () { if (window.top === window) { document.location.href = document.querySelector('meta[property="og:url"]').content } }) return onload }, combine: function themeGeniusReactCombineGeniusResources (song, html, annotations, cb) { let headhtml = '' // Make annotations clickable const regex = /annotation-fragment="(\d+)"/g html = html.replace(regex, '$0 data-annotationid="$1"') // Change design html = html.split('
').join('
') // Remove cookie consent html = html.replace(/' // Scrollbar colors headhtml += '\n' // Highlight annotated lines on hover headhtml += ` ` // Add to const parts = html.split('') html = parts[0] + '\n' + headhtml + '\n' + parts.slice(1).join('') return cb(html) } }, spotify: { name: 'Spotify', scripts: function themeSpotifyScripts () { const onload = [] // Define globals var annotations1234 // Hide cookies box function // var iv458 // function hideCookieBox458 () {if(document.querySelector(".optanon-allow-all")){document.querySelector(".optanon-allow-all").click(); clearInterval(iv458)}} // onload.push(function() { iv458 = window.setInterval(hideCookieBox458, 500) }) // Hide footer function hideFooter895 () { const f = document.querySelectorAll('.footer div'); if (f.length) { removeIfExists(f[0]); removeIfExists(f[1]) } } function hideSecondaryFooter895 () { if (document.querySelector('.footer.footer--secondary')) { document.querySelector('.footer.footer--secondary').parentNode.removeChild(document.querySelector('.footer.footer--secondary')) } } onload.push(hideFooter895) onload.push(hideSecondaryFooter895) // Hide other stuff function hideStuff235 () { const grayBox = document.querySelector('.column_layout-column_span-initial_content>.dfp_unit.u-x_large_bottom_margin.dfp_unit--in_read') removeIfExists(grayBox) removeIfExists(document.querySelector('.header .header-expand_nav_menu')) } onload.push(hideStuff235) // Show annotations function function showAnnotation1234 (ev) { ev.preventDefault() const id = this.dataset.annotationid document.querySelectorAll('.song_body-lyrics .referent--yellow.referent--highlighted').forEach(function (e) { e.className = e.className.replace(/\breferent--yellow\b/, '').replace(/\breferent--highlighted\b/, '') }) this.className += ' referent--yellow referent--highlighted' if (typeof annotations1234 === 'undefined') { if (document.getElementById('annotationsdata1234')) { annotations1234 = JSON.parse(document.getElementById('annotationsdata1234').innerHTML) } else { annotations1234 = {} console.log('No annotation data found #annotationsdata1234') } } if (id in annotations1234) { const annotation = annotations1234[id][0] const main = document.querySelector('.annotationbox') main.innerHTML = '' main.style.display = 'block' const bodyRect = document.body.getBoundingClientRect() const elemRect = this.getBoundingClientRect() const top = elemRect.top - bodyRect.top + elemRect.height main.style.top = top + 'px' main.style.left = '5px' const div0 = document.createElement('div') div0.className = 'annotationcontent' main.appendChild(div0) let html = '
$author
$body
' html = html.replace(/\$body/g, decodeHTML(annotation.body.html)).replace(/\$author/g, decodeHTML(annotation.created_by.name)) div0.innerHTML = html targetBlankLinks145() // Change link target to _blank window.setTimeout(function () { document.body.addEventListener('click', hideAnnotationOnClick1234) }, 100) // hide on click } } function hideAnnotationOnClick1234 (ev) { let target = ev.target while (target) { if (target.id === 'annotationbox') { return } if (target.className && target.className.indexOf('referent') !== -1) { const id = parseInt(target.dataset.id) return showAnnotation1234.call(target, ev, id) } target = target.parentNode } document.body.removeEventListener('click', hideAnnotationOnClick1234) const main = document.querySelector('.annotationbox') main.style.display = 'none' } onload.push(function () { if (document.getElementById('annotationsdata1234')) { annotations1234 = JSON.parse(document.getElementById('annotationsdata1234').innerHTML) } }) // Make song title clickable function clickableTitle037 () { if (!document.querySelector('.header_with_cover_art-primary_info-title')) { return } const url = document.querySelector('meta[property="og:url"]').content const h1 = document.querySelector('.header_with_cover_art-primary_info-title') h1.innerHTML = '' + h1.innerHTML + '' // Featuring and album name const h2 = document.querySelector('.header_with_cover_art-primary_info-primary_artist').parentNode document.querySelectorAll('.metadata_unit-label').forEach(function (el) { if (el.innerText.toLowerCase().indexOf('feat') !== -1) { h1.innerHTML += ' ' + el.parentNode.innerText.trim() } else if (el.innerText.toLowerCase().indexOf('album') !== -1) { h2.innerHTML = h2.innerHTML + ' \u2022 ' + el.parentNode.querySelector('a').parentNode.innerHTML.trim() } }) // Remove other meta like Producer while (document.querySelector('h3')) { document.querySelector('h3').remove() } } onload.push(clickableTitle037) // Change links to target=_blank function targetBlankLinks145 () { const as = document.querySelectorAll('body a:not([href|="#"]):not([target=_blank])') as.forEach(function (a) { a.target = '_blank' }) } onload.push(() => window.setTimeout(targetBlankLinks145, 1000)) if (!annotationsEnabled) { // Remove all annotations onload.push(function removeAnnotations135 () { document.querySelectorAll('.song_body-lyrics .referent,.song_body-lyrics a[class*=referent]').forEach(function (a) { while (a.firstChild) { a.parentNode.insertBefore(a.firstChild, a) } a.remove() }) }) } else { // Add click handler to annotations document.querySelectorAll('*[data-annotationid]').forEach((a) => a.addEventListener('click', showAnnotation1234)) } // Open real page if not in frame onload.push(function () { if (window.top === window) { document.location.href = document.querySelector('meta[property="og:url"]').content } }) return onload }, combine: function themeSpotifyXombineGeniusResources (song, html, annotations, onCombine) { let headhtml = '' if (html.indexOf('class="lyrics">') === -1) { const doc = new window.DOMParser().parseFromString(html, 'text/html') const originalUrl = doc.querySelector('meta[property="og:url"]').content if (html.indexOf('__PRELOADED_STATE__ = JSON.parse(\'') !== -1) { const jsonStr = html.split('__PRELOADED_STATE__ = JSON.parse(\'')[1].split('\');\n')[0].replace(/\\([^\\])/g, '$1').replace(/\\\\/g, '\\') const jData = JSON.parse(jsonStr) const root = parsePreloadedStateData(jData.songPage.lyricsData.body, document.createElement('div')) // Annotations root.querySelectorAll('a[data-id]').forEach(function (a) { a.dataset.annotationid = a.dataset.id a.classList.add('referent--yellow') }) const lyricshtml = root.innerHTML const h1 = doc.querySelector('div[class^=SongHeader__Column] h1') const titleNode = h1.firstChild const titleA = h1.appendChild(document.createElement('a')) titleA.href = originalUrl titleA.target = '_blank' titleA.appendChild(titleNode) h1.classList.add('mytitle') const titlehtml = '
' + h1.parentNode.outerHTML + '
' headhtml = `` // Add annotation data headhtml += '\n' return onCombine(` ${headhtml} ${titlehtml}
${lyricshtml}
`) } return onCombine(`

😱 Oops!


Sorry, these lyrics seem to use new genius page design.
They cannot be shown with the "Spotify theme" (yet)
Could you inform the author of this program about the problem and provide the following information:

Error:   Unknown genius page design
Genius:  ${originalUrl}


You can simply post the information on github:
https://github.com/cvzi/Spotify-Genius-Lyrics-userscript/issues/4
or via email: cuzi@openmail.cc

Thanks for your help!

`) } // Make annotations clickable const regex = /annotation-fragment="(\d+)"/g html = html.replace(regex, '$0 data-annotationid="$1"') // Remove cookie consent html = html.replace(/' // CSS headhtml += '\n\n' // Add to parts = html.split('') html = parts[0] + '\n' + headhtml + '\n' + parts.slice(1).join('') return onCombine(html) } } } themeKey = Object.keys(themes)[0] theme = themes[themeKey] function combineGeniusResources (song, html, annotations, cb) { if (html.indexOf('__PRELOADED_STATE__ = JSON.parse') !== -1) { if (!themeKey.endsWith('React') && (themeKey + 'React') in themes) { themeKey += 'React' theme = themes[themeKey] console.log(`Temporarily activated React theme: ${theme.name}`) } } else { if (themeKey.endsWith('React') && themeKey.substring(0, themeKey.length - 5) in themes) { themeKey = themeKey.substring(0, themeKey.length - 5) theme = themes[themeKey] console.log(`Temporarily deactivated React theme: ${theme.name}`) } } return theme.combine(song, html, annotations, cb) } function onResize () { const iframe = document.getElementById('lyricsiframe') if (iframe) { iframe.style.width = document.getElementById('lyricscontainer').clientWidth - 1 + 'px' iframe.style.height = (document.querySelector('.Root__nav-bar .navBar').clientHeight + document.querySelector('.now-playing-bar ').clientHeight - document.querySelector('.lyricsnavbar').clientHeight) + 'px' } } function initResize () { window.addEventListener('mousemove', onMouseMoveResize) window.addEventListener('mouseup', stopResize) window.removeEventListener('resize', onResize) } function onMouseMoveResize (e) { optionCurrentSize = 100 - (e.clientX / document.body.clientWidth * 100) resizeLeftContainer.style.width = (100 - optionCurrentSize) + '%' resizeContainer.style.width = optionCurrentSize + '%' } function stopResize () { window.removeEventListener('mousemove', onMouseMoveResize) window.removeEventListener('mouseup', stopResize) window.addEventListener('resize', onResize) onResize() GM.setValue('optioncurrentsize', optionCurrentSize) } function getCleanLyricsContainer () { document.querySelectorAll('.loadingspinner').forEach((spinner) => spinner.remove()) const topContainer = document.querySelector('.Root__top-container') if (!document.getElementById('lyricscontainer')) { topContainer.style.width = (100 - optionCurrentSize) + '%' topContainer.style.float = 'left' resizeContainer = document.createElement('div') resizeContainer.id = 'lyricscontainer' resizeContainer.style = 'min-height: 100%; width: ' + optionCurrentSize + '%; position: relative; z-index: 1; float:left' topContainer.parentNode.insertBefore(resizeContainer, topContainer.nextSibling) } else { resizeContainer = document.getElementById('lyricscontainer') resizeContainer.innerHTML = '' topContainer.parentNode.insertBefore(resizeContainer, topContainer.nextSibling) } resizeLeftContainer = topContainer return document.getElementById('lyricscontainer') } function hideLyrics () { document.querySelectorAll('.loadingspinner').forEach((spinner) => spinner.remove()) if (document.getElementById('lyricscontainer')) { document.getElementById('lyricscontainer').parentNode.removeChild(document.getElementById('lyricscontainer')) const topContainer = document.querySelector('.Root__top-container') topContainer.style.width = '100%' topContainer.style.removeProperty('float') } addLyricsButton() } function showLyrics (song, searchresultsLengths) { const container = getCleanLyricsContainer() if ('info' in GM && 'scriptHandler' in GM.info && GM.info.scriptHandler === 'Greasemonkey') { container.innerHTML = '

This script only works in Tampermonkey

Greasemonkey is no longer supported because of this bug greasemonkey/issues/2574 in Greasemonkey.' return } const separator = document.createElement('span') separator.setAttribute('class', 'second-line-separator') separator.setAttribute('style', 'padding:0px 3px') separator.appendChild(document.createTextNode('•')) const bar = document.createElement('div') bar.setAttribute('class', 'lyricsnavbar') bar.style.fontSize = '0.7em' bar.style.userSelect = 'none' container.appendChild(bar) // Resize button const resizeButton = document.createElement('span') resizeButton.style.fontSize = '1.8em' resizeButton.style.cursor = 'ew-resize' resizeButton.appendChild(document.createTextNode('⇹')) resizeButton.addEventListener('mousedown', initResize) bar.appendChild(resizeButton) bar.appendChild(separator.cloneNode(true)) // Hide button const hideButton = document.createElement('a') hideButton.href = '#' hideButton.appendChild(document.createTextNode('Hide')) hideButton.addEventListener('click', function hideButtonClick (ev) { ev.preventDefault() optionAutoShow = false // Temporarily disable showing lyrics automatically on song change clearInterval(mainIv) hideLyrics() }) bar.appendChild(hideButton) bar.appendChild(separator.cloneNode(true)) // Config button const configButton = document.createElement('a') configButton.href = '#' configButton.appendChild(document.createTextNode('Options')) configButton.addEventListener('click', function configButtonClick (ev) { ev.preventDefault() config() }) bar.appendChild(configButton) bar.appendChild(separator.cloneNode(true)) // Wrong lyrics const wrongLyricsButton = document.createElement('a') wrongLyricsButton.href = '#' wrongLyricsButton.appendChild(document.createTextNode('Wrong lyrics')) wrongLyricsButton.addEventListener('click', function wrongLyricsButtonClick (ev) { ev.preventDefault() document.querySelectorAll('.loadingspinner').forEach((spinner) => spinner.remove()) forgetLyricsSelection(currentTitle, currentArtists, this.dataset.hit) showSearchField(currentArtists + ' ' + currentTitle) }) bar.appendChild(wrongLyricsButton) // Back button if (searchresultsLengths) { bar.appendChild(separator.cloneNode(true)) const backbutton = document.createElement('a') backbutton.href = '#' if (searchresultsLengths === true) { backbutton.appendChild(document.createTextNode('Back to search results')) } else { backbutton.appendChild(document.createTextNode('Back to search (' + (searchresultsLengths - 1) + ' other result' + (searchresultsLengths === 2 ? '' : 's') + ')')) } backbutton.addEventListener('click', function backbuttonClick (ev) { ev.preventDefault() addLyrics(true) }) bar.appendChild(backbutton) } const iframe = document.createElement('iframe') iframe.id = 'lyricsiframe' container.appendChild(iframe) iframe.style.opacity = 0.1 iframe.src = emptySpotifyURL + '?405#html,' + encodeURIComponent('Loading...') iframe.style.width = container.clientWidth - 1 + 'px' iframe.style.height = (document.querySelector('.Root__nav-bar .navBar').clientHeight + document.querySelector('.now-playing-bar ').clientHeight - bar.clientHeight) + 'px' const spinnerHolder = document.body.appendChild(document.createElement('div')) spinnerHolder.classList.add('loadingspinnerholder') spinnerHolder.style.left = (iframe.getClientRects()[0].left + container.clientWidth / 2) + 'px' spinnerHolder.style.top = '100px' spinnerHolder.title = 'Downloading lyrics...' const spinner = spinnerHolder.appendChild(document.createElement('div')) spinner.classList.add('loadingspinner') spinner.innerHTML = '5' loadGeniusSong(song, function loadGeniusSongCb (html) { spinner.innerHTML = '4' spinnerHolder.title = 'Downloading annotations...' loadGeniusAnnotations(song, html, annotationsEnabled, function loadGeniusAnnotationsCb (song, html, annotations) { spinner.innerHTML = '3' spinnerHolder.title = 'Composing page...' combineGeniusResources(song, html, annotations, function combineGeniusResourcesCb (html) { spinner.innerHTML = '3' spinnerHolder.title = 'Loading page...' iframe.src = emptySpotifyURL + '#html:post' const iv = window.setInterval(function () { spinner.innerHTML = '2' spinnerHolder.title = 'Rendering...' iframe.contentWindow.postMessage({ iAm: scriptName, type: 'writehtml', html: html, themeKey: themeKey }, '*') }, 1500) const clear = function () { window.clearInterval(iv) window.setTimeout(function () { iframe.style.opacity = 1.0 spinnerHolder.remove() }, 1000) } addOneMessageListener('htmlwritten', function () { window.clearInterval(iv) spinner.innerHTML = '1' spinnerHolder.title = 'Calculating...' }) addOneMessageListener('pageready', clear) window.setTimeout(clear, 20000) iframe.style.position = 'fixed' }) }) }) } function listSongs (hits, container, query) { if (!container) { container = getCleanLyricsContainer() } // Back to search button const backToSearchButton = document.createElement('a') backToSearchButton.href = '#' backToSearchButton.appendChild(document.createTextNode('Back to search')) backToSearchButton.addEventListener('click', function backToSearchButtonClick (ev) { ev.preventDefault() if (query) { showSearchField(query) } else if (currentArtists) { showSearchField(currentArtists + ' ' + currentTitle) } else { showSearchField() } }) const separator = document.createElement('span') separator.setAttribute('class', 'second-line-separator') separator.setAttribute('style', 'padding:0px 3px') separator.appendChild(document.createTextNode('•')) // Hide button const hideButton = document.createElement('a') hideButton.href = '#' hideButton.appendChild(document.createTextNode('Hide')) hideButton.addEventListener('click', function hideButtonClick (ev) { ev.preventDefault() hideLyrics() }) // List search results const trackhtml = '
🅖
📄
$title
' container.innerHTML = '
    ' container.insertBefore(hideButton, container.firstChild) container.insertBefore(separator, container.firstChild) container.insertBefore(backToSearchButton, container.firstChild) const ol = container.querySelector('ol.tracklist') const searchresultsLengths = hits.length const title = currentTitle const artists = currentArtists const onclick = function onclick () { rememberLyricsSelection(title, artists, this.dataset.hit) showLyrics(JSON.parse(this.dataset.hit), searchresultsLengths) } hits.forEach(function forEachHit (hit) { const li = document.createElement('li') li.setAttribute('class', 'tracklist-row') li.setAttribute('role', 'button') li.innerHTML = trackhtml.replace(/\$title/g, hit.result.title_with_featured).replace(/\$artist/g, hit.result.primary_artist.name).replace(/\$lyrics_state/g, hit.result.lyrics_state).replace(/\$stats\.pageviews/g, 'pageviews' in hit.result.stats ? metricPrefix(hit.result.stats.pageviews, 1) : ' - ') li.dataset.hit = JSON.stringify(hit) li.addEventListener('click', onclick) ol.appendChild(li) }) } function addLyrics (force, beLessSpecific) { let songTitle = document.querySelector('a[data-testid="nowplaying-track-link"]').innerText const feat = songTitle.indexOf(' (feat') if (feat !== -1) { songTitle = songTitle.substring(0, feat).trim() } const musicIsPlaying = document.querySelector('.now-playing-bar .player-controls__buttons .control-button.control-button--circled').className.toLowerCase().indexOf('pause') !== -1 const songArtistsArr = [] document.querySelectorAll('.Root__now-playing-bar .now-playing .ellipsis-one-line a[href^="/artist/"]').forEach((e) => songArtistsArr.push(e.innerText)) let songArtists = songArtistsArr.join(' ') if (force || (!document.hidden && musicIsPlaying && (currentTitle !== songTitle || currentArtists !== songArtists))) { currentTitle = songTitle currentArtists = songArtists const firstArtist = songArtistsArr[0] const simpleTitle = songTitle = songTitle.replace(/\s*-\s*.+?$/, '') // Remove anything following the last dash if (beLessSpecific) { songArtists = firstArtist songTitle = simpleTitle } const hitFromCache = getLyricsSelection(songTitle, songArtists) if (!force && hitFromCache) { showLyrics(hitFromCache, true) } else { geniusSearch(songTitle + ' ' + songArtists, function geniusSearchCb (r) { const hits = r.response.sections[0].hits if (hits.length === 0) { hideLyrics() if (!beLessSpecific && (firstArtist !== songArtists || simpleTitle !== songTitle)) { // Try again with only the first artist or the simple title addLyrics(!!force, true) } else if (force) { showSearchField() } } else if (hits.length === 1) { showLyrics(hits[0]) } else { listSongs(hits) } }) } } } function searchByQuery (query, container) { geniusSearch(query, function geniusSearchCb (r) { const hits = r.response.sections[0].hits if (hits.length === 0) { window.alert(scriptName + '\n\nNo search results') } else { listSongs(hits, container, query) } }) } function showSearchField (query) { const b = getCleanLyricsContainer() const div = b.appendChild(document.createElement('div')) div.style = 'padding:5px' div.appendChild(document.createTextNode('Search genius.com: ')) div.appendChild(document.createElement('br')) div.style.paddingRight = '15px' const input = div.appendChild(document.createElement('input')) input.style = 'width:92%;border:0;border-radius:500px;padding:8px 5px 8px 25px;text-overflow:ellipsis' input.placeholder = 'Search genius.com...' if (query) { input.value = query } else if (currentArtists) { input.value = currentArtists } input.addEventListener('change', function onSearchLyricsButtonClick () { this.style.color = 'black' if (input.value) { searchByQuery(input.value, b) } }) input.addEventListener('keyup', function onSearchLyricsKeyUp (ev) { this.style.color = 'black' if (ev.keyCode === 13) { ev.preventDefault() if (input.value) { searchByQuery(input.value, b) } } }) input.focus() const mag = div.appendChild(document.createElement('div')) mag.style.marginTop = '-27px' mag.style.marginLeft = '3px' mag.appendChild(document.createTextNode('🔎')) } function addLyricsButton () { if (document.getElementById('showlyricsbutton')) { return } const b = document.createElement('div') b.setAttribute('id', 'showlyricsbutton') b.setAttribute('style', 'position:absolute; top: 0px; right:0px; color:#ffff64; cursor:pointer') b.setAttribute('title', 'Load lyrics from genius.com') b.appendChild(document.createTextNode('🅖')) b.addEventListener('click', function onShowLyricsButtonClick () { optionAutoShow = true // Temporarily enable showing lyrics automatically on song change mainIv = window.setInterval(main, 2000) addLyrics(true) }) document.body.appendChild(b) } function config () { loadCache() // Blur background if (document.querySelector('.Root__top-container')) { document.querySelector('.Root__top-container').style.filter = 'blur(4px)' } if (document.getElementById('lyricscontainer')) { document.getElementById('lyricscontainer').style.filter = 'blur(1px)' } const win = document.createElement('div') win.setAttribute('id', 'myconfigwin39457845') win.setAttribute('style', 'position:absolute; top: 10px; right:10px; padding:15px; background:white; border-radius:10%; border:2px solid black; color:black; z-index:10') win.appendChild(document.createElement('style')).innerHTML = '#myconfigwin39457845 div {margin:2px 0; padding:5px;border-radius: 5px;background-color: #EFEFEF;}' document.body.appendChild(win) const h1 = document.createElement('h1') win.appendChild(h1).appendChild(document.createTextNode('Options')) const a = document.createElement('a') a.href = 'https://github.com/cvzi/Spotify-Genius-Lyrics-userscript/issues' a.style = 'color:blue' win.appendChild(a).appendChild(document.createTextNode('Report problem: github.com/cvzi/Spotify-Genius-Lyrics-userscript')) // Switch: Show automatically let div = win.appendChild(document.createElement('div')) const checkAutoShow = div.appendChild(document.createElement('input')) checkAutoShow.type = 'checkbox' checkAutoShow.id = 'checkAutoShow748' checkAutoShow.checked = optionAutoShow === true const onAutoShow = function onAutoShowListener () { GM.setValue('optionautoshow', checkAutoShow.checked === true) } checkAutoShow.addEventListener('click', onAutoShow) checkAutoShow.addEventListener('change', onAutoShow) let label = div.appendChild(document.createElement('label')) label.setAttribute('for', 'checkAutoShow748') label.appendChild(document.createTextNode(' Automatically show lyrics when new song starts')) div.appendChild(document.createElement('br')) div.appendChild(document.createTextNode('(if you disable this, a small button will appear in the top right corner to show the lyrics)')) // Select: Theme div = win.appendChild(document.createElement('div')) div.appendChild(document.createTextNode('Theme: ')) const selectTheme = div.appendChild(document.createElement('select')) if (themeKey.endsWith('React')) { themeKey = themeKey.substring(0, themeKey.length - 5) } for (const key in themes) { if (key.endsWith('React')) { continue } const option = selectTheme.appendChild(document.createElement('option')) option.value = key if (themeKey === key) { option.selected = true } option.appendChild(document.createTextNode(themes[key].name)) } const onSelectTheme = function onSelectThemeListener () { if (themeKey !== selectTheme.selectedOptions[0].value) { theme = themes[selectTheme.selectedOptions[0].value] addLyrics(true) } themeKey = selectTheme.selectedOptions[0].value GM.setValue('theme', themeKey) } selectTheme.addEventListener('change', onSelectTheme) // Switch: Show annotations div = win.appendChild(document.createElement('div')) const checkAnnotationsEnabled = div.appendChild(document.createElement('input')) checkAnnotationsEnabled.type = 'checkbox' checkAnnotationsEnabled.id = 'checkAnnotationsEnabled748' checkAnnotationsEnabled.checked = annotationsEnabled === true const onAnnotationsEnabled = function onAnnotationsEnabledListener () { if (checkAnnotationsEnabled.checked !== annotationsEnabled) { annotationsEnabled = checkAnnotationsEnabled.checked === true addLyrics(true) GM.setValue('annotationsenabled', annotationsEnabled) } } checkAnnotationsEnabled.addEventListener('click', onAnnotationsEnabled) checkAnnotationsEnabled.addEventListener('change', onAnnotationsEnabled) label = div.appendChild(document.createElement('label')) label.setAttribute('for', 'checkAnnotationsEnabled748') label.appendChild(document.createTextNode(' Show annotations')) // Buttons div = win.appendChild(document.createElement('div')) const closeButton = div.appendChild(document.createElement('button')) closeButton.appendChild(document.createTextNode('Close')) closeButton.style.color = 'black' closeButton.addEventListener('click', function onCloseButtonClick () { win.parentNode.removeChild(win) // Un-blur background if (document.querySelector('.Root__top-container')) { document.querySelector('.Root__top-container').style.filter = '' } if (document.getElementById('lyricscontainer')) { document.getElementById('lyricscontainer').style.filter = '' } }) const bytes = metricPrefix(JSON.stringify(selectionCache).length + JSON.stringify(requestCache).length, 2, 1024) + 'Bytes' const clearCacheButton = div.appendChild(document.createElement('button')) clearCacheButton.appendChild(document.createTextNode('Clear cache (' + bytes + ')')) clearCacheButton.style.color = 'black' clearCacheButton.addEventListener('click', function onClearCacheButtonClick () { Promise.all([GM.setValue('selectioncache', '{}'), GM.setValue('requestcache', '{}')]).then(function () { clearCacheButton.innerHTML = 'Cleared' selectionCache = {} requestCache = {} }) }) } function addOneMessageListener (type, cb) { onMessage.push([type, cb]) } function listenToMessages () { window.addEventListener('message', function (e) { if (!onMessage || typeof e.data !== 'object' || !('iAm' in e.data) || e.data.iAm !== scriptName) { return } for (let i = 0; i < onMessage.length; i++) { if (onMessage[i][0] === e.data.type) { onMessage[i][1](e) onMessage.splice(i, 1) i-- } } }) } function addCss () { document.head.appendChild(document.createElement('style')).innerHTML = ` .lyricsiframe { opacity:0.1; transition:opacity 2s; } .loadingspinnerholder { position:absolute; top:100px; left:100px; cursor:progress } .loadingspinner { color:rgb(255, 255, 100); text-align:center; pointer-events: none; width: 2.5em; height: 2.5em; border: 0.4em solid transparent; border-color: rgb(255, 255, 100) #181818 #181818 #181818; border-radius: 50%; animation: loadingspin 2s ease infinite } @keyframes loadingspin { 25% { transform: rotate(90deg) } 50% { transform: rotate(180deg) } 75% { transform: rotate(270deg) } 100% { transform: rotate(360deg) } } .lyricsnavbar span,.lyricsnavbar a:link,.lyricsnavbar a:visited { color: rgb(179, 179, 179); text-decoration:none; transition:color 400ms; } .lyricsnavbar a:hover,.lyricsnavbar span:hover { color:white; text-decoration:none; }` } function main () { if (document.querySelector('.now-playing')) { if (optionAutoShow) { addLyrics() } else { addLyricsButton() } } } (function () { Promise.all([ GM.getValue('theme', themeKey), GM.getValue('annotationsenabled', annotationsEnabled) ]).then(function (values) { if (Object.prototype.hasOwnProperty.call(themes, values[0])) { themeKey = values[0] } else { console.log('Invalid value for theme key: GM.getValue("theme") = ' + values[0]) themeKey = Reflect.ownKeys(themes)[0] } theme = themes[themeKey] annotationsEnabled = !!values[1] if (document.location.href.startsWith(emptySpotifyURL + '#html:post')) { let received = false window.addEventListener('message', function (e) { if (received || typeof e.data !== 'object' || !('iAm' in e.data) || e.data.iAm !== scriptName || e.data.type !== 'writehtml') { return } if ('themeKey' in e.data && Object.prototype.hasOwnProperty.call(themes, e.data.themeKey)) { themeKey = e.data.themeKey theme = themes[themeKey] console.log(`Theme activated in iframe: ${theme.name}`) } received = true document.write(e.data.html) e.source.postMessage({ iAm: scriptName, type: 'htmlwritten' }, '*') window.setTimeout(function () { const onload = theme.scripts() onload.forEach((func) => func()) e.source.postMessage({ iAm: scriptName, type: 'pageready' }, '*') }, 500) }) } else if (document.location.href.startsWith(emptySpotifyURL + '?405#html,')) { document.write(decodeURIComponent(document.location.hash.split('#html,')[1])) } else { listenToMessages() loadCache() addCss() mainIv = window.setInterval(main, 2000) window.addEventListener('resize', onResize) } }) })()