Musescore Downloader

download pdf or print any sheets!

目前为 2022-10-19 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Musescore Downloader
  3. // @version 1.1
  4. // @description download pdf or print any sheets!
  5. // @author Charlie
  6. // @match https://musescore.com/*
  7. // @namespace https://greasyfork.org/users/890174
  8. // @grant GM_addStyle
  9. // ==/UserScript==
  10.  
  11. setTimeout(() => {
  12. "use strict"
  13. GM_addStyle(`
  14. header > *:not(nav),
  15. .hFMfR > .AFyen,
  16. .YaB2I > section :nth-last-child(n+2),
  17. aside > div:nth-last-child(2),
  18. aside > div:nth-child(2),
  19. aside > div:nth-child(3),
  20. #jmuse-scroller-component div:not(.BmIOX),
  21. footer, ._9aj2, .p6izg, .TnQwe, .U8wvj, .jxGLy, .YRlfn, .YMprF, .MXIPY {
  22. display: none !important;
  23. }
  24. .hFMfR {
  25. left: unset !important;
  26. right: 36px !important;
  27. }
  28. .dXt6a {
  29. background-color: #e1effe !important;
  30. padding: .3em .8em !important;
  31. border-left: 5px solid #1a4f9f !important;
  32. font-size: 14px !important;
  33. }
  34. .ZvxB2, .AJXCt {
  35. background-color: #e1effe7c !important;
  36. padding: .6em .8em !important;
  37. border-radius: 4px !important;
  38. outline: 1.5px solid #1a4f9f !important;
  39. flex-direction: column !important;
  40. }
  41. aside a {
  42. padding: 0 .3em !important;
  43. transition: background-color ease-out .15s !important;
  44. }
  45. aside a:hover {
  46. background-color: #45f !important;
  47. color: #f3f3ff !important;
  48. }
  49. aside > div:nth-child(5) {
  50. border: none !important;
  51. }
  52. aside > div:nth-child(4),
  53. aside > div:nth-child(5) {
  54. padding: 12px 20px !important;
  55. }
  56. aside > div:nth-child(4) > div {
  57. display: flex !important;
  58. place-items: center !important;
  59. }
  60. aside > div:nth-child(4) > div > .Pl3iC {
  61. margin-right: 16px !important;
  62. }
  63. aside table {
  64. background-color: #e1effe7c !important;
  65. padding: .3em .6em !important;
  66. border-radius: 3px !important;
  67. outline: 1.5px solid #1a4f9f !important;
  68. }
  69. aside table tr {
  70. padding-bottom: 5px !important;
  71. }
  72. .a0naR,
  73. .Au_pg,
  74. .Al3lQ {
  75. height: 32px !important;
  76. width: 32px !important;
  77. color: transparent !important;
  78. border-radius: 5px !important;
  79. }
  80. .hyzNL {
  81. max-height: unset !important;
  82. }
  83. .dXt6a section {
  84. margin: 0 !important;
  85. }`)
  86.  
  87. const ZH = navigator.language == "zh-CN"
  88.  
  89. async function download() {
  90. const Window = window.open()
  91. const id = location.pathname.match(/\/[^\/]*$/)[0].slice(1)
  92. const length = document.getElementsByClassName("BmIOX").length
  93. Window.document.write(`
  94. <!DOCTYPE html>
  95. <head>
  96. <title>${document.getElementsByTagName('h1')[0].innerText}</title>
  97. <style>
  98. body {
  99. margin: 0;
  100. display: flex;
  101. min-height: 100vh;
  102. flex-direction: column;
  103. place-items: center;
  104. justify-content: center;
  105. color: rgb(49, 63, 78);
  106. background-color: rgb(237, 242, 247);
  107. transform-origin: top;
  108. transition: background-color ease .3s;
  109. will-change: background-color;
  110. overflow-x: scroll;
  111. }
  112.  
  113. svg {
  114. width: 100vw;
  115. height: auto;
  116. display: block;
  117. margin: auto;
  118. }
  119.  
  120. .card {
  121. display: flex;
  122. text-align: center;
  123. place-items: center;
  124. font-size: 1.2em;
  125. width: ${ ZH ? 360 : 480 }px;
  126. box-shadow: 10px 10px 8px rgba(0, 0, 0, 0.07);
  127. padding: 1.5em 2em;
  128. background-color: rgb(255, 255, 255);
  129. border-radius: 8px;
  130. transition: all ease-out .3s;
  131. animation: card-intro .8s ease-out;
  132. overflow: hidden;
  133. white-space: nowrap;
  134. }
  135.  
  136. @keyframes card-intro {
  137. from {
  138. box-shadow: 10px 10px 2px rgba(0, 0, 0, 0.03);
  139. width: 0;
  140. opacity: 0.6;
  141. }
  142. to {
  143. box-shadow: 10px 10px 8px rgba(0, 0, 0, 0.07);
  144. opacity: 1;
  145. }
  146. }
  147.  
  148. .card-icon {
  149. display: flex;
  150. flex-direction: column;
  151. place-items: center;
  152. margin-right: 3em;
  153. font-weight: bold;
  154. }
  155.  
  156. .card-text {
  157. flex: 1;
  158. }
  159.  
  160. b {
  161. color: rgb(49, 140, 252);
  162. }
  163.  
  164. .spinner, .spinner * { box-sizing: border-box; }
  165. .spinner {
  166. height: 40px;
  167. width: 40px;
  168. top: calc( -10px * 2 / 3);
  169. margin-left: calc(10px / 3);
  170. margin-bottom: calc(10px / 3);
  171. }
  172. .spinner .sq {
  173. height: 10px;
  174. width: 10px;
  175. top: calc( -10px * 2 / 3);
  176. margin-right: calc(10px / 3);
  177. margin-top: calc(10px / 3);
  178. background: rgb(49, 140, 252);
  179. float: left;
  180. position: relative;
  181. opacity: 0;
  182. animation: spinner 6s infinite;
  183. }
  184. ${Array(9).fill().map((_, i) => `.spinner .sq:nth-child(${i+1}) { animation-delay: calc(300ms * ${8-i}); }`).join("\n")}
  185. .spinner .clear { clear: both; }
  186. @keyframes spinner {
  187. 0% { opacity: 0; }
  188. 5% { opacity: 1; top: 0; }
  189. 50.9% { opacity: 1; top: 0; }
  190. 55.9% { opacity: 0; top: inherit; }
  191. }
  192.  
  193. @media print {
  194. @page {
  195. margin: 0;
  196. }
  197. button {
  198. display: none;
  199. }
  200. svg {
  201. width: 21cm;
  202. height: 29.7cm;
  203. }
  204. }
  205.  
  206. .btn-group {
  207. position: fixed;
  208. left: 32px;
  209. top: 24px;
  210. }
  211. button {
  212. font-size: 1.4em;
  213. font-weight: bold;
  214. box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.1);
  215. padding: .4em ${ ZH ? 1.5 : 0.8 }em;
  216. margin-right: 1.2em;
  217. background-color: rgb(255, 255, 255);
  218. color: rgb(49, 63, 78);
  219. border-radius: 4px;
  220. cursor: pointer;
  221. outline: 1.5px solid rgb(49, 63, 78);
  222. border: none;
  223. transition: all ease-out .2s;
  224. }
  225. button:hover {
  226. background-color: rgb(235, 235, 235);
  227. }
  228. button:active {
  229. outline-color: black;
  230. transform: scale(0.97);
  231. }
  232.  
  233. .hint {
  234. margin: 1em 0;
  235. color: #a7b2be;
  236. }
  237. </style>
  238. </head>
  239. <body>
  240. <div class="card">
  241. <div class="card-icon">
  242. <div class="spinner">
  243. <div class="sq"></div>
  244. <div class="sq"></div>
  245. <div class="sq"></div>
  246. <div class="sq clear"></div>
  247. <div class="sq"></div>
  248. <div class="sq"></div>
  249. <div class="sq clear"></div>
  250. <div class="sq"></div>
  251. <div class="sq"></div>
  252. </div>
  253. <div class="card-icon-text">${ ZH ? "下载中" : "Downloading" }</div>
  254. </div>
  255. <div class="card-text">
  256. ${ ZH ? `已下载 <b id="download-status">0</b> 页,共 <b>${length}</b> 页`
  257. : `<b id="download-status">0</b> page(s) loaded, <b>${length}</b> total` }
  258. </div>
  259. </div>
  260. <span class="hint">${ ZH ? "点击左上方的 <b>打印</b> 按钮,选择 <b>另存为PDF</b> 即可下载乐谱。" : "To download the sheet, click <b>PRINT</b> button on the top left then select <b>Export PDF</b>." }</span>
  261. </body>`)
  262. let data = Array(length).fill(""), cnt = 0
  263.  
  264. async function getData() {
  265. return Promise.all(
  266. data.filter(e => e.length == 0).map((_, i) => new Promise(async (res, rej) => {
  267. let url = await fetch(`https://musescore.com/api/jmuse?id=${id}&type=img&v2=1&index=${i}`, {
  268. headers: { authorization: "8c022bdef45341074ce876ae57a48f64b86cdcf5" }
  269. }).then(e => e.json()).then(e => e.info.url).catch(rej)
  270. data[i] = await fetch(url).then(e => e.text()).catch(rej)
  271. console.log(i, data[i])
  272. Window.document.getElementById("download-status").innerText = ++cnt
  273. res()
  274. }))
  275. ).catch((err) => console.error(err))
  276. }
  277. await getData()
  278.  
  279. setTimeout(async () => {
  280. if(cnt != length) {
  281. Window.document.getElementsByClassName("card-text")[0].innerText = ZH ? "下载失败,请稍后再试。" : "Download failed, try again later."
  282. return
  283. }
  284. setTimeout(() => {
  285. Window.document.body.style.background = "white"
  286. Window.document.body.innerHTML = data.join("") + `<div class="btn-group"><button onclick="print()">${ ZH ? "打印" : "PRINT" }</button>`
  287. const svgs = [...Window.document.getElementsByTagName("svg")]
  288. svgs.forEach((e) => e.setAttribute("viewBox", `0 0 ${e.width.baseVal.value} ${e.height.baseVal.value}`))
  289. Window.scrollTo(0, 0)
  290. }, 400)
  291. }, 800)
  292. }
  293.  
  294. const btns = [...document.getElementsByTagName("button")]
  295. btns.filter(el => {
  296. const val = el.attributes.getNamedItem("name")?.value
  297. return val == "download" || val == "print"
  298. }).forEach(el => {
  299. const type = el.attributes.getNamedItem("name").value
  300. const fakeEl = el.cloneNode(true)
  301. fakeEl.style.border = "2px #0dbc79 solid"
  302. if(type == "download") fakeEl.style.background = "#0dbc79"
  303. fakeEl.onclick = download
  304. el.parentNode.replaceChild(fakeEl, el)
  305. })
  306. }, 500)