main.ts (5311B)
1 import "./meta" 2 3 import { ScorePlayerData } from "./types" 4 import { waitForDocumentLoaded } from "./utils" 5 import * as recaptcha from "./recaptcha" 6 7 import { PDFWorkerHelper } from "./worker-helper" 8 import FileSaver from "file-saver/dist/FileSaver.js" 9 10 const saveAs: typeof import("file-saver").saveAs = FileSaver.saveAs 11 12 let pdfBlob: Blob 13 14 const generatePDF = async (imgURLs: string[], imgType: "svg" | "png", name?: string) => { 15 if (pdfBlob) { 16 return saveAs(pdfBlob, `${name}.pdf`) 17 } 18 19 const cachedImg = document.querySelector("img[src*=score_]") as HTMLImageElement 20 const { naturalWidth: width, naturalHeight: height } = cachedImg 21 22 const worker = new PDFWorkerHelper() 23 const pdfArrayBuffer = await worker.generatePDF(imgURLs, imgType, width, height) 24 worker.terminate() 25 26 pdfBlob = new Blob([pdfArrayBuffer]) 27 28 saveAs(pdfBlob, `${name}.pdf`) 29 } 30 31 const getPagesNumber = (scorePlayerData: ScorePlayerData) => { 32 try { 33 return scorePlayerData.json.metadata.pages 34 } catch (_) { 35 return document.querySelectorAll("img[src*=score_]").length 36 } 37 } 38 39 const getImgType = (): "svg" | "png" => { 40 try { 41 const imgE: HTMLImageElement = document.querySelector("img[src*=score_]") 42 const { pathname } = new URL(imgE.src) 43 const imgtype = pathname.match(/\.(\w+)$/)[1] 44 return imgtype as "svg" | "png" 45 } catch (_) { 46 return null 47 } 48 } 49 50 const getTitle = (scorePlayerData: ScorePlayerData) => { 51 try { 52 return scorePlayerData.json.metadata.title 53 } catch (_) { 54 return "" 55 } 56 } 57 58 const getScoreFileName = (scorePlayerData: ScorePlayerData) => { 59 return getTitle(scorePlayerData).replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, "_") 60 } 61 62 const main = () => { 63 64 // @ts-ignore 65 if (!window.UGAPP || !window.UGAPP.store || !window.UGAPP.store.jmuse_settings) { return } 66 67 // init recaptcha 68 recaptcha.init() 69 70 // @ts-ignore 71 const scorePlayer: ScorePlayerData = window.UGAPP.store.jmuse_settings.score_player 72 73 const { id } = scorePlayer.json 74 const baseURL = scorePlayer.urls.image_path 75 76 // const msczURL = `https://musescore.com/static/musescore/scoredata/score/${getIndexPath(id)}/${id}/score_${vid}_${scoreHexId}.mscz` 77 78 // https://github.com/Xmader/cloudflare-worker-musescore-mscz 79 const msczURL = `https://musescore.now.sh/api/mscz?id=${id}&token=` 80 81 const mxlURL = baseURL + "score.mxl" 82 const { midi: midiURL, mp3: mp3URL } = scorePlayer.urls 83 84 const btnsDiv = document.querySelector(".score-right .buttons-wrapper") || document.querySelectorAll("aside section > div")[4] 85 const downloadBtn = btnsDiv.querySelector("button, .button") as HTMLElement 86 downloadBtn.onclick = null 87 88 // fix the icon of the download btn 89 // if the `downloadBtn` seleted was a `Print` btn, replace the `print` icon with the `download` icon 90 const svgPath: SVGPathElement = downloadBtn.querySelector("svg > path") 91 if(svgPath) { 92 svgPath.setAttribute("d", "M9.6 2.4h4.8V12h2.784l-5.18 5.18L6.823 12H9.6V2.4zM19.2 19.2H4.8v2.4h14.4v-2.4z") 93 } 94 95 const imgType = getImgType() || "svg" 96 97 const sheetImgURLs = Array.from({ length: getPagesNumber(scorePlayer) }).fill(null).map((_, i) => { 98 return baseURL + `score_${i}.${imgType}` 99 }) 100 101 const downloadURLs = { 102 "MSCZ": msczURL, 103 "PDF": null, 104 "MusicXML": mxlURL, 105 "MIDI": midiURL, 106 "MP3": mp3URL, 107 } 108 109 const createBtn = (name: string) => { 110 const btn = downloadBtn.cloneNode(true) as HTMLElement 111 112 if (btn.nodeName.toLowerCase() == "button") { 113 btn.setAttribute("style", "width: 205px !important") 114 } else { 115 btn.dataset.target = "" 116 } 117 118 const textNode = [...btn.childNodes].find((x) => { 119 return x.textContent.includes("Download") || x.textContent.includes("Print") 120 }) 121 textNode.textContent = `Download ${name}` 122 123 return { 124 btn, 125 textNode, 126 } 127 } 128 129 const newDownloadBtns = Object.keys(downloadURLs).map((name) => { 130 const url = downloadURLs[name] 131 const { btn, textNode } = createBtn(name) 132 133 if (name == "PDF") { 134 btn.onclick = () => { 135 const text = textNode.textContent 136 const filename = getScoreFileName(scorePlayer) 137 138 textNode.textContent = "Processing…" 139 140 generatePDF(sheetImgURLs, imgType, filename).then(() => { 141 textNode.textContent = text 142 }) 143 } 144 } else if (name == "MSCZ") { 145 btn.onclick = async () => { 146 const text = textNode.textContent 147 textNode.textContent = "Processing…" 148 149 const token = await recaptcha.execute() 150 const filename = getScoreFileName(scorePlayer) 151 152 const r = await fetch(url + token) 153 const data = await r.blob() 154 155 textNode.textContent = text 156 157 saveAs(data, `${filename}.mscz`) 158 } 159 } else { 160 btn.onclick = () => { 161 window.open(url) 162 } 163 } 164 165 return btn 166 }) 167 168 downloadBtn.replaceWith(...newDownloadBtns) 169 170 } 171 172 waitForDocumentLoaded().then(main)