// ==UserScript==
// @name Spotify Genius Lyrics
// @description Shows lyrics from genius.com on the Spotify web player
// @description:es Mostra la letra de genius.com de las canciones en el reproductor web de Spotify
// @description:de Zeigt den Songtext von genius.com im Spotify-Webplayer an
// @description:fr Présente les paroles de chansons de genius.com sur Spotify
// @description:pl Pokazuje teksty piosenek z genius.com na Spotify
// @description:pt Mostra letras de genius.com no Spotify
// @description:it Mostra i testi delle canzoni di genius.com su Spotify
// @description:ja スクリプトは、Spotify (スポティファイ)上の genius.com から歌詞を表示します
// @namespace https://greasyfork.org/users/20068
// @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @copyright 2020, cuzi (https://github.com/cvzi)
// @supportURL https://github.com/cvzi/Spotify-Genius-Lyrics-userscript/issues
// @icon https://avatars.githubusercontent.com/u/251374?s=200&v=4
// @version 23.6.14
// @require https://greasyfork.org/scripts/406698-geniuslyrics/code/GeniusLyrics.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js
// @grant GM.xmlHttpRequest
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.registerMenuCommand
// @grant GM_openInTab
// @connect genius.com
// @match https://open.spotify.com/*
// @match https://genius.com/songs/new
// @sandbox JavaScript
// @downloadURL https://update.greasyfork.icu/scripts/377439/Spotify%20Genius%20Lyrics.user.js
// @updateURL https://update.greasyfork.icu/scripts/377439/Spotify%20Genius%20Lyrics.meta.js
// ==/UserScript==
/*
Copyright (C) 2020 cuzi (cuzi@openmail.cc)
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, see
${JSON.stringify(res, null, 2)}` } }) } function showSearchField (query) { const b = getCleanLyricsContainer() const div = b.appendChild(document.createElement('div')) div.style = 'padding:5px' div.appendChild(document.createTextNode('Search genius.com: ')) // Hide button const hideButton = div.appendChild(document.createElement('a')) hideButton.href = '#' hideButton.style = 'float: right; padding-right: 10px;' hideButton.appendChild(document.createTextNode('Hide')) hideButton.addEventListener('click', function hideButtonClick (ev) { ev.preventDefault() hideLyrics() }) const br = div.appendChild(document.createElement('br')) br.style.clear = 'right' 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 (genius.current.compoundTitle) { input.value = genius.current.compoundTitle.replace('\t', ' ') } else if (genius.current.artists && genius.current.title) { input.value = genius.current.artists + ' ' + genius.current.title } else if (genius.current.artists) { input.value = genius.current.artists } input.addEventListener('focus', function onSearchLyricsButtonFocus () { this.style.color = 'black' }) input.addEventListener('change', function onSearchLyricsButtonClick () { this.style.color = 'black' if (input.value) { startSearch(input.value, b) } }) input.addEventListener('keyup', function onSearchLyricsKeyUp (ev) { this.style.color = 'black' if (ev.code === 'Enter' || ev.code === 'NumpadEnter') { ev.preventDefault() if (input.value) { startSearch(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; font-size:14px; color:#ffff64; cursor:pointer; z-index:3000;') b.setAttribute('title', 'Load lyrics from genius.com') b.appendChild(document.createTextNode('🅖')) b.addEventListener('click', function onShowLyricsButtonClick () { genius.option.autoShow = true // Temporarily enable showing lyrics automatically on song change window.clearInterval(genius.iv.main) genius.iv.main = window.setInterval(main, 2000) b.remove() addLyrics(true) }) document.body.appendChild(b) if (b.clientWidth < 10) { b.setAttribute('style', 'position:absolute; top: 0px; right:0px; font-size:14px; background-color:#0007; color:#ffff64; cursor:pointer; z-index:3000;border:1px solid #ffff64;border-radius: 100%;padding: 0px 5px;font-size: 10px;') b.innerHTML = 'G' } } function configShowSpotifyLyrics (div) { // Input: Show lyrics from Spotify if no lyrics found on genius.com const id = 'input945455' const input = div.appendChild(document.createElement('input')) input.type = 'checkbox' input.id = id GM.getValue('show_spotify_lyrics', true).then(function (v) { input.checked = v }) const label = div.appendChild(document.createElement('label')) label.setAttribute('for', id) label.appendChild(document.createTextNode('Open lyrics from Spotify if no lyrics found on genius.com')) const onChange = function onChangeListener () { GM.setValue('show_spotify_lyrics', input.checked) } input.addEventListener('change', onChange) } function configSubmitSpotifyLyrics (div) { // Input: Submit lyrics from Spotify to genius.com const id = 'input337565' const input = div.appendChild(document.createElement('input')) input.type = 'checkbox' input.id = id input.setAttribute('title', '...in case Spotify has lyrics that genius.com does not have') GM.getValue('submit_spotify_lyrics', true).then(function (v) { input.checked = v }) const label = div.appendChild(document.createElement('label')) label.setAttribute('for', id) label.appendChild(document.createTextNode('Suggest to submit lyrics from Spotify to genius.com')) label.setAttribute('title', '...in case Spotify has lyrics that genius.com does not have') const onChange = function onChangeListener () { GM.setValue('submit_spotify_lyrics', input.checked) } input.addEventListener('change', onChange) } function configHideSpotifySuggestions (div) { // Input: Hide suggestions and hints from Spotify about new features const id = 'input875687' const input = div.appendChild(document.createElement('input')) input.type = 'checkbox' input.id = id input.setAttribute('title', 'Hide suggestions and hints from Spotify about new features') GM.getValue('hide_spotify_suggestions', true).then(function (v) { input.checked = v }) const label = div.appendChild(document.createElement('label')) label.setAttribute('for', id) label.appendChild(document.createTextNode('Hide suggestions and hints from Spotify about new features')) const onChange = function onChangeListener () { GM.setValue('hide_spotify_suggestions', input.checked) } input.addEventListener('change', onChange) } function configHideSpotifyNowPlayingView (div) { // Input: Hide "Now Playing View" const id = 'input12567826' const input = div.appendChild(document.createElement('input')) input.type = 'checkbox' input.id = id input.setAttribute('title', 'Hide Spotify\'s "Now Playing View"') GM.getValue('hide_spotify_now_playing_view', true).then(function (v) { input.checked = v }) const label = div.appendChild(document.createElement('label')) label.setAttribute('for', id) label.appendChild(document.createTextNode('Hide Spotify\'s "Now Playing View"')) const onChange = function onChangeListener () { GM.setValue('hide_spotify_now_playing_view', input.checked) } input.addEventListener('change', onChange) } function addCss () { document.head.appendChild(document.createElement('style')).innerHTML = ` .lyricsiframe { opacity:0.1; transition:opacity 2s; margin:0px; padding:0px; } .loadingspinnerholder { position:absolute; top:100px; left:100px; cursor:progress } .lyricsnavbar { background-color: rgb(80, 80, 80); background-image: linear-gradient(rgba(0, 0, 0, 0.6), rgb(18, 18, 18)); border-radius: 8px 8px 0px 0px; margin: 8px 0px 0px 0px; padding:0px 10px; } .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; } .lyricsnavbar .second-line-separator,.lyricsnavbar .second-line-separator:hover { padding:0px 10px !important; color: transparent; vertical-align: text-bottom; } .geniushits li.tracklist-row { cursor:pointer } .geniushits li.tracklist-row:hover { background-color: #fff5; border-radius: 5px; } .geniushits li .geniushiticonout { display:inline-block; } .geniushits li:hover .geniushiticonout { display:none } .geniushits li .geniushiticonover { display:none } .geniushits li:hover .geniushiticonover { display:inline-block; padding-top:5px; } .geniushiticon { width:25px; height:2em; display:inline-block; vertical-align: top; } .geniushitname { display:inline-block; position: relative; overflow:hidden } .geniushitname .tracklist-name { font-size: 16px; font-weight: 400; color:white; } .geniushitname.runningtext .tracklist-name { display: inline-block; position: relative; animation: 3s linear 1s infinite normal runtext; } .geniushitname.runningtext:hover .tracklist-name { animation: none !important; } .geniushits .second-line-separator { opacity: 0.7 } .geniushitname .geniusbadge { color: #121212; background-color: hsla(0,0%,100%,.6); border-radius: 2px; text-transform: uppercase; font-size: 9px; line-height: 10px; min-width: 16px; height: 16px; padding: 0 2px; margin: 0 3px; } @keyframes runtext { 0%, 25% { transform: translateX(0%); left: 0%; } 75%, 100% { transform: translateX(-100%); left: 100%; } } ` } function styleIframeContent () { if (genius.option.themeKey === 'genius' || genius.option.themeKey === 'geniusReact') { genius.style.enabled = true genius.style.setup = () => { genius.style.setup = null // run once; set variables to genius.styleProps if (genius.option.themeKey !== 'genius' && genius.option.themeKey !== 'geniusReact') { genius.style.enabled = false return false } return true } } else { genius.style.enabled = false genius.style.setup = null } } function main () { if (document.querySelector('.Root [data-testid="player-controls"] [data-testid="playback-progressbar"]') && document.querySelector(songTitleQuery)) { if (genius.option.autoShow) { addLyrics() } else { addLyricsButton() } } } if (document.location.hostname === 'genius.com') { // https://genius.com/songs/new fillGeniusForm() } else { window.setInterval(function removeAds () { // Remove "premium" button try { const button = document.querySelector('button[class^=Button][aria-label*=Premium]') if (button) { button.style.display = 'none' } } catch (e) { console.warn(e) } // Remove "install app" button try { const button = document.querySelector('a[href*="/download"]') if (button) { button.style.display = 'none' } } catch (e) { console.warn(e) } // Remove iframe "GET 3 MONTHS FREE" try { const iframe = document.querySelector('iframe[data-testid="inAppMessageIframe"]') if (iframe && iframe.contentDocument && iframe.contentDocument.body) { iframe.contentDocument.body.querySelectorAll('button').forEach(function (button) { if (button.parentNode.innerHTML.indexOf('Dismiss_action') !== -1) { button.click() } }) } } catch (e) { console.warn(e) } // Remove another iframe "GET 3 MONTHS FREE" try { const iframe = document.querySelector('.ReactModalPortal iframe[srcdoc*="/purchase/"]') if (iframe && iframe.contentDocument && iframe.contentDocument.body) { const dismissButtons = Array.from(iframe.contentDocument.body.querySelectorAll('button')).filter(b => b.textContent.toLowerCase().includes('dismiss')) if (dismissButtons.length) { dismissButtons[0].click() } const nonUrlButtons = Array.from(iframe.contentDocument.body.querySelectorAll('button')).filter(b => b.dataset.clickToActionAction !== 'URL') if (nonUrlButtons.length) { nonUrlButtons[0].click() } } } catch (e) { console.warn(e) } GM.getValue('hide_spotify_suggestions', true).then(function (hideSuggestions) { if (hideSuggestions) { // Remove hints and suggestions document.querySelectorAll('.encore-announcement-set button[class*="Button-"]').forEach(b => b.click()) // Check "show never again" document.querySelectorAll('#dont.show.onboarding.npv').forEach(c => (c.checked = true)) // Close bubble document.querySelectorAll('.tippy-box button[class*="Button-"]').forEach(b => b.click()) } }) GM.getValue('hide_spotify_now_playing_view', true).then(function (hideNowPlaying) { if (hideNowPlaying) { // Close "Now Playing View" // New: 2025-04 document.querySelectorAll('[data-testid="control-button-npv"][data-active="true"]').forEach(function (b) { b.click() }) // Old: 2024-10 document.querySelectorAll('#Desktop_PanelContainer_Id [data-testid="PanelHeader_CloseButton"] button[class*="Button-"]').forEach(function (b) { if (b.parentNode.previousElementSibling && b.parentNode.previousElementSibling.querySelector('button[data-testid="more-button"]')) { // Second button is the "Now Playing View" button but not in the "Queue view" b.click() } }) } }) }, 3000) genius = geniusLyrics({ GM, scriptName, scriptIssuesURL: 'https://github.com/cvzi/Spotify-Genius-Lyrics-userscript/issues', scriptIssuesTitle: 'Report problem: github.com/cvzi/Spotify-Genius-Lyrics-userscript/issues', domain: 'https://open.spotify.com', emptyURL: 'https://open.spotify.com/robots.txt', main, addCss, listSongs, showSearchField, addLyrics, hideLyrics, getCleanLyricsContainer, setFrameDimensions, initResize, onResize, config: [ configShowSpotifyLyrics, configSubmitSpotifyLyrics, configHideSpotifySuggestions, configHideSpotifyNowPlayingView ], toggleLyricsKey: { shiftKey: true, ctrlKey: false, altKey: false, key: 'L' }, onNoResults, onNewSongPlaying }) genius.option.enableStyleSubstitution = true genius.option.cacheHTMLRequest = true // 1 lyrics page consume 2XX KB [OR 25 ~ 50KB under ] genius.onThemeChanged.push(styleIframeContent) GM.registerMenuCommand(scriptName + ' - Show lyrics', () => addLyrics(true)) GM.registerMenuCommand(scriptName + ' - Options', () => genius.f.config()) GM.registerMenuCommand(scriptName + ' - Submit lyrics to Genius', () => submitLyricsFromMenu()) window.setInterval(updateAutoScroll, 1000) window.setInterval(improveLyricsPaywall, 10000) }