// ==UserScript== // @name Youtube Genius Lyrics // @description Shows lyrics/songtexts from genius.com on Youtube next to music videos // @description:es Mostra la letra de genius.com de las canciones en Youtube junto a los vídeos musicales // @description:de Zeigt den Songtext von genius.com neben Musikvideos auf Youtube // @description:fr Présente les paroles des chansons de genius.com sur Youtube à côté des vidéos de musique. // @description:pl Pokazuje teksty piosenek z genius.com na Youtube obok teledysków // @description:pt Mostra letras de canções de genius.com no Youtube ao lado de vídeos de música // @description:it Mostra i testi delle canzoni di genius.com su Youtube accanto ai video musicali // @description:ja Youtube(ユーチューブ)では、ミュージックビデオの横に genius.com の歌詞が表示されます // @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt // @copyright 2020, cuzi (https://github.com/cvzi) // @author cuzi // @icon https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/72x72/E044.png // @supportURL https://github.com/cvzi/Youtube-Genius-Lyrics-userscript/issues // @version 10.9.4 // @require https://greasyfork.org/scripts/406698-geniuslyrics/code/GeniusLyrics.js // @grant GM.xmlHttpRequest // @grant GM.setValue // @grant GM.getValue // @grant GM.registerMenuCommand // @grant GM_addValueChangeListener // @connect genius.com // @match https://www.youtube.com/* // @match https://music.youtube.com/* // @namespace https://greasyfork.org/users/20068 // @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 . */ /* global GM, genius, geniusLyrics, top, GM_addValueChangeListener */ // eslint-disable-line no-unused-vars, no-redeclare 'use strict' let genius const SCRIPT_NAME = 'Youtube Genius Lyrics' let lyricsDisplayState = 'hidden' let disableShowLyricsButton = false // hide if the page is confirmed as non-video page function addCss () { let style = document.querySelector('style#youtube_genius_lyrics_style') if (style === null) { style = document.createElement('style') style.id = 'youtube_genius_lyrics_style' style.innerHTML = ` #myconfigwin39457845 { z-index: 2060 !important; } html { /* allow modification from external */ --ygl-container-right: var(--ytd-margin-6x, 4px); --ygl-container-default-padding: 2px 6px; --ygl-spinner-color: rgb(255, 255, 100); } #lyricscontainer { box-sizing: border-box; position: fixed; right: 0; margin: 0px; padding: 0px; background-color: white; z-index: 2001; font-size: 1.4rem; border: 0; border-radius: 0; background: var(--ytd-searchbox-background); color: var(--ytd-searchbox-text-color); border: 1px solid var(--ytd-searchbox-legacy-border-color); padding: var(--ygl-container-default-padding); /* overrided by found/loading */ line-height: 100%; width: calc(var(--ygl-container-width, 324px) - var(--ytd-margin-6x, 24px) + 7px); right: calc(var(--ygl-container-right) - 1px); } #lyricsiframe { opacity: 0.1; transition: opacity 2s; margin: 0; padding: 0; } .lyricsnavbar { font-size: 0.83em; text-align: right; padding-right: 10px; background-color: #fafafa; background: transparent; color: inherit; display: flex; user-select: none; flex-direction: row; justify-content: end; align-items: center; align-items: stretch; } .lyricsnavbar span { color: var(--yt-live-chat-primary-text-color); text-decoration: none; transition: color 400ms; } .lyricsnavbar span:hover { color: var(--yt-live-chat-toast-action-color); text-decoration: none; } body .loadingspinner { /* override .loadingspinner */ color: currentColor; font-size: 1em; line-height: 2.5em; --ygl-spinner-border-color: var(--yt-live-chat-secondary-text-color, #181818); border-color: var(--ygl-spinner-color) var(--ygl-spinner-border-color) var(--ygl-spinner-border-color) var(--ygl-spinner-border-color); } .loadingspinnerholder { z-index: 2050; background-color: inherit; cursor: progress; } .lorem br { height: 0px; line-height: 0; font-size: 0; padding: 0; margin: 0; } .lorem { --ygl-lorem-gray: var(--yt-live-chat-disabled-icon-button-color); padding: 10px 0px 0px 15px; font-size: 1.4rem; line-height: 2.2rem; letter-spacing: 0.3rem; user-select: none !important; pointer-events: none !important; width: 100%; } /* this is only supported in modern browsers */ @property --num { syntax: ''; initial-value: 0; inherits: false; } @keyframes loadingLorem { 0% { --num: 0; } 99% { --num: 8; } 100% { --num: 0; } } .lorem-scroll { --num: 0; animation: loadingLorem 4s ease infinite; margin-top: calc(-13rem - 2rem*var(--num, 0)); contain: content; width: 96%; overflow: hidden; } .lorem .white { background-color: inherit; color: white; user-select: none !important; } .lorem .gray { background-color: var(--ygl-lorem-gray); color: transparent; user-select: none !important; } #showlyricsbutton { position: fixed; /* youtube's unknown layout bug - the 'absolute' position top would offset upwards by around 80px after the page is changed */ /* note: youtube layouts are inside ytd-watch-flexy by default; therefore the 'absolute' layout for elements outside ytd-watch-flexy is not guaranteed */ z-index: 3000; right: 4px; cursor: pointer; border-radius: 50%; margin: auto; padding: 0px 1px; text-align: center; font-size: 15px; line-height: 14px; background: #ffff64; color: #000a; padding: 9px; display: flex; align-content: center; align-items: center; justify-content: center; contain: strict; opacity: 0.7; transition: opacity 50ms; /* --ytd-masthead-height --ytd-toolbar-height --ytd-watch-flexy-masthead-height */ top: var(--ytd-toolbar-height, var(--ytd-masthead-height, var(--ytd-watch-flexy-masthead-height, 56px))); } #showlyricsbutton:hover { opacity: 1.0; } /* :fullscreen shall be sufficient for modern browsers */ /* see more at https://caniuse.com/mdn-css_selectors_fullscreen */ :fullscreen #showlyricsbutton { display: none; } :-moz-full-screen #showlyricsbutton { display: none; } :-webkit-full-screen #showlyricsbutton { display: none; } #showlyricsbutton::before { color: inherit; content: 'G'; position: absolute; display: block; pointer-events: none; user-select: none; touch-action: none; } .youtube-genius-lyrics-search-container { border: 1px solid black; border-radius: 3px; --ygl-lyricscontainer-padding: 0px 10px; } span.youtube-genius-lyrics-results-line-separator, span.youtube-genius-lyrics-found-separator { padding: 0px 3px; } .youtube-genius-lyrics-results-container { border: 1px solid black; border-radius: 3px; } ol.youtube-genius-lyrics-results-tracklist { list-style: none; width: 99%; font-size: 1.15em; } li.youtube-genius-lyrics-results-li { cursor: pointer; transition: background-color 0.2s; margin: 2px; border-radius: 3px; padding: 8px 6px; } span.youtube-genius-lyrics-results-hide-btn, span.youtube-genius-lyrics-results-back-btn, span.youtube-genius-lyrics-found-hide-btn, span.youtube-genius-lyrics-found-back-btn { cursor: pointer; } li.youtube-genius-lyrics-results-li div.onhover { display: none; margin-top: -0.25em; } li.youtube-genius-lyrics-results-li div.onout { display: block; } li.youtube-genius-lyrics-results-li:hover div.onhover { display: block; } li.youtube-genius-lyrics-results-li:hover div.onout { display: none; } ol.youtube-genius-lyrics-results-tracklist { font-size: var(--ytd-link-font-size); } li.youtube-genius-lyrics-results-li { /* --tyt-tracklist-li-background: var(--yt-live-chat-slider-container-color); */ --tyt-tracklist-li-background: var(--ytd-searchbox-legacy-button-color); background-color: var(--tyt-tracklist-li-background); color: var(--ytd-searchbox-text-color); border: 1px solid var(--ytd-searchbox-legacy-border-color); list-style: none; /* color: var(--yt-live-chat-secondary-text-color); --tyt-tracklist-li-background: var(--ytd-searchbox-legacy-button-color); --tyt-tracklist-li-background: var(--yt-spec-commerce-tonal-hover); */ opacity: 0.9; } li.youtube-genius-lyrics-results-li:hover { --tyt-tracklist-li-background: var(--yt-live-chat-button-dark-background-color); /* --tyt-tracklist-li-background: var(--yt-live-chat-slider-active-color); */ border: 1px solid var(--ytd-searchbox-legacy-button-hover-border-color); opacity: 1.0; } li.youtube-genius-lyrics-results-li.lyrics-minor-result { font-size: 80%; opacity: 0.6; } li.youtube-genius-lyrics-results-li.lyrics-minor-result:hover { opacity: 0.7; } li.youtube-genius-lyrics-results-li * { pointer-events: none; } li.youtube-genius-lyrics-results-li div.onhover span { color: black; font-size: 2.0em; } li.youtube-genius-lyrics-results-li div.onout span { font-size: 1.5em; } body #lyricscontainer ol.tracklist li .onhover, body #lyricscontainer ol.tracklist li .onout { display: none; } body #lyricscontainer>ol.tracklist { max-width: 480px; min-width: 280px; width: auto; } span.youtube-genius-lyrics-found-config-btn { cursor: pointer; } span.youtube-genius-lyrics-found-wonglyrics-btn { cursor: pointer; } html { --ygl-theater-player-max-width: '--NULL--'; --ygl-theater-player-float: '--NULL--'; } html[youtube-genius-lyrics-container="found"] ytd-watch-flexy[theater], html[youtube-genius-lyrics-container="loading"] ytd-watch-flexy[theater] { --ygl-theater-player-max-width: 50%; --ygl-theater-player-float: right; } ytd-watch-flexy[theater] #movie_player .ytp-left-controls { max-width: var(--ygl-theater-player-max-width); } ytd-watch-flexy[theater] #movie_player .ytp-right-controls { float: var(--ygl-theater-player-float); } html[youtube-genius-lyrics-container="found"] ytd-watch-flexy[theater] #movie_player video[src], html[youtube-genius-lyrics-container="loading"] ytd-watch-flexy[theater] #movie_player video[src] { left: 0 !important; } html[youtube-genius-lyrics-container] #showlyricsbutton { display: none; } .youtube-genius-lyrics-search-input { -webkit-appearance: none; appearance: none; -webkit-font-smoothing: antialiased; background-color: transparent; border: none; box-shadow: none; color: inherit; font-family: Roboto, Noto, sans-serif; text-align: inherit; border: 1px solid currentColor; box-sizing: border-box; padding: 1px 0; margin: 0; outline-offset: 0px; outline: none; padding: 1px 6px; font-size: 80%; background-color: var(--yt-emoji-picker-search-background-color); border-color: currentColor; margin: 0 6px 0 6px; flex: 1; } [dark] .youtube-genius-lyrics-search-input { border-color: transparent; } .youtube-genius-lyrics-search-input:not(:focus)::placeholder { border-color: var(--yt-emoji-picker-search-placeholder-color); color: var(--yt-emoji-picker-search-placeholder-color); } .youtube-genius-lyrics-search-input.placeholder-lastfetch:not(:focus)::placeholder, .youtube-genius-lyrics-search-input.placeholder-lastfetch:focus::placeholder{ color: var(--yt-spec-text-secondary); font-weight: bold; } .youtube-genius-lyrics-search-input:focus, .youtube-genius-lyrics-search-input:active { border-color: var(--paper-checkbox-checked-color); } span.youtube-genius-lyrics-search-search-btn { cursor: pointer; vertical-align: middle; align-self: center; display: inline-flex; margin-left: 0; margin-right: 8px; } .youtube-genius-lyrics-search-search-btn:hover { color: var(--yt-live-chat-toast-action-color); } span.youtube-genius-lyrics-search-search-btn > svg { fill: currentColor; pointer-events: none; touch-action: none; user-select: none; } @keyframes inputNoResult { 0% { transform: translateX(0px); } 25% { transform: translateX(-6px); } 50% { transform: translateX(0px); } 75% { transform: translateX(6px); } 100% { transform: translateX(6px); } } .youtube-genius-lyrics-search-input.lyrics-input-noresult { border-color: red; opacity: 0.75; animation: inputNoResult 140ms; animation-iteration-count: 3; } .lyrics-searching { /* dont make animation here */ filter: blur(0.7px); opacity: 0.85; pointer-events: none; } html { --ygl-lyricsiframe-flex: '--NULL--'; --ygl-lyricscontainer-bottom: '--NULL--'; --ygl-lyricscontainer-border-radius: '--NULL--'; --ygl-lyricscontainer-padding: '--NULL--'; --ygl-lyricscontainer-display: '--NULL--'; --ygl-lyricscontainer-flex-direction: '--NULL--'; --ygl-lyricscontainer-bar-padding: '--NULL--'; } .youtube-genius-lyrics-loading-container, .youtube-genius-lyrics-found-container { --ygl-lyricsiframe-flex: 1; --ygl-lyricscontainer-bottom: 4px; /* allow override */ --ygl-lyricscontainer-border-radius: 0px 0px 8px 8px; /* allow override */ --ygl-lyricscontainer-padding: 0px 8px 8px 8px; /* allow override */ --ygl-lyricscontainer-display: flex; --ygl-lyricscontainer-flex-direction: column; --ygl-lyricscontainer-bar-padding: 4px 6px; } .lyricsnavbar { padding: var(--ygl-lyricscontainer-bar-padding); } body #lyricscontainer { display: var(--ygl-lyricscontainer-display); flex-direction: var(--ygl-lyricscontainer-flex-direction); bottom: var(--ygl-lyricscontainer-bottom); border-radius: var(--ygl-lyricscontainer-border-radius); padding: var(--ygl-lyricscontainer-padding); } #lyricsiframe { flex: var(--ygl-lyricsiframe-flex); } #lyricscontainer.youtube-genius-lyrics-loading-container #lyricsiframe { position: absolute; width: 80%; height: 80%; transform: translate(10%, 10%); } #lyricscontainer.youtube-genius-lyrics-loading-container .loadingspinnerholder { position: relative; width: auto !important; top: auto !important; display: flex; flex-direction: column; align-content: center; align-items: center; justify-content: center; overflow: hidden; contain: content; } .youtube-genius-lyrics-search-container { --ygl-lyricscontainer-display: flex; --ygl-lyricscontainer-flex-direction: row; align-items: center; } .youtube-genius-lyrics-results-container { --ygl-lyricscontainer-padding: 2px 6px 6px 6px; } .youtube-genius-lyrics-search-hide-btn, .youtube-genius-lyrics-results-back-btn, .youtube-genius-lyrics-results-hide-btn, .youtube-genius-lyrics-results-config-btn, .youtube-genius-lyrics-search-config-btn { border: 1px solid var(--ytd-searchbox-background); margin: 3px; font-size: 0.83em; color: var(--yt-live-chat-primary-text-color); text-decoration: none; transition: color 400ms; cursor: pointer; } .youtube-genius-lyrics-search-hide-btn:hover, .youtube-genius-lyrics-results-back-btn:hover, .youtube-genius-lyrics-results-hide-btn:hover, .youtube-genius-lyrics-results-config-btn:hover, .youtube-genius-lyrics-search-config-btn:hover { color: var(--yt-live-chat-toast-action-color); text-decoration: none; } .youtube-genius-lyrics-results-line-separator { font-size: 0.83em; color: var(--yt-live-chat-primary-text-color); } .youtube-genius-lyrics-search-label { font-family: cursive; padding: 0px 6px; color: var(--yt-live-chat-primary-text-color); font-size: 80%; } [dark] .youtube-genius-lyrics-search-label { color: var(--yt-live-chat-author-chip-owner-background-color); } .youtube-genius-lyrics-tracklist-info-container{ display: flex; column-gap: 6px; flex-direction: row; flex-wrap: nowrap; } .youtube-genius-lyrics-tracklist-info-primary{ flex:1; } .youtube-genius-lyrics-tracklist-info-secondary{ text-transform: uppercase; font-style: italic; } ` document.head.appendChild(style) } } function appendElements (target, elements) { if (typeof target.append === 'function') { target.append(...elements) } else { for (const element of elements) { target.appendChild(element) } } } const removeElements = (typeof window.DocumentFragment.prototype.append === 'function') ? function (elements) { document.createDocumentFragment().append(...elements) } : function (elements) { for (const element of elements) { element.remove() } } function getMastheadHeight () { // can be replaced by youtube css custom properties const masthead = document.querySelector('ytd-masthead#masthead') return masthead.getBoundingClientRect().height } function calcContainerWidthTop () { let w const upnext = document.querySelector('#secondary #secondary-inner') || document.getElementById('upnext') const playlist = document.querySelector('ytd-playlist-panel-renderer#playlist') const video = document.querySelector('ytd-watch-flexy div#primary video') if (upnext && upnext.getBoundingClientRect().left > 0) { w = window.innerWidth - upnext.getBoundingClientRect().left - 5 } else if (playlist && playlist.getBoundingClientRect().left > 0) { w = window.innerWidth - playlist.getBoundingClientRect().left - 5 } else if (video) { w = window.innerWidth - 1.02 * video.getBoundingClientRect().right } else { w = window.innerWidth * 0.45 } w = Math.min(window.innerWidth * 0.75, w) const top = getMastheadHeight() const isTheatherView = !!document.querySelector('ytd-watch-flexy[theater]') if (isTheatherView) { return { top, width: parseInt(0.3 * document.querySelector('#columns').clientWidth), isTheatherView } } else { return { top, width: w, isTheatherView } } } function setFrameDimensions (container, iframe, bar) { // eslint-disable-line no-unused-vars // if (!container || !iframe || !bar) { // console.warn('elements not found in setFrameDimensions()') // return // } // const bar = container.querySelector('.lyricsnavbar') // const width = iframe.style.width = container.clientWidth - 1 + 'px' // const height = iframe.style.height = window.innerHeight - bar.clientHeight - getMastheadHeight() + 'px' /* iframe.style.width = container.clientWidth - 1 + 'px' iframe.style.height = window.innerHeight - bar.clientHeight - getMastheadHeight() + 'px' if (genius.option.themeKey === 'spotify') { iframe.style.backgroundColor = '#181818' bar.style.backgroundColor = '#181818' } else { iframe.style.backgroundColor = '' } */ // return [width, height] } let lastResizeDT = 0 function onResize () { const tdt = Date.now() lastResizeDT = tdt window.setTimeout(function () { if (tdt === lastResizeDT) { resize() } }, 600) } function resize () { const container = document.getElementById('lyricscontainer') if (!container) { return } const { top, width } = calcContainerWidthTop() container.style.top = `${top}px` container.style.setProperty('--ygl-container-width', `${width}px`) // const iframe = document.getElementById('lyricsiframe') // if (iframe) { // const bar = container.querySelector('.lyricsnavbar') // if (bar) { // setFrameDimensions(container, iframe, bar) // } // } } function getCleanLyricsContainer () { let container = null const { top, width } = calcContainerWidthTop() if (!document.getElementById('lyricscontainer')) { container = document.createElement('div') container.id = 'lyricscontainer' document.body.appendChild(container) } else { container = document.getElementById('lyricscontainer') container.innerHTML = '' container.className = '' container.style = '' } container.style.top = `${top}px` container.style.setProperty('--ygl-container-width', `${width}px`) document.body.appendChild(container) const result = document.getElementById('lyricscontainer') if (result !== container) { console.warn(SCRIPT_NAME + ' getCleanLyricsContainer() Could not insert the element correctly') } return result } function hideLyrics () { if (document.querySelector('.loadingspinnerholder') !== null) { genius.f.cancelLoading() } const elementsToBeRemoved = [...document.querySelectorAll('.loadingspinnerholder')] const lyricscontainer = document.getElementById('lyricscontainer') if (lyricscontainer) { elementsToBeRemoved.push(lyricscontainer) } document.documentElement.removeAttribute('youtube-genius-lyrics-container') const isHiding = elementsToBeRemoved.length > 0 removeElements(elementsToBeRemoved) addLyricsButton() return isHiding } async function showLyricsButtonClicked () { removeLyricsButton() genius.option.autoShow = true // Temporarily enable showing lyrics automatically on song change addLyrics(true) } function addLyricsButton () { if (disableShowLyricsButton === true) return if (document.getElementById('showlyricsbutton')) { return } // const top = getMastheadHeight() const showlyricsbutton = document.createElement('div') showlyricsbutton.id = 'showlyricsbutton' showlyricsbutton.setAttribute('title', 'Load lyrics from genius.com') showlyricsbutton.addEventListener('click', showLyricsButtonClicked, false) document.body.appendChild(showlyricsbutton) } function removeLyricsButton () { let showlyricsbutton = document.getElementById('showlyricsbutton') if (showlyricsbutton !== null) { try { showlyricsbutton.remove() } catch (e) { // do nothing } } showlyricsbutton = null } let lastVideoId = null let lastForceVideoId = null let hitMaps = null function obtainDataCarouselLockups (ep) { if (!ep) { return null } let m = null try { m = ep.engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items[2].videoDescriptionMusicSectionRenderer.carouselLockups } catch (e) { m = null } return m } function getSimpleText (defaultMetadata) { if (!defaultMetadata) { return null } if (typeof defaultMetadata.simpleText === 'string') { return defaultMetadata.simpleText } if (defaultMetadata.runs) { const texts = defaultMetadata.runs.map(entry => entry.text) if (texts.length === 1 && typeof texts[0] === 'string') { return texts[0] } } return null } function simpleTextFixup (text) { return text.replace(/だ/g, 'だ') } function titleFix (text) { return text.replace(/\(([A-Za-z][a-z]+) ([Vv]ersion|[Vv]er\.?)\)/g, '($1)') // Genius Lyrics title use Ver. instead of Version; e.g. English Version } function getMusicTitleAndAuthor (pData) { const response = pData.response const engagementPanels = response.engagementPanels let carouselLockups = null for (const ep of engagementPanels) { const m = obtainDataCarouselLockups(ep) if (m !== null) { carouselLockups = m break } } if (carouselLockups && carouselLockups.length === 1) { let a1 = null let a2 = null try { a1 = carouselLockups[0].carouselLockupRenderer.infoRows[0].infoRowRenderer.defaultMetadata a2 = carouselLockups[0].carouselLockupRenderer.infoRows[1].infoRowRenderer.defaultMetadata } catch (e) { } a1 = getSimpleText(a1) a2 = getSimpleText(a2) if (a1 && a2 && typeof a1 === 'string' && typeof a2 === 'string') { let title = null try { title = pData.playerResponse.videoDetails.title } catch (e) { } if (title && typeof title === 'string') { a1 = titleFix(simpleTextFixup(a1)) a2 = simpleTextFixup(a2) title = simpleTextFixup(title) const newValue = `${a2} ${a1}` return { title, singer: a2, song: a1, text: newValue } } } } else if (carouselLockups && carouselLockups.length > 1) { setDisableShowLyricsButton(true) // one video with multiple musics } return null } function getYoutubeMainVideo () { let video = document.querySelector('#ytd-player #movie_player video[src]') if (video !== null) { return video } video = document.querySelector('video[src]') if (video !== null) { return video } return null } function isYoutubeVideoPlaying () { const videoPlayerContainer = document.querySelector('#ytd-player #movie_player') let video = null if (videoPlayerContainer) { const isPaused = videoPlayerContainer.classList.contains('paused-mode') if (isPaused) { return false } const isPlaying = videoPlayerContainer.classList.contains('playing-mode') if (isPlaying) { return true } video = window.HTMLElement.prototype.querySelector.call(videoPlayerContainer, 'video[src]') if (video !== null) { const paused = video.paused if (paused === true) { return false } if (paused === false) { return true } } } if (video === null) { video = document.querySelector('video[src]') } if (video !== null) { const paused = video.paused if (paused === true) { return false } if (paused === false) { return true } } } function traditionalYtdDescriptionInfo (videoTitle, videoDetails) { let songArtists let songTitle = videoTitle // song title text processing songTitle = songTitle .replace(/[\u180E\u200B-\u200D\u2060\uFEFF]+/g, '') // zero-spacing .replace(/[\s/\u0009-\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\u00B7\u237D\u2420\u2422\u2423]+/g, ' ') /* spacing */ // eslint-disable-line no-control-regex // .replace(/[\uFF01-\uFF0F\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u3000\u3001-\u303F\u2000-\u206F]+/g, ' ') // Symbols and Punctuation // Symbols and Punctuation can be part of the artist name (e.g. &TEAM, milli-billi) songTitle = songTitle.replace(/\s+/, ' ') songTitle = simpleTextFixup(songTitle) songTitle = songTitle.replace(/\(.+?\)/g, '') songTitle = songTitle.replace(/\[.+?\]/g, '') songTitle = songTitle.replace(/\b(PERFORMANCE VIDEO|official mv|karaoke mv|official|music mv|audio|music|video|karaoke)\b/gi, '') songTitle = songTitle.replace(/exclusive\s*-?/gi, '') songTitle = songTitle.replace(/-\s*-/gi, ' - ') songTitle = songTitle.trim() // Pattern: Artist - Song title songTitle = songTitle.split(/\s+[-–]\s+/) if (songTitle.length === 1) { // Pattern: Artist | Song title const m = songTitle[0].match(/(.+?)\s*\|\s*(.+)/) if (m) { songTitle = [m[1], m[2]] } } if (songTitle.length === 1) { // Pattern: Artist "Song title" const m = songTitle[0].match(/(.+?)\s*["“”'`´*]+(.+)["“”'`´*]+/) if (m) { songTitle = [m[1], m[2]] } } if (songTitle.length === 1) { // Pattern: Songtitle by Artist const m = songTitle[0].match(/(.+?)\s+by\s+(.+)/) if (m) { songTitle = [m[2], m[1]] } } if (songTitle.length === 1 && 'author' in videoDetails) { // Fallback to video author name songArtists = videoDetails.author songArtists = songArtists.replace(/\b(vevo|official|music|channel)\b/gi, '') songArtists = songArtists.replace(/-\s*topic/gi, '') } else { songArtists = songTitle.shift().trim() } const songArtistsArr = songArtists.split(',').map(s => s.trim()) songTitle = songTitle.join(' - ').trim() songTitle = genius.f.cleanUpSongTitle(songTitle) // return object result return { songTitle, songArtistsArr } } function newYtdDescriptionInfo (ytdDescriptionInfo) { const songArtistsArr = [ytdDescriptionInfo.singer] // const songArtists = ytdDescriptionInfo.singer const songTitle = ytdDescriptionInfo.song.replace('(Karaoke)', '') // return object result return { songTitle, songArtistsArr } } function getYtdAppData () { const ytdApp = document.querySelector('ytd-app') return (ytdApp.__data || 0).data || ytdApp.data || null } function isYtdAppReady () { let ytdAppData = null let videoDetails = null try { ytdAppData = getYtdAppData() if ('player' in ytdAppData && 'args' in ytdAppData.player && 'raw_player_response' in ytdAppData.player.args && 'videoDetails' in ytdAppData.player.args.raw_player_response) { videoDetails = ytdAppData.player.args.raw_player_response.videoDetails } else { videoDetails = ytdAppData.playerResponse.videoDetails } } catch (e) { } return !!videoDetails } function getVideoInfo (ytdAppData) { let videoDetails = null try { if ('player' in ytdAppData && 'args' in ytdAppData.player && 'raw_player_response' in ytdAppData.player.args && 'videoDetails' in ytdAppData.player.args.raw_player_response) { videoDetails = ytdAppData.player.args.raw_player_response.videoDetails || null } else { videoDetails = ytdAppData.playerResponse.videoDetails || null } } catch (e) { console.warn(SCRIPT_NAME + ' addLyrics() Could not find videoDetails') console.log(e) const m = document.location.href.match(/v=(\w+)&?/) videoDetails = { videoId: (m && m[1] ? m[1] : ''), keywords: [], shortDescription: '' } } return videoDetails // could be null } function getPageSongInfo (ytdAppData, videoDetails) { let isMusic = false let ytdDescriptionInfo = null let videoTitle = null let genre = null let isFamilySafe = null // obtain the music info from modern meta panel ytdDescriptionInfo = getMusicTitleAndAuthor(ytdAppData) if (ytdDescriptionInfo !== null) { isMusic = true } // videoTitle try { videoTitle = getSimpleText(ytdAppData.playerResponse.microformat.playerMicroformatRenderer.title) } catch (e) { } // genre try { genre = ytdAppData.playerResponse.microformat.playerMicroformatRenderer.category } catch (e) { } if (typeof videoTitle !== 'string') { return null } console.log(`Youtube Genius Lyrics - Genre "${genre}" is found`) if (ytdDescriptionInfo === null) { if (genre === 'Music') { isMusic = true } } // isFamilySafe try { isFamilySafe = ytdAppData.playerResponse.microformat.playerMicroformatRenderer.isFamilySafe } catch (e) { } if (isFamilySafe === false) return null // not suitable to load lyrics; isFamilySafe shall not be false for music // traditionalYtdDescriptionInfo if ytdDescriptionInfo is not available const { songTitle, songArtistsArr } = (ytdDescriptionInfo === null) ? traditionalYtdDescriptionInfo(videoTitle, videoDetails) : newYtdDescriptionInfo(ytdDescriptionInfo) return { songTitle, songArtistsArr, isMusic } } function setDisableShowLyricsButton (newValue) { if (newValue === disableShowLyricsButton) return disableShowLyricsButton = newValue if (disableShowLyricsButton === true) { removeLyricsButton() } // note: setting to false would not immediately generate the button } function addLyrics (force, beLessSpecific) { const isMainCall = arguments.length === 0 if (isMainCall) setDisableShowLyricsButton(false) // reset to false for every addLyrics() try { // ytd application data const ytdAppData = getYtdAppData() if (!ytdAppData) return // rarely happen; only if addLyrics triggered when the page is still loading if (ytdAppData.page !== 'watch') { // Not a video page or video page not visible lastVideoId = null if (isMainCall) setDisableShowLyricsButton(true) // no button if the page is confirmed as non-video page like 'browse', 'search' genius.f.hideLyricsWithMessage() return } // ytd video data const videoDetails = getVideoInfo(ytdAppData) if (!videoDetails || !videoDetails.videoId) { lastVideoId = null if (isMainCall) setDisableShowLyricsButton(false) // try to get page info again when user clicks the button else if (force === true) setDisableShowLyricsButton(true) // no video details when the button is clicked genius.f.hideLyricsWithMessage() return } const tmpVideoId = `${videoDetails.videoId}${genius.option.themeKey}` if (lastVideoId === tmpVideoId && document.getElementById('lyricscontainer')) { // Same video id and same theme and lyrics are showing -> stop here return } lastVideoId = tmpVideoId const pageSongInfoRes = getPageSongInfo(ytdAppData, videoDetails) // setDisableShowLyricsButton(true) might have called in getPageSongInfo if (!pageSongInfoRes) { // video id is known but the song info is unknown (or not suitable to show lyrics) // do not load lyrics even if force = true setDisableShowLyricsButton(true) // the page is loaded but no song info; disable the button genius.f.hideLyricsWithMessage() return } let { songTitle, songArtistsArr, isMusic } = pageSongInfoRes if (force) { isMusic = true lastForceVideoId = lastVideoId } else if (isMusic === false && (lastForceVideoId === null || lastForceVideoId !== lastVideoId)) { // show button if not disabled genius.f.hideLyricsWithMessage() return } const musicIsPlaying = isYoutubeVideoPlaying() genius.f.loadLyrics(force, beLessSpecific, songTitle, songArtistsArr, musicIsPlaying) } catch (e) { // do nothing } } let lastPos = null function updateAutoScroll (video, force) { let pos = null if (!video) { video = getYoutubeMainVideo() } if (video) { pos = video.currentTime / video.duration } if (pos !== null && pos >= 0 && `${lastPos}` !== `${pos}`) { lastPos = pos const ct = video.currentTime if (force === true) { genius.f.scrollLyrics(pos) } else { setTimeout(() => { const ct1 = video.currentTime if (ct1 - ct < 50 / 1000 && ct1 > ct) { genius.f.scrollLyrics(ct1 / video.duration) } }, 30) } } } function safeString (s) { return (s || 0).length > 0 ? `${s}` : '' } async function performSearch () { try { const container = document.querySelector('#lyricscontainer.youtube-genius-lyrics-search-container') const input = document.querySelector('input.youtube-genius-lyrics-search-input') if (!container || !input) return if (!input.value) { input.value = safeString(window.lastFetchedQuery) } const inputValue = input.value if (inputValue) { try { input.blur() } catch (e) { } container.classList.add('lyrics-searching') genius.f.searchByQuery(inputValue, container, (res) => { let c = document.querySelector('#lyricscontainer') try { if (res && res.status === 200) { const hits = res.hits if (!hits) return if (hits.length > 0) { listSongs(hits, container, inputValue) } else { input.classList.add('lyrics-input-noresult') window.incorrectInputValue = inputValue } } } catch (e) { } if (c !== null) { c.classList.remove('lyrics-searching') } c = null }) } else { input.classList.add('lyrics-input-noresult') window.incorrectInputValue = '' } } catch (e) { console.warn(e) } } function onSearchLyricsKeyDown (ev) { const input = this if (typeof window.incorrectInputValue === 'string' && input.value !== window.incorrectInputValue) { window.incorrectInputValue = null input.classList.remove('lyrics-input-noresult') } } function onSearchLyricsKeyUp (ev) { const input = this if (ev.code === 'Escape') { ev.preventDefault() input.value = '' } else if (ev.code === 'Enter') { ev.preventDefault() performSearch() return } if (typeof window.incorrectInputValue === 'string' && input.value !== window.incorrectInputValue) { window.incorrectInputValue = null input.classList.remove('lyrics-input-noresult') } } function onSearchLyricsSearchBtnClick (ev) { ev.preventDefault() performSearch() } function onSearchLyricsHideBtnClick (ev) { genius.f.hideLyricsWithMessage() } function showSearchField (query) { const spanLabel = document.createElement('span') spanLabel.classList.add('youtube-genius-lyrics-search-label') spanLabel.textContent = 'Search genius.com: ' const input = document.createElement('input') input.classList.add('youtube-genius-lyrics-search-input') // input.placeholder = 'Search genius.com...' const searchBtn = document.createElement('span') searchBtn.classList.add('youtube-genius-lyrics-search-search-btn') searchBtn.innerHTML = '' // Hide button const hideButton = document.createElement('span') hideButton.classList.add('youtube-genius-lyrics-search-hide-btn') hideButton.textContent = 'Hide' hideButton.addEventListener('click', onSearchLyricsHideBtnClick, false) const configButton = createConfigBtn('youtube-genius-lyrics-search-config-btn') input.value = safeString(query) || '' const fetechQuery = safeString(window.lastFetchedQuery) input.placeholder = fetechQuery || `${genius.option.defaultPlaceholder}` input.classList.toggle('placeholder-lastfetch', !!fetechQuery) let b = null // container window.incorrectInputValue = null input.addEventListener('keydown', onSearchLyricsKeyDown, false) input.addEventListener('keyup', onSearchLyricsKeyUp, false) searchBtn.addEventListener('click', onSearchLyricsSearchBtnClick, false) // flush DOM b = getCleanLyricsContainer() b.classList.add('youtube-genius-lyrics-search-container') document.documentElement.setAttribute('youtube-genius-lyrics-container', 'search') appendElements(b, [ spanLabel, input, searchBtn, configButton, hideButton ]) document.body.appendChild(b) /* eslint-disable no-new */ new Promise(() => { input.focus() }) } function createConfigBtn (cName) { const configButton = document.createElement('span') configButton.classList.add(cName) configButton.textContent = 'Options' configButton.addEventListener('click', function configButtonClick (ev) { genius.f.config() }) return configButton } function onLyricsFoundHideBtnClick (ev) { genius.option.autoShow = false // Temporarily disable showing lyrics automatically on song change genius.f.hideLyricsWithMessage() } function onLyricsFoundBackToSearchClick (ev) { if (document.querySelector('.loadingspinnerholder') !== null) { genius.f.cancelLoading() } genius.f.forgetLyricsSelection(genius.current.title, genius.current.artists) showSearchField() } function setupLyricsDisplayDOM (song, searchresultsLengths) { // eslint-disable-line no-unused-vars // getCleanLyricsContainer const container = getCleanLyricsContainer() container.className = '' // custom.getCleanLyricsContainer might forget to clear the className if the element is reused container.classList.add('youtube-genius-lyrics-found-container') document.documentElement.setAttribute('youtube-genius-lyrics-container', 'found') if (typeof genius.f.isGreasemonkey === 'function' && genius.f.isGreasemonkey()) { container.innerHTML = 'This script only works in TampermonkeyGreasemonkey is no longer supported because of this bug greasemonkey/issues/2574 in Greasemonkey.' return } let separator = document.createElement('span') separator.classList.add('second-line-separator') separator.classList.add('youtube-genius-lyrics-found-separator') separator.textContent = '•' const bar = document.createElement('div') bar.classList.add('lyricsnavbar') /* // Resize button if ('initResize' in custom) { const resizeButton = document.createElement('span') resizeButton.style.fontSize = '1.8em' resizeButton.style.cursor = 'ew-resize' resizeButton.textContent = '⇹' resizeButton.addEventListener('mousedown', custom.initResize) elementsToBeAppended.push(resizeButton, separator.cloneNode(true)) } */ // Hide button const hideButton = document.createElement('span') hideButton.classList.add('youtube-genius-lyrics-found-hide-btn') hideButton.textContent = 'Hide' hideButton.addEventListener('click', onLyricsFoundHideBtnClick, false) // Config button const configButton = createConfigBtn('youtube-genius-lyrics-found-config-btn') let goToSearchBtn = null if (searchresultsLengths === 1) { // Wrong lyrics button const wrongLyricsButton = document.createElement('span') wrongLyricsButton.classList.add('youtube-genius-lyrics-found-wonglyrics-btn') wrongLyricsButton.textContent = 'Search lyrics' wrongLyricsButton.addEventListener('click', onLyricsFoundBackToSearchClick, false) goToSearchBtn = wrongLyricsButton } else if (searchresultsLengths > 1) { // Back button const backbutton = document.createElement('span') backbutton.classList.add('youtube-genius-lyrics-found-back-btn') backbutton.textContent = `Back to search (${searchresultsLengths - 1} other result${searchresultsLengths === 2 ? '' : 's'})` backbutton.addEventListener('click', onLyricsFoundBackToSearchClick, false) goToSearchBtn = backbutton } const iframe = document.createElement('iframe') iframe.id = 'lyricsiframe' iframe.style.opacity = 0.1 // flush to DOM tree appendElements(bar, [ goToSearchBtn, separator.cloneNode(true), configButton, separator.cloneNode(true), hideButton ]) appendElements(container, [bar, iframe]) // clean up separator = null return { container, bar, iframe } } function getHitOfElement (li) { if (!li || li.nodeType !== 1 || !hitMaps) { return null } return hitMaps.get(li) || null } function formatPageViews (stats) { if (!stats) return null return 'pageviews' in stats && typeof stats.pageviews === 'number' ? genius.f.metricPrefix(stats.pageviews, 1) : ' - ' } async function rememberLyricsSelection (title, artists, hit) { // in order to call "genius.f.rememberLyricsSelection(title, artists, jsonHit)", use async call to get jsonHit /* eslint-disable no-new */ const jsonHit = await new Promise(function (resolve) { // this is not a complete async function, but it helps not to block the scripting resolve(JSON.stringify(hit)) }) genius.f.rememberLyricsSelection(title, artists, jsonHit) } function onLyricsResultsBackBtnClick (ev) { showSearchField() } function onLyricsResultsHideBtnClick (ev) { genius.f.hideLyricsWithMessage() } function onLyricsResultsTrackListClick (ev) { const tracklist = this const element = ev.target if (element.nodeName === 'LI') { const hit = getHitOfElement(element) if (hit !== null) { const title = genius.current.title const artists = genius.current.artists const searchresultsLengths = tracklist.querySelectorAll('li').length genius.f.showLyrics(hit, searchresultsLengths) rememberLyricsSelection(title, artists, hit) } } } function listSongs (hits, container, query) { // Back to search button const backToSearchButton = document.createElement('span') backToSearchButton.classList.add('youtube-genius-lyrics-results-back-btn') backToSearchButton.textContent = 'Back to search' backToSearchButton.addEventListener('click', onLyricsResultsBackBtnClick, false) const separator = document.createElement('span') separator.classList.add('second-line-separator') separator.classList.add('youtube-genius-lyrics-results-line-separator') separator.textContent = '•' // Hide button const hideButton = document.createElement('span') hideButton.classList.add('youtube-genius-lyrics-results-hide-btn') hideButton.textContent = 'Hide' hideButton.addEventListener('click', onLyricsResultsHideBtnClick, false) // Config button const configButton = createConfigBtn('youtube-genius-lyrics-results-config-btn') // List search results const tracklistOL = document.createElement('ol') tracklistOL.classList.add('tracklist') tracklistOL.classList.add('youtube-genius-lyrics-results-tracklist') tracklistOL.addEventListener('click', onLyricsResultsTrackListClick, true) let autoHit = autoSelectLyrics(hits) // setup _matchSource if (autoHit) autoHit = autoHit.hit const isTopResultBeingAutoHit = autoHit === hits[0] // prepare results const liArr = hits.map(function hitsMap (hit) { const li = document.createElement('li') li.classList.add('youtube-genius-lyrics-results-li') if (isTopResultBeingAutoHit && hit._order === hits[0]._order && hit._matchScore === hits[0]._matchScore) { li.classList.add('lyrics-major-result') } else { li.classList.add('lyrics-minor-result') } li.setAttribute('title', `${hit.result.title_with_featured}`) const showPageViews = false // no need to show this; pageviews usually NaN const showLyricsState = true const trackhtml = ` 🅖 📄 ${hit.result.primary_artist.name} • ${hit.result.title} ${showPageViews && showLyricsState ? `👁 ${formatPageViews(hit.result.stats) || ''} ${hit.result.lyrics_state}` : ''} ${!showPageViews && showLyricsState ? `${hit.result.lyrics_state}` : ''} ` li.innerHTML = trackhtml if (!hitMaps) { hitMaps = new WeakMap() } hitMaps.set(li, hit) return li }) appendElements(tracklistOL, liArr) // Flush DOM if (!container) { container = getCleanLyricsContainer() } else { container.className = '' container.innerHTML = '' } container.classList.add('youtube-genius-lyrics-results-container') document.documentElement.setAttribute('youtube-genius-lyrics-container', 'results') appendElements(container, [ backToSearchButton, separator, configButton, separator.cloneNode(true), hideButton, tracklistOL ]) } function loremIpsum () { const classText = ['', ''] const classWhitespace = ['', ''] const random = (x) => 1 + parseInt(Math.random() * x) let text = '' for (let v = 0; v < Math.max(3, random(5)) + 4; v++) { for (let b = 0; b < random(6); b++) { const line = [] for (let l = 0; l < random(9); l++) { for (let w = 0; w < 1 + random(10); w++) { for (let i = 0; i < 1 + random(7); i++) { line.push('x') } line.push(classText[1] + classWhitespace[0] + ' ' + classWhitespace[1] + classText[0]) } line.push(classText[1] + '\n\n' + classText[0]) } text += classText[0] + line.join('') + classText[1] + '\n\n' } } return text } function createSpinner (spinnerHolder) { lyricsDisplayState = 'loading' const lyricscontainer = document.getElementById('lyricscontainer') lyricscontainer.className = '' lyricscontainer.classList.add('youtube-genius-lyrics-loading-container') document.documentElement.setAttribute('youtube-genius-lyrics-container', 'loading') const spinner = spinnerHolder.appendChild(document.createElement('div')) spinner.classList.add('loadingspinner') const lorem = spinnerHolder.appendChild(document.createElement('div')) lorem.classList.add('lorem', 'lorem-scroll') lorem.innerHTML = loremIpsum() return spinner } function customSpinnerDOM (container, bar, iframe) { const spinnerDOM = { createSpinnerHolder: () => { const spinnerHolder = document.createElement('div') spinnerHolder.classList.add('loadingspinnerholder') spinnerDOM.spinnerHolder = spinnerHolder }, createSpinner: () => { let spinner = null const spinnerHolder = spinnerDOM.spinnerHolder spinner = createSpinner(spinnerHolder) spinnerDOM.spinner = spinner }, displaySpinnerHolder: () => { container.appendChild(spinnerDOM.spinnerHolder) }, setStatusTitle: (title) => { const spinnerHolder = spinnerDOM.spinnerHolder spinnerHolder.title = title }, setSpinnerNum: (text) => { const spinner = spinnerDOM.spinner spinner.textContent = text }, remove: (text) => { const spinnerHolder = spinnerDOM.spinnerHolder spinnerHolder.remove() } } return spinnerDOM } function iframeLoadedCallback1 (res) { lyricsDisplayState = 'loaded' } function iframeLoadedCallback2 (res) { // nothing } function textSlash (text) { // Create a set object which contains the information of the title text = text .replace(/\b([a-z0-9A-Z]+)[:~\-+]([a-z0-9A-Z]+)\b/, '$1$3') .replace(/[\uFF01-\uFF5E]/g, (m) => { // Halfwidth and Fullwidth Forms return String.fromCharCode(m.charCodeAt(0) - 65248) }) .replace(/[\u180E\u200B-\u200D\u2060\uFEFF]+/g, '') // zero-spacing .replace(/[\s/\u0009-\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\u00B7\u237D\u2420\u2422\u2423]+/g, '/') /* spacing */ // eslint-disable-line no-control-regex .replace(/[\uFF01-\uFF0F\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u3000\u3001-\u303F\u2000-\u206F]+/g, '/') // Symbols and Punctuation .replace(/\/+/g, '/') let s = text.split('/') const r = new Set() for (let t of s) { if (t && t.length > 0) { t = t.toLowerCase() if (!r.has(t)) { r.add(t) } } } s = null return r } function autoSelectLyrics (hits) { // only do title matching (+channel name) // to search the lyrics, use the short title // to figure out which one is the most correct one, use full / featured title const ytdApp = document.querySelector('ytd-app') let ytdAppData = null let videoDetails = null if (!ytdApp) return ytdAppData = getYtdAppData() if ('player' in ytdAppData && 'args' in ytdAppData.player && 'raw_player_response' in ytdAppData.player.args && 'videoDetails' in ytdAppData.player.args.raw_player_response) { videoDetails = ytdAppData.player.args.raw_player_response.videoDetails } else { videoDetails = ytdAppData.playerResponse.videoDetails } const videoTitle = videoDetails.title if (typeof videoTitle !== 'string') return // console.log(videoDetails) // console.log(videoTitle, textSlash(videoTitle)) const slashVideoTitle = textSlash(`${videoTitle}\n${videoDetails.author || ''}`) let automaticHit = null for (const hit of hits) { const result = hit.result || 0 const fTitle = result.full_title || result.title_with_featured || result.title if (!fTitle || typeof fTitle !== 'string') continue const slashFTitle = textSlash(`${fTitle}\n${result.artist_names || ''}`) let score = 0 for (const key of slashFTitle.keys()) { if (slashVideoTitle.has(key)) score++ } hit._matchScore = score if (automaticHit === null || hit._matchScore > automaticHit._matchScore) { automaticHit = hit } } // console.log(hits, automaticHit) if (automaticHit !== null) { return { hit: automaticHit } } } function main () { if (lyricsDisplayState === 'loading') { // avoid iframe communcation error return } let ytdAppData = getYtdAppData() if (!ytdAppData) { // the status is unknown return } else if (ytdAppData.page !== 'watch' || !isYtdAppReady()) { // ytdApp is initized but the pagetype or video info not exist // (youtube web app page to 'search' or 'browse') genius.f.hideLyricsWithMessage() return } ytdAppData = null if (genius.option.autoShow) { addLyrics() } else { addLyricsButton() } } let isTriggered = false function executeMainWhenVisible (t) { if (!isTriggered) { window.requestAnimationFrame(() => { if (isTriggered) return setTimeout(() => { if (isTriggered) return isTriggered = true main() }, t) }) } } function delayedMain () { isTriggered = false // time allowed for other userscript(s) prepare the page // and also not block the page window.lastFetchedQuery = null // reset search when media changed executeMainWhenVisible(200) } function newAppHint (status) { // TODO should this be removed in favor of a README hint in the next version? if (document.location.pathname === '/robots.txt') { return } if (document.getElementById('lyricscontainer') || document.getElementById('showlyricsbutton')) { // Other script already running return GM.setValue('newapphint', -1) } if (status % 10 === 0) { let style = document.querySelector('style#newapphint785_style') if (style === null) { style = document.createElement('style') style.id = 'newapphint785_style' style.innerHTML = ` #newapphint785 { position:fixed; top:0%; left:0%; padding:10px; background-color:#202020; color:#bbb; font-size:large; border:2px solid red; border-radius: 5px; box-shadow: red 1px 1px 10px; transition:left 500ms, top 500ms; z-index:2500 } #newapphint785 a:link, #newapphint785 a:visited { color:white; text-decoration:none } #newapphint785 a:hover { color:#b0ae10; text-decoration:none } #newapphint785 button { font-size: large; background: #555; border: 2px outset #555; margin: 3px 10px; padding: 2px; color: #eee; } #newapphint785 button:hover { border: 2px outset #fff; color: #fff; } ` document.head.appendChild(style) } const container = document.createElement('div') container.id = 'newapphint785' document.body.appendChild(container) const h2 = container.appendChild(document.createElement('h2')) h2.textContent = '⚠️ Youtube Genius Lyrics 🆕' const p = container.appendChild(document.createElement('p')) p.textContent = '▶️ If you would like to see lyrics here as well, you can now install a new userscript specifically for music.youtube.com:' p.appendChild(document.createElement('br')) p.appendChild(document.createElement('br')) const aSource = p.appendChild(document.createElement('a')) aSource.target = '_blank' aSource.href = 'https://openuserjs.org/scripts/cuzi/Youtube_Music_Genius_Lyrics' aSource.textContent = '📑 https://openuserjs.org/scripts/cuzi/Youtube_Music_Genius_Lyrics' p.appendChild(document.createElement('br')) p.appendChild(document.createElement('br')) const aInstall = p.appendChild(document.createElement('a')) aInstall.href = 'https://openuserjs.org/install/cuzi/Youtube_Music_Genius_Lyrics.user.js' aInstall.textContent = '💘 Click to install new script' aInstall.addEventListener('click', function () { GM.setValue('newapphint', -1).then(function () { aInstall.innerHTML = 'ℹ️ Please reload (F5) the page after installing' }) }) p.appendChild(document.createElement('br')) p.appendChild(document.createElement('br')) const remindMeLater = container.appendChild(document.createElement('button')) remindMeLater.textContent = '🔜 Remind me later' remindMeLater.addEventListener('click', function () { GM.setValue('newapphint', 1).then(() => container.remove()) }) container.appendChild(document.createElement('br')) const doNotShowAgain = container.appendChild(document.createElement('button')) doNotShowAgain.textContent = '🆗🆒 Do not show again' doNotShowAgain.addEventListener('click', function () { GM.setValue('newapphint', -1).then(() => container.remove()) }) window.setTimeout(function () { container.style.left = `calc(50% - ${container.clientWidth / 2}px)` container.style.top = `calc(50% - ${container.clientHeight / 2}px)` }, 100) } else if (status > 0) { GM.setValue('newapphint', status + 1) } } if (document.location.hostname.startsWith('music')) { GM.getValue('newapphint', 0).then(function (status) { window.setTimeout(() => newAppHint(status), 5000) }) } else { let isInIframe = null try { isInIframe = top && window && top.constructor.name === 'Window' && window.constructor.name === 'Window' && top !== window } catch (e) { } const isRobotsTxt = document.location.href.indexOf('robots.txt') >= 0 const setupMain = isRobotsTxt ? function setupMain () { // do nothing } : function setupMain () { executeMainWhenVisible(600) document.removeEventListener('yt-navigate-finish', delayedMain, false) document.addEventListener('yt-navigate-finish', delayedMain, false) } genius = null if (isInIframe && !isRobotsTxt) { // do nothing } else { // should it be required for robots.txt as well?? can remove?? genius = geniusLyrics({ GM, scriptName: SCRIPT_NAME, scriptIssuesURL: 'https://github.com/cvzi/Youtube-Genius-Lyrics-userscript/issues', scriptIssuesTitle: 'Report problem: github.com/cvzi/Youtube-Genius-Lyrics-userscript/issues', domain: 'https://www.youtube.com/', emptyURL: 'https://www.youtube.com/robots.txt', main, setupMain, addCss, getHitOfElement, listSongs, showSearchField, setupLyricsDisplayDOM, addLyrics, hideLyrics, getCleanLyricsContainer, setFrameDimensions, onResize, createSpinner, customSpinnerDOM, iframeLoadedCallback1, iframeLoadedCallback2, autoSelectLyrics }) } if (isRobotsTxt === false && genius !== null && genius.option) { GM.registerMenuCommand(SCRIPT_NAME + ' - Show lyrics', () => addLyrics(true)) function videoTimeUpdate (ev) { if (genius.f.isScrollLyricsCallable()) { if ((ev || 0).target.nodeName === 'VIDEO') updateAutoScroll() } } document.addEventListener('genius-lyrics-actor', (ev) => { const detail = ((ev || 0).detail || 0) const action = detail.action || '' if (action === 'hideLyrics') { genius.f.hideLyricsWithMessage() } else if (action === 'showLyrics') { showLyricsButtonClicked() } else if (action === 'reloadCurrentLyrics') { genius.f.reloadCurrentLyrics() } else if (action === 'forgetCurrentLyricsSelection') { genius.f.forgetCurrentLyricsSelection() } else if (action === 'setOption' && typeof detail.prop === 'string' && 'value' in detail) { genius.option[detail.prop] = detail.value } }) window.addEventListener('message', function (e) { const data = ((e || 0).data || 0) if (data.iAm === SCRIPT_NAME && data.type === 'lyricsDisplayState') { let isScrollLyricsEnabled = false if (data.visibility !== 'loading') { const c = document.querySelector('#lyricscontainer.youtube-genius-lyrics-loading-container') if (c) { c.classList.remove('youtube-genius-lyrics-loading-container') if (data.visibility === 'loaded') { c.classList.add('youtube-genius-lyrics-found-container') document.documentElement.setAttribute('youtube-genius-lyrics-container', 'found') window.lastFetchedQuery = `${genius.current.artists} ${genius.current.title}` } else { document.documentElement.removeAttribute('youtube-genius-lyrics-container') // ??? } } } else { window.lastFetchedQuery = null } if (data.visibility === 'loaded' && data.lyricsSuccess === true) { isScrollLyricsEnabled = genius.f.isScrollLyricsEnabled() // 'lyricsDisplayState:loaded' is after isPageAbleForAutoScroll setup in 'iframeContentRendered' if (genius.f.isScrollLyricsCallable()) { // update scroll position when the iframe is rendered updateAutoScroll(null, true) } } lyricsDisplayState = data.visibility if (isScrollLyricsEnabled === true) { document.addEventListener('timeupdate', videoTimeUpdate, true) } else { document.removeEventListener('timeupdate', videoTimeUpdate, true) } } }) document.addEventListener('play', function (ev) { if (((ev || 0).target || 0).nodeName === 'VIDEO' && ev.target.matches('#movie_player video[src]')) { if (lyricsDisplayState === 'hidden') { window.setTimeout(main, 600) } } }, true) function autoscrollenabledChanged () { // when value is configurated in any tab, this function will be triggered in all tabs by Userscript Manager if (typeof genius.f.updateAutoScrollEnabled !== 'function') return window.requestAnimationFrame(() => { // not execute for all foreground and background tabs, only execute when the tab is visibile / when the tab shows genius.f.updateAutoScrollEnabled().then(() => { let isScrollLyricsEnabled = false if (lyricsDisplayState === 'loaded') { isScrollLyricsEnabled = genius.f.isScrollLyricsEnabled() } if (isScrollLyricsEnabled === true) { document.addEventListener('timeupdate', videoTimeUpdate, true) } else { document.removeEventListener('timeupdate', videoTimeUpdate, true) } }) }) } if (typeof GM_addValueChangeListener === 'function') { GM_addValueChangeListener('autoscrollenabled', autoscrollenabledChanged) } 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') return false const ytdApp = document.querySelector('ytd-app') if (!ytdApp) return const cStyle = window.getComputedStyle(ytdApp) let background = cStyle.getPropertyValue('--yt-spec-base-background') let color = cStyle.getPropertyValue('--yt-spec-text-primary') // let bbp = cStyle.getPropertyValue('--yt-spec-brand-background-primary') // let cfs = cStyle.getPropertyValue('--yt-caption-font-size') let fontSize = null let slbc = cStyle.getPropertyValue('--ytd-searchbox-legacy-button-color') const linkColor = cStyle.getPropertyValue('--yt-spec-call-to-action') || '' const expander = document.querySelector('ytd-expander.style-scope.ytd-video-secondary-info-renderer') if (expander) { fontSize = window.getComputedStyle(expander).fontSize } else { fontSize = cStyle.fontSize } if (typeof background === 'string' && typeof color === 'string' && background.length > 3 && color.length > 3) { // do nothing } else { background = null color = null } if (typeof fontSize === 'string' && fontSize.length > 2) { // do nothing } else { fontSize = null } if (typeof slbc === 'string') { // do nothing } else { slbc = null } Object.assign(genius.styleProps, { '--egl-background': (background === null ? '' : `${background}`), '--egl-color': (color === null ? '' : `${color}`), '--egl-font-size': (fontSize === null ? '' : `${fontSize}`), '--egl-infobox-background': (slbc === null ? '' : `${slbc}`), '--egl-link-color': (`${linkColor}`) }) return true } } Object.assign(genius.minimizeHit, { noImageURL: true, noFeaturedArtists: true, simpleReleaseDate: true, noRawReleaseDate: true, shortenArtistName: true, fixArtistName: true, removeStats: true, // pageviews cannot be displayed noRelatedLinks: true, onlyCompleteLyrics: false }) genius.option.enableStyleSubstitution = true genius.option.cacheHTMLRequest = true // 1 lyrics page consume 2XX KB [OR 25 ~ 50KB under ] document.documentElement.classList.add('youtube-genius-lyrics') } }
${hit.result.primary_artist.name} • ${hit.result.title}
👁 ${formatPageViews(hit.result.stats) || ''} ${hit.result.lyrics_state}
${hit.result.lyrics_state}