lyricUtils v2

helpful utilities when working with song lyrics and OpenSong

目前为 2022-07-15 提交的版本。查看 最新版本

// ==UserScript==
// @name        lyricUtils v2
// @namespace   Violentmonkey Scripts
// @include     https://www.azlyrics.com/lyrics/*
// @include     https://www.letras.com/*
// @include     https://4334.sk/*
// @grant       none
// @version     2.0
// @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
  textKey: "" // what to use to get text (for exampe innerText or textContent)
}

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;
    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")
  btn.style.padding = ".5rem"
  btn.style.border = "2px solid darkblue"
  btn.style.borderRadius = "5px"
  btn.style.margin = "0 .25rem"
  btn.onclick = onclick
  btn.textContent = text
  return btn
}

function copyMe(textToCopy) {
  const TempText = document.createElement("textarea");
  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]
  let verses = [""] // starts with empty verse
  
  console.log("nl", newLines)
  for (let i = 0; i < newLines.length; i++) {
    const l = newLines[i]
    if (l === "") {
      verses.push("") // start a new verse
    } else {
      verses[verses.length - 1] += l + "<br>" // continue an existing verse
    }
  }
  verses = verses.filter(v => v !== "") // filter out empty verses
  return verses
}

function getTextContent() {
  let stuff = ""
  const newHolder = document.getElementById("lyricUtils-holder")

  Array.from(newHolder.children).forEach(child => {
    if (!(child.classList.contains("lyricUtils-button"))) stuff += child.innerText
    if (child.classList.contains("lyricUtils-vhead") || child.classList.contains("lyricUtils-verse")) stuff += "\n"
  })
  console.log(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
    //vDiv.style.margin = "1rem 0"
    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)
    })
  } else {
    console.log("headers already added")
  }
}

function removeHeaders() {
  [...document.getElementsByClassName("lyricUtils-vhead")].forEach(head => head.remove())
}

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 extractors = {
  _standard: (qs, key) => {
    const rawText = text(qs, key)
    const verses = groupToVerses(rawText)
    const ourElement = constructVerseElement(verses)
    if (site.holderqs === "overwrite") {
      elem(site.querySelector).id = "lyricUtils-holder"
      elem(site.querySelector).innerHTML = ourElement.innerHTML
    } else {
      const holder = document.querySelector(site.holderqs)
      holder.insertBefore(ourElement, elem(site.querySelector).nextElementSibling)
    }
    
    const newHolder = document.getElementById("lyricUtils-holder")
    newHolder.prepend(makeBtn("📋", () => { copyMe(getTextContent()) }))
    newHolder.prepend(makeBtn("⛔", removeHeaders))
    newHolder.prepend(makeBtn("➕", addHeaders))
    
    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 }
    `)
    
    // 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)
  }
}


determineSite()
extractors[site.n]()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址