// ==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.18
// @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 none
// ==/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)
}