您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
helpful utilities when working with song lyrics and OpenSong
当前为
// ==UserScript== // @name lyricUtils v2 // @namespace Violentmonkey Scripts // @include https://www.azlyrics.com/lyrics/* // @include https://www.letras.com/* // @include https://4334.sk/* // @include https://www.songlyrics.com/* // @include https://www.lyrics.com/track/* // @include https://www.lyrics.com/lyric-lf/* // @include https://bethelmusic.com/chords-and-lyrics/* // @include https://genius.com/* // @grant none // @version 2.6 // @author KraXen72 // @locale en-US // @license GPLv3 // @description helpful utilities when working with song lyrics and OpenSong // ==/UserScript== // the current site let site = { n: "", // name querySelector: "", // where to get text from holderqs: "", // where to inject our versed div, other: overwrite, headonly textKey: "" // what to use to get text (for exampe innerText or textContent) } const TempText = document.createElement("textarea"); const defaultElemStyleObj = { padding: ".5rem", border: "2px solid darkblue", borderRadius: "5px", margin: "0 .25rem", height: "40px", width: "40px", boxSizing: "border-box", } function determineSite() { //console.log(window.location.hostname) switch (window.location.hostname) { case "4334.sk": site.n = "4334"; site.holderqs = ".entry-content" break; case "www.letras.com": site.n = "letras"; site.querySelector = ".cnt-letra.p402_premium" site.holderqs = "overwrite" site.textKey = "innerText" break; case "www.azlyrics.com": site.n = "azlyrics"; site.querySelector = ".col-xs-12.col-lg-8.text-center div:not([class]):not([id])"; site.holderqs = ".col-xs-12.col-lg-8.text-center" site.textKey = "textContent" break; case "www.songlyrics.com": site.n = "songlyrics" site.querySelector = "#songLyricsDiv" site.holderqs = "overwrite" site.textKey = "textContent" break; case "www.lyrics.com": site.n = "lyricscom" site.querySelector = "#lyric-body-text" site.holderqs = "overwrite" site.textKey = "innerText" break; case "bethelmusic.com": site.n = "bethel" site.holderqs = "#tabLyrics" site.textKey = "innerText" break; case "genius.com": site.n = "genius" site.querySelector = '.YYrds' site.holderqs = "headonly" site.textKey = "innerText" break; default: console.error(`unknown site ${window.location.hostname}`); } } function injectCSS(css) { const styleTag = document.createElement("style") styleTag.innerHTML = css document.head.appendChild(styleTag) } function makeBtn(text, onclick) { const btn = document.createElement("button") btn.classList.add("lyricUtils-button") Object.assign(btn.style, defaultElemStyleObj) btn.onclick = onclick btn.textContent = text return btn } function copyMe(textToCopy) { TempText.value = textToCopy; //TempText.style.display = "none" document.body.appendChild(TempText); TempText.select(); document.execCommand("copy"); document.body.removeChild(TempText); console.log("copied stuff", textToCopy) } function groupToVerses(text) { const lines = text.split("\n") const newLines = [...lines].filter(l => l !== "Hide Chords") let verses = [""] // starts with empty verse console.log("nl", newLines) for (let i = 0; i < newLines.length; i++) { const l = newLines[i] if (site.n === "genius" ? l.startsWith("[") && l.endsWith("]") : l === "" ) { verses.push("") // start a new verse } else if (l.startsWith(" ")) { // if a thing like " (What?)" is on new line, after annotation. verses[verses.length - 1] = verses[verses.length - 1].slice(0, -4) + l + "<br>" } else { verses[verses.length - 1] += l + "<br>" // continue an existing verse } } verses = verses.filter(v => v.trim() !== "") // filter out empty verses return verses } function getTextContent() { let stuff = "" const newHolder = document.getElementById("lyricUtils-holder") Array.from(newHolder.children).forEach(child => { if (site.n === "genius") { if (!(child.classList.contains("lyricUtils-button")) && child.id !== "lyricUtils-stateDiv") { child.innerHTML .split("<br>") .filter((item, i, arr) => item.trim() !== "" || i === arr.length - 1) .forEach((line, i, arr) => stuff += line.replaceAll("&", "&") + "\n") } } else { if (!(child.classList.contains("lyricUtils-button")) && child.id !== "lyricUtils-stateDiv") stuff += child.innerText if (child.classList.contains("lyricUtils-vhead") || child.classList.contains("lyricUtils-verse")) stuff += "\n" } }) console.log("tocopy", stuff) return stuff } function constructVerseElement(verses) { const holder = document.createElement("div") holder.id = "lyricUtils-holder" verses.forEach((v, index, arr) => { const vDiv = document.createElement("div") vDiv.id = `V${index + 1}-lyricUtils` vDiv.classList.add("lyricUtils-verse") vDiv.innerHTML = v holder.appendChild(vDiv) if (index !== arr.length -1) holder.appendChild(document.createElement("br")) }) return holder } function addHeaders() { if (document.querySelector(".lyricUtils-vhead") === null) { const allVerses = [...document.getElementsByClassName("lyricUtils-verse")] const newHolder = document.getElementById("lyricUtils-holder") allVerses.forEach(verse => { const vHead = document.createElement("div") vHead.classList.add("lyricUtils-vhead") vHead.textContent = `[${verse.id.split("-")[0]}]` // VXX-lyricUtils => [VXX] newHolder.insertBefore(vHead, verse) }) document.getElementById("lyricUtils-stateDiv").style.background = "lightgreen" } else { console.log("headers already added") } } function removeHeaders() { [...document.getElementsByClassName("lyricUtils-vhead")].forEach(head => head.remove()) document.getElementById("lyricUtils-stateDiv").style.background = "" } const elem = (qs) => { if (typeof qs === "string") return document.querySelector(qs) return qs } const text = (qs, key) => { if (typeof qs === "string") return document.querySelector(qs)[key] return qs[key] } const textAll = (qs, key) => { if (typeof qs === "string") return [...document.querySelectorAll(qs)].map(elem => elem[key]).join("\n") return qs[key] } // transparent modifier function function transModFn(argument, debug = true) { if (debug) console.log(`fn-debug:`, argument) return argument } const extractors = { // there are a bunch of mod function that can modify some part of the process, if needed // by default they are set to transModFn, which just passes through the value _standard: (qs, key, modTextFn = transModFn, modVerseFn = transModFn, modElemFn = transModFn) => { console.log("debug: ", qs, key) const rawText = modTextFn( text(qs, key) ) const verses = modVerseFn( groupToVerses(rawText) ) const ourElement = modElemFn( constructVerseElement(verses) ) if (site.holderqs === "overwrite") { elem(site.querySelector).innerHTML = ourElement.innerHTML elem(site.querySelector).id = "lyricUtils-holder" //has to go second, because qs can be an id selector } else if (site.holderqs === "headonly") { const holder = elem(site.querySelector).parentElement injectCSS(`.lyricUtils-verse { display: none; }`) holder.insertBefore(ourElement, elem(site.querySelector)) } else { const holder = document.querySelector(site.holderqs) holder.insertBefore(ourElement, elem(site.querySelector).nextElementSibling) } const stateElem = Object.assign(document.createElement("button"), { id: "lyricUtils-stateDiv", innerHTML: "🆚", title: "green = opensong verse headings [V1], [V2], ... are added"}) Object.assign(stateElem.style, {... defaultElemStyleObj, cursor: "default" }) const newHolder = document.getElementById("lyricUtils-holder") newHolder.prepend(stateElem) newHolder.prepend(makeBtn("📋", () => { copyMe(getTextContent()) })) newHolder.prepend(makeBtn("⛔", removeHeaders)) newHolder.prepend(makeBtn("➕", addHeaders)) injectCSS(`.lyricUtils-button:hover { background-color: rgba(0,0,0,0.2) }`) console.log(site.n) console.log(verses) }, azlyrics: () => { injectCSS(`${site.querySelector} { display: none !important; }`) extractors._standard(site.querySelector, site.textKey) }, letras: () => { injectCSS(`#player { display: none !important; } ::selection { background: #dcdc00 !important; }`); extractors._standard(site.querySelector, site.textKey) }, 4334: () => { injectCSS(` div[style*="visibility: visible; position: absolute;"] { display: none !important; } .entry-content { display: flex; flex-direction: row-reverse; justify-content: space-between } .site-navigation-inner .searchform input, { width: 200px !important; padding-left: 10px !important; transition: all 0s !important; } .site-navigation-inner .searchform.active { margin-left: 0px !important } `) // fix up stuff for copying: 4334 has chord divs in the text const clonedText = document.querySelector(".chordwp-container").cloneNode(true) const wrappers = [...clonedText.querySelectorAll(".chwp-lyrics-row-wrapper")] lines = [] wrappers.forEach(w => { const lyr = [...w.querySelectorAll(".chwp-lyrics")] let txt = [] lyr.forEach(l => txt.push(l.textContent)) lines.push(`<div class="line">${txt.join("")}</div>`) if (w.nextElementSibling && w.nextElementSibling.tagName === "BR") lines.push(`<div class="line"><br></div>`) }) clonedText.innerHTML = lines.join("\n") site.querySelector = clonedText site.textKey = "innerText" extractors._standard(site.querySelector, site.textKey) }, songlyrics: () => { injectCSS(` .iComment-popup { display: none !important; pointer-events: none !important; } #lyricUtils-holder { font-size: 16px; line-height: 1.6; } `) extractors._standard(site.querySelector, site.textKey) }, lyricscom: () => { extractors._standard(site.querySelector, site.textKey) }, genius: () => { injectCSS(` #lyricUtils-holder { grid-column: left-start / left-end; height: 2.1rem !important; min-height:0 !important; } .fdEmdh, .lyricUtils-vhead { display: none; } `) // overwrite the text getter to use querySelectorAll, overwrite the verses to remove verse headings extractors._standard(site.querySelector, site.textKey, () => textAll(site.querySelector, site.textKey), (value) => { return value.map(verse => { items = verse.split("<br>") if (items[0].startsWith("[")) items.shift() return items.join("<br>") }) }) }, bethel: () => { const lyricsDiv = document.createElement("div") lyricsDiv.innerHTML = document.querySelector("#tabLyrics .content").innerHTML const bTags = [...lyricsDiv.querySelectorAll("p > b")] bTags.forEach(b => b.remove()) injectCSS(` #tabLyrics { display: grid; grid-template: auto / max-content max-content auto; column-gap: 1rem; } #lyricUtils-holder { font-size: 16px; line-height: 2; grid-colum: 2 / 3 } #tabLyrics .content { grid-colum: 1 / 2 } .nav.nav-tabs { display: none !important; poiner-events: none; } `) site.querySelector = lyricsDiv extractors._standard(lyricsDiv, site.textKey) } } determineSite() extractors[site.n]()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址