This commit is contained in:
Xmader 2020-11-24 17:44:52 -05:00
parent 3b9dd171c7
commit adb7f5075d
No known key found for this signature in database
GPG Key ID: A20B97FB9EB730E4
3 changed files with 265 additions and 388 deletions

649
dist/main.js vendored
View File

@ -5,7 +5,7 @@
// @supportURL https://github.com/Xmader/musescore-downloader/issues
// @updateURL https://msdl.librescore.org/install.user.js
// @downloadURL https://msdl.librescore.org/install.user.js
// @version 0.15.20
// @version 0.16.0
// @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱
// @author Xmader
// @match https://musescore.com/*/*
@ -22,10 +22,11 @@
window.eval('(' + function () {
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
@ -36,185 +37,27 @@
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var FileSaver = createCommonjsModule(function (module, exports) {
(function (global, factory) {
{
factory();
}
})(commonjsGlobal, function () {
/*
* FileSaver.js
* A saveAs() FileSaver implementation.
*
* By Eli Grey, http://eligrey.com
*
* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
* source : http://purl.eligrey.com/github/FileSaver.js
*/
// The one and only way of getting global scope in all environments
// https://stackoverflow.com/q/3277182/1008999
var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof commonjsGlobal === 'object' && commonjsGlobal.global === commonjsGlobal ? commonjsGlobal : void 0;
function bom(blob, opts) {
if (typeof opts === 'undefined') opts = {
autoBom: false
};else if (typeof opts !== 'object') {
console.warn('Deprecated: Expected third argument to be a object');
opts = {
autoBom: !opts
};
} // prepend BOM for UTF-8 XML and text/* types (including HTML)
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob([String.fromCharCode(0xFEFF), blob], {
type: blob.type
});
}
return blob;
}
function download(url, name, opts) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = function () {
saveAs(xhr.response, name, opts);
};
xhr.onerror = function () {
console.error('could not download file');
};
xhr.send();
}
function corsEnabled(url) {
var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker
xhr.open('HEAD', url, false);
try {
xhr.send();
} catch (e) {}
return xhr.status >= 200 && xhr.status <= 299;
} // `a.click()` doesn't work for all browsers (#465)
var FileSaver_min = createCommonjsModule(function (module, exports) {
(function(a,b){b();})(commonjsGlobal,function(){function b(a,b){return "undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c);},d.onerror=function(){console.error("could not download file");},d.send();}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send();}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"));}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b);}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof commonjsGlobal&&commonjsGlobal.global===commonjsGlobal?commonjsGlobal:void 0,a=f.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href);},4E4),setTimeout(function(){e(j);},0));}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else {var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i);});}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null;},k.readAsDataURL(b);}else {var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m);},4E4);}});f.saveAs=g.saveAs=g,(module.exports=g);});
function click(node) {
try {
node.dispatchEvent(new MouseEvent('click'));
} catch (e) {
var evt = document.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
node.dispatchEvent(evt);
}
}
var saveAs = _global.saveAs || ( // probably in some web worker
typeof window !== 'object' || window !== _global ? function saveAs() {}
/* noop */
// Use download attribute first if possible (#193 Lumia mobile)
: 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) {
var URL = _global.URL || _global.webkitURL;
var a = document.createElement('a');
name = name || blob.name || 'download';
a.download = name;
a.rel = 'noopener'; // tabnabbing
// TODO: detect chrome extensions & packaged apps
// a.target = '_blank'
if (typeof blob === 'string') {
// Support regular links
a.href = blob;
if (a.origin !== location.origin) {
corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank');
} else {
click(a);
}
} else {
// Support blobs
a.href = URL.createObjectURL(blob);
setTimeout(function () {
URL.revokeObjectURL(a.href);
}, 4E4); // 40s
setTimeout(function () {
click(a);
}, 0);
}
} // Use msSaveOrOpenBlob as a second approach
: 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) {
name = name || blob.name || 'download';
if (typeof blob === 'string') {
if (corsEnabled(blob)) {
download(blob, name, opts);
} else {
var a = document.createElement('a');
a.href = blob;
a.target = '_blank';
setTimeout(function () {
click(a);
});
}
} else {
navigator.msSaveOrOpenBlob(bom(blob, opts), name);
}
} // Fallback to using FileReader and a popup
: function saveAs(blob, name, opts, popup) {
// Open a popup immediately do go around popup blocker
// Mostly only available on user interaction and the fileReader is async so...
popup = popup || open('', '_blank');
if (popup) {
popup.document.title = popup.document.body.innerText = 'downloading...';
}
if (typeof blob === 'string') return download(blob, name, opts);
var force = blob.type === 'application/octet-stream';
var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') {
// Safari doesn't allow downloading of blob URLs
var reader = new FileReader();
reader.onloadend = function () {
var url = reader.result;
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
if (popup) popup.location.href = url;else location = url;
popup = null; // reverse-tabnabbing #460
};
reader.readAsDataURL(blob);
} else {
var URL = _global.URL || _global.webkitURL;
var url = URL.createObjectURL(blob);
if (popup) popup.location = url;else location.href = url;
popup = null; // reverse-tabnabbing #460
setTimeout(function () {
URL.revokeObjectURL(url);
}, 4E4); // 40s
}
});
_global.saveAs = saveAs.saveAs = saveAs;
{
module.exports = saveAs;
}
});
});
const saveAs = FileSaver.saveAs;
// Only Node.JS has a process variable that is of [[Class]] process
var detectNode = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
const escapeFilename = (s) => {
return s.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_');
};
const getFetch = () => {
if (!detectNode) {
return fetch;
}
else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return require('node-fetch');
}
};
const fetchData = (url, init) => __awaiter(void 0, void 0, void 0, function* () {
const r = yield fetch(url, init);
const data = yield r.arrayBuffer();
@ -236,6 +79,8 @@
});
});
const getSandboxWindow = () => {
if (typeof document === 'undefined')
return {};
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.append(iframe);
@ -273,10 +118,11 @@
(function () {
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
@ -10869,7 +10715,7 @@
this.write_in_progress = true;
var self = this;
nextTick(function() {
process.nextTick(function() {
self.write_in_progress = false;
var res = self._write(flush, input, in_off, in_len, out, out_off, out_len);
self.callback(res[0], res[1]);
@ -11413,7 +11259,7 @@
}
});
} else {
nextTick(callback);
process.nextTick(callback);
}
};
@ -11437,7 +11283,7 @@
if (ws.ended) {
if (callback)
nextTick(callback);
process.nextTick(callback);
} else if (ws.ending) {
if (callback)
this.once('end', callback);
@ -11454,7 +11300,7 @@
Zlib$1.prototype.close = function(callback) {
if (callback)
nextTick(callback);
process.nextTick(callback);
if (this._closed)
return;
@ -11464,7 +11310,7 @@
this._binding.close();
var self = this;
nextTick(function() {
process.nextTick(function() {
self.emit('close');
});
};
@ -23198,13 +23044,17 @@
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
node.dispatchEvent(evt);
}
}
} // Detect WebView inside a native macOS app by ruling out all browsers
// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos
var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent);
var saveAs = _global.saveAs || ( // probably in some web worker
typeof window !== 'object' || window !== _global ? function saveAs() {}
/* noop */
// Use download attribute first if possible (#193 Lumia mobile)
: 'download' in HTMLAnchorElement.prototype ? function saveAs(blob, name, opts) {
// Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
: 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) {
var URL = _global.URL || _global.webkitURL;
var a = document.createElement('a');
name = name || blob.name || 'download';
@ -23268,7 +23118,7 @@
var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
if ((isChromeIOS || force && isSafari) && typeof FileReader === 'object') {
if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') {
// Safari doesn't allow downloading of blob URLs
var reader = new FileReader();
@ -26346,94 +26196,6 @@ Please pipe the document into a Node stream.\
}
}
/* eslint-disable @typescript-eslint/no-unsafe-return */
// run at document-start
const ugappJsStore = (() => {
try {
const l = document.body.children;
const el = [...l].find(e => Object.keys(e.dataset).length > 0);
const json = Object.values(el.dataset)[0];
return JSON.parse(json);
}
catch (err) {
console$1.error(err);
return null;
}
})();
const IPNS_KEY = 'QmSdXtvzC8v8iTTZuj5cVmiugnzbR1QATYRcGix4bBsioP';
const RADIX = 20;
const scoreinfo = {
get playerdata() {
// @ts-ignore
return ugappJsStore.store.page.data.score;
},
get id() {
try {
return this.playerdata.id;
}
catch (_a) {
const el = document.querySelector("meta[property='al:ios:url']");
const m = el.content.match(/(\d+)$/);
return +m[1];
}
},
get idLastDigit() {
return (+this.id) % RADIX;
},
get title() {
try {
return this.playerdata.title;
}
catch (_a) {
const el = document.querySelector("meta[property='og:title']");
return el.content;
}
},
get fileName() {
return this.title.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_');
},
get pageCount() {
try {
return this.playerdata.pages_count;
}
catch (_a) {
return document.querySelectorAll('.gXB83').length;
}
},
get baseUrl() {
let thumbnailUrl;
try {
thumbnailUrl = this.playerdata.thumbnails.original;
}
catch (_a) {
const el = document.querySelector("meta[property='og:image']");
thumbnailUrl = el.content;
}
const { origin, pathname } = new URL(thumbnailUrl);
// remove the last part
return origin + pathname.split('/').slice(0, -1).join('/') + '/';
},
get msczIpfsRef() {
return `/ipns/${IPNS_KEY}/${this.idLastDigit}/${this.id}.mscz`;
},
get msczCidUrl() {
return `https://ipfs.infura.io:5001/api/v0/block/stat?arg=${this.msczIpfsRef}`;
},
get sheetImgType() {
try {
const imgE = document.querySelector('img[src*=score_]');
const { pathname } = new URL(imgE.src);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const imgtype = pathname.match(/\.(\w+)$/)[1];
return imgtype;
}
catch (_) {
// return null
return 'svg';
}
},
};
/* eslint-disable no-extend-native */
/* eslint-disable @typescript-eslint/ban-types */
/**
@ -26587,8 +26349,8 @@ Please pipe the document into a Node stream.\
midi: magicHookConstr('midi'),
mp3: magicHookConstr('mp3'),
};
const getApiUrl = (type, index) => {
return `/api/jmuse?id=${scoreinfo.id}&type=${type}&index=${index}&v2=1`;
const getApiUrl = (id, type, index) => {
return `/api/jmuse?id=${id}&type=${type}&index=${index}&v2=1`;
};
const getApiAuth = (type, index) => __awaiter(void 0, void 0, void 0, function* () {
var _a;
@ -26617,8 +26379,8 @@ Please pipe the document into a Node stream.\
}
return magic;
});
const getFileUrl = (type, index = 0) => __awaiter(void 0, void 0, void 0, function* () {
const url = getApiUrl(type, index);
const getFileUrl = (id, type, index = 0) => __awaiter(void 0, void 0, void 0, function* () {
const url = getApiUrl(id, type, index);
const auth = yield getApiAuth(type);
const r = yield fetch(url, {
headers: {
@ -26632,7 +26394,7 @@ Please pipe the document into a Node stream.\
let pdfBlob;
const _downloadPDF = (imgURLs, imgType, name = '') => __awaiter(void 0, void 0, void 0, function* () {
if (pdfBlob) {
return saveAs(pdfBlob, `${name}.pdf`);
return FileSaver_min.saveAs(pdfBlob, `${name}.pdf`);
}
const cachedImg = document.querySelector('img[src*=score_]');
const { naturalWidth: width, naturalHeight: height } = cachedImg;
@ -26640,87 +26402,57 @@ Please pipe the document into a Node stream.\
const pdfArrayBuffer = yield worker.generatePDF(imgURLs, imgType, width, height);
worker.terminate();
pdfBlob = new Blob([pdfArrayBuffer]);
saveAs(pdfBlob, `${name}.pdf`);
FileSaver_min.saveAs(pdfBlob, `${name}.pdf`);
});
const downloadPDF = () => __awaiter(void 0, void 0, void 0, function* () {
const imgType = scoreinfo.sheetImgType;
const pageCount = scoreinfo.pageCount;
const downloadPDF = (scoreinfo, sheet) => __awaiter(void 0, void 0, void 0, function* () {
const imgType = sheet.imgType;
const pageCount = sheet.pageCount;
const rs = Array.from({ length: pageCount }).map((_, i) => {
if (i === 0) { // The url to the first page is static. We don't need to use API to obtain it.
return scoreinfo.baseUrl + `score_${i}.${imgType}`;
return sheet.thumbnailUrl;
}
else { // obtain image urls using the API
return getFileUrl('img', i);
return getFileUrl(scoreinfo.id, 'img', i);
}
});
const sheetImgURLs = yield Promise.all(rs);
return _downloadPDF(sheetImgURLs, imgType, scoreinfo.fileName);
});
let msczBufferP;
const fetchMscz = () => __awaiter(void 0, void 0, void 0, function* () {
const MSCZ_BUF_SYM = Symbol('msczBufferP');
const fetchMscz = (scoreinfo, _fetch = getFetch()) => __awaiter(void 0, void 0, void 0, function* () {
let msczBufferP = scoreinfo.store.get(MSCZ_BUF_SYM);
if (!msczBufferP) {
const url = scoreinfo.msczCidUrl;
msczBufferP = (() => __awaiter(void 0, void 0, void 0, function* () {
const r0 = yield fetch(url);
assertRes(r0);
const { Key } = yield r0.json();
const r = yield fetch(`https://ipfs.infura.io/ipfs/${Key}`);
const r0 = yield _fetch(url);
// ipfs-http-gateway specific error
// may read further error msg as json
if (r0.status !== 500) {
assertRes(r0);
}
const cidRes = yield r0.json();
const r = yield _fetch(scoreinfo.loadMsczUrl(cidRes));
assertRes(r);
const data = yield r.arrayBuffer();
return data;
}))();
scoreinfo.store.set(MSCZ_BUF_SYM, msczBufferP);
}
return msczBufferP;
});
const downloadMscz = () => __awaiter(void 0, void 0, void 0, function* () {
const data = new Blob([yield fetchMscz()]);
const downloadMscz = (scoreinfo, saveAs) => __awaiter(void 0, void 0, void 0, function* () {
const data = new Blob([yield fetchMscz(scoreinfo)]);
const filename = scoreinfo.fileName;
saveAs(data, `${filename}.mscz`);
});
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.10/webmscore.js';
// fonts for Chinese characters (CN) and Korean hangul (KR)
// JP characters are included in the CN font
const FONT_URLS = ['CN', 'KR'].map(l => `https://cdn.jsdelivr.net/npm/@librescore/fonts/SourceHanSans${l}-Regular.woff2`);
const SF3_URL = 'https://cdn.jsdelivr.net/npm/@librescore/sf3/FluidR3Mono_GM.sf3';
const SOUND_FONT_LOADED = Symbol('SoundFont loaded');
const initMscore = (w) => __awaiter(void 0, void 0, void 0, function* () {
if (!w['WebMscore']) {
// init webmscore (https://github.com/LibreScore/webmscore)
const script = w.document.createElement('script');
script.src = WEBMSCORE_URL;
w.document.body.append(script);
yield new Promise(resolve => { script.onload = resolve; });
}
});
let fonts;
const initFonts = () => {
// load CJK fonts
// CJK (East Asian) characters will be rendered as "tofu" if there is no font
if (!fonts) {
fonts = Promise.all(FONT_URLS.map(url => fetchData(url)));
}
};
const loadSoundFont = (score) => {
if (!score[SOUND_FONT_LOADED]) {
const loadPromise = (() => __awaiter(void 0, void 0, void 0, function* () {
yield score.setSoundFont(yield fetchData(SF3_URL));
}))();
score[SOUND_FONT_LOADED] = loadPromise;
}
return score[SOUND_FONT_LOADED];
};
const loadMscore = (w) => __awaiter(void 0, void 0, void 0, function* () {
initFonts();
yield initMscore(w);
const WebMscore = w['WebMscore'];
// parse mscz data
const data = new Uint8Array(new Uint8Array(yield fetchMscz()));
const score = yield WebMscore.load('mscz', data, yield fonts);
yield score.generateExcerpts();
return score;
});
/**
* type checking only so no missing keys
*/
function createLocale(obj) {
return Object.freeze(obj);
}
var en = createLocale({
'PROCESSING'() {
@ -26776,20 +26508,23 @@ Please pipe the document into a Node stream.\
},
});
/**
* type checking only so no missing keys
*/
function createLocale(obj) {
return Object.freeze(obj);
}
const locales = ((l) => Object.freeze(l))({
en,
es,
});
// detect browser language
const lang = (() => {
let userLangs;
if (!detectNode) {
userLangs = navigator.languages;
}
else {
const env = process.env;
const l = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE || '';
userLangs = [l.slice(0, 2)];
}
const names = Object.keys(locales);
const _lang = navigator.languages.find(l => {
const _lang = userLangs.find(l => {
// find the first occurrence of valid languages
return names.includes(l);
});
@ -26801,6 +26536,105 @@ Please pipe the document into a Node stream.\
return locale[key];
}
/* eslint-disable @typescript-eslint/no-var-requires */
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.10/webmscore.js';
// fonts for Chinese characters (CN) and Korean hangul (KR)
// JP characters are included in the CN font
const FONT_URLS = ['CN', 'KR'].map(l => `https://cdn.jsdelivr.net/npm/@librescore/fonts/SourceHanSans${l}-Regular.woff2`);
const SF3_URL = 'https://cdn.jsdelivr.net/npm/@librescore/sf3/FluidR3Mono_GM.sf3';
const SOUND_FONT_LOADED = Symbol('SoundFont loaded');
const initMscore = (w) => __awaiter(void 0, void 0, void 0, function* () {
if (!detectNode) { // attached to a page
if (!w['WebMscore']) {
// init webmscore (https://github.com/LibreScore/webmscore)
const script = w.document.createElement('script');
script.src = WEBMSCORE_URL;
w.document.body.append(script);
yield new Promise(resolve => { script.onload = resolve; });
}
return w['WebMscore'];
}
else { // nodejs
return require('webmscore').default;
}
});
let fonts;
const initFonts = () => {
// load CJK fonts
// CJK (East Asian) characters will be rendered as "tofu" if there is no font
if (!fonts) {
if (detectNode) {
// module.exports.CN = ..., module.exports.KR = ...
const FONTS = Object.values(require('@librescore/fonts'));
const fs = require('fs');
fonts = Promise.all(FONTS.map((path) => fs.promises.readFile(path)));
}
else {
fonts = Promise.all(FONT_URLS.map(url => fetchData(url)));
}
}
};
const loadSoundFont = (score) => {
if (!score[SOUND_FONT_LOADED]) {
const loadPromise = (() => __awaiter(void 0, void 0, void 0, function* () {
let data;
if (detectNode) {
// module.exports.FluidR3Mono = ...
const SF3 = Object.values(require('@librescore/sf3'))[0];
const fs = require('fs');
data = yield fs.promises.readFile(SF3);
}
else {
data = yield fetchData(SF3_URL);
}
yield score.setSoundFont(data);
}))();
score[SOUND_FONT_LOADED] = loadPromise;
}
return score[SOUND_FONT_LOADED];
};
const loadMscore = (scoreinfo, w) => __awaiter(void 0, void 0, void 0, function* () {
initFonts();
const WebMscore = yield initMscore(w);
// parse mscz data
const data = new Uint8Array(new Uint8Array(yield fetchMscz(scoreinfo)));
const score = yield WebMscore.load('mscz', data, yield fonts);
yield score.generateExcerpts();
return score;
});
const INDV_DOWNLOADS = [
{
name: i18n('DOWNLOAD')('PDF'),
fileExt: 'pdf',
action: (score) => score.savePdf(),
},
{
name: i18n('DOWNLOAD')('MSCZ'),
fileExt: 'mscz',
action: (score) => score.saveMsc('mscz'),
},
{
name: i18n('DOWNLOAD')('MusicXML'),
fileExt: 'mxl',
action: (score) => score.saveMxl(),
},
{
name: i18n('DOWNLOAD')('MIDI'),
fileExt: 'mid',
action: (score) => score.saveMidi(true, true),
},
{
name: i18n('DOWNLOAD_AUDIO')('FLAC'),
fileExt: 'flac',
action: (score) => loadSoundFont(score).then(() => score.saveAudio('flac')),
},
{
name: i18n('DOWNLOAD_AUDIO')('OGG'),
fileExt: 'ogg',
action: (score) => loadSoundFont(score).then(() => score.saveAudio('ogg')),
},
];
var btnListCss = "div {\n flex-wrap: wrap;\n display: flex;\n align-items: center;\n font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif;\n position: absolute;\n z-index: 999;\n background: #f6f6f6;\n}\n\nbutton {\n width: 205px !important;\n height: 38px;\n\n color: #fff;\n background: #1f74bd;\n\n cursor: pointer;\n\n margin-bottom: 4px;\n margin-right: 4px;\n padding: 4px 12px;\n\n justify-content: start;\n align-self: center;\n\n font-size: 16px;\n border-radius: 2px;\n border: 0;\n\n display: inline-flex;\n position: relative;\n\n font-family: inherit;\n}\n\nsvg {\n display: inline-block;\n margin-right: 5px;\n width: 20px;\n height: 20px;\n margin-top: auto;\n margin-bottom: auto;\n}\n\nspan {\n margin-top: auto;\n margin-bottom: auto;\n}";
const getBtnContainer = () => {
@ -26957,7 +26791,7 @@ Please pipe the document into a Node stream.\
a.dispatchEvent(new MouseEvent('click'));
}), fallback, timeout);
};
BtnAction.mscoreWindow = (fn) => {
BtnAction.mscoreWindow = (scoreinfo, fn) => {
return (btnName, btn, setText) => __awaiter(this, void 0, void 0, function* () {
const _onclick = btn.onclick;
btn.onclick = null;
@ -26979,7 +26813,7 @@ Please pipe the document into a Node stream.\
setText(btnName);
btn.onclick = _onclick;
});
score = yield loadMscore(w);
score = yield loadMscore(scoreinfo, w);
fn(w, score, txt);
});
};
@ -27015,9 +26849,83 @@ Please pipe the document into a Node stream.\
};
})(BtnAction || (BtnAction = {}));
class ScoreInfo {
constructor() {
this.IPNS_KEY = 'QmSdXtvzC8v8iTTZuj5cVmiugnzbR1QATYRcGix4bBsioP';
this.RADIX = 20;
this.store = new Map();
}
get idLastDigit() {
return (+this.id) % this.RADIX;
}
get fileName() {
return escapeFilename(this.title);
}
get msczIpfsRef() {
return `/ipns/${this.IPNS_KEY}/${this.idLastDigit}/${this.id}.mscz`;
}
get msczCidUrl() {
return `https://ipfs.infura.io:5001/api/v0/block/stat?arg=${this.msczIpfsRef}`;
}
loadMsczUrl(cidRes) {
const cid = cidRes.Key;
if (!cid) {
// read further error msg
const err = cidRes.Message;
if (err.includes('no link named')) { // file not found
throw new Error('score not in dataset');
}
else {
throw new Error(err);
}
}
this.msczUrl = `https://ipfs.infura.io/ipfs/${cid}`;
return this.msczUrl;
}
}
class ScoreInfoInPage extends ScoreInfo {
constructor(document) {
super();
this.document = document;
}
get id() {
const el = this.document.querySelector("meta[property='al:ios:url']");
const m = el.content.match(/(\d+)$/);
return +m[1];
}
get title() {
const el = this.document.querySelector("meta[property='og:title']");
return el.content;
}
}
class SheetInfo {
get imgType() {
const thumbnail = this.thumbnailUrl;
const imgtype = thumbnail.match(/\.(\w+)$/)[1];
return imgtype;
}
}
class SheetInfoInPage extends SheetInfo {
constructor(document) {
super();
this.document = document;
}
get pageCount() {
return this.document.querySelectorAll('.gXB83').length;
}
get thumbnailUrl() {
// url to the image of the first page
const el = this.document.querySelector('link[as=image]');
const url = el.href;
return url.split('@')[0];
}
}
const { saveAs } = FileSaver_min;
const main = () => {
const btnList = new BtnList();
const filename = scoreinfo.fileName;
const scoreinfo = new ScoreInfoInPage(document);
const { fileName, id } = scoreinfo;
let indvPartBtn = null;
const fallback = () => {
// btns fallback to load from MSCZ file (`Individual Parts`)
@ -27025,33 +26933,33 @@ Please pipe the document into a Node stream.\
};
btnList.add({
name: i18n('DOWNLOAD')('MSCZ'),
action: BtnAction.process(downloadMscz),
action: BtnAction.process(() => downloadMscz(scoreinfo, saveAs)),
});
btnList.add({
name: i18n('DOWNLOAD')('PDF'),
action: BtnAction.process(downloadPDF, fallback, 3 * 60 * 1000 /* 3min */),
action: BtnAction.process(() => downloadPDF(scoreinfo, new SheetInfoInPage(document)), fallback, 3 * 60 * 1000 /* 3min */),
});
btnList.add({
name: i18n('DOWNLOAD')('MusicXML'),
action: BtnAction.mscoreWindow((w, score) => __awaiter(void 0, void 0, void 0, function* () {
action: BtnAction.mscoreWindow(scoreinfo, (w, score) => __awaiter(void 0, void 0, void 0, function* () {
const mxl = yield score.saveMxl();
const data = new Blob([mxl]);
saveAs(data, `${filename}.mxl`);
saveAs(data, `${fileName}.mxl`);
w.close();
})),
});
btnList.add({
name: i18n('DOWNLOAD')('MIDI'),
action: BtnAction.download(() => getFileUrl('midi'), fallback, 30 * 1000 /* 30s */),
action: BtnAction.download(() => getFileUrl(id, 'midi'), fallback, 30 * 1000 /* 30s */),
});
btnList.add({
name: i18n('DOWNLOAD')('MP3'),
action: BtnAction.download(() => getFileUrl('mp3'), fallback, 30 * 1000 /* 30s */),
action: BtnAction.download(() => getFileUrl(id, 'mp3'), fallback, 30 * 1000 /* 30s */),
});
indvPartBtn = btnList.add({
name: i18n('IND_PARTS')(),
tooltip: i18n('IND_PARTS_TOOLTIP')(),
action: BtnAction.mscoreWindow((w, score, txt) => __awaiter(void 0, void 0, void 0, function* () {
action: BtnAction.mscoreWindow(scoreinfo, (w, score, txt) => __awaiter(void 0, void 0, void 0, function* () {
const metadata = yield score.metadata();
console$1.log('score metadata loaded by webmscore', metadata);
// add the "full score" option as a "part"
@ -27060,38 +26968,7 @@ Please pipe the document into a Node stream.\
txt.remove();
const fieldset = w.document.createElement('fieldset');
w.document.body.append(fieldset);
const downloads = [
{
name: i18n('DOWNLOAD')('PDF'),
fileExt: 'pdf',
action: (score) => score.savePdf(),
},
{
name: i18n('DOWNLOAD')('MSCZ'),
fileExt: 'mscz',
action: (score) => score.saveMsc('mscz'),
},
{
name: i18n('DOWNLOAD')('MusicXML'),
fileExt: 'mxl',
action: (score) => score.saveMxl(),
},
{
name: i18n('DOWNLOAD')('MIDI'),
fileExt: 'mid',
action: (score) => score.saveMidi(true, true),
},
{
name: i18n('DOWNLOAD_AUDIO')('FLAC'),
fileExt: 'flac',
action: (score) => loadSoundFont(score).then(() => score.saveAudio('flac')),
},
{
name: i18n('DOWNLOAD_AUDIO')('OGG'),
fileExt: 'ogg',
action: (score) => loadSoundFont(score).then(() => score.saveAudio('ogg')),
},
];
const downloads = INDV_DOWNLOADS;
// part selection
const DEFAULT_PART = -1; // initially select "full score"
for (const excerpt of metadata.excerpts) {
@ -27130,7 +27007,7 @@ Please pipe the document into a Node stream.\
const checked = fieldset.querySelector('input:checked');
const partName = checked.alt;
const data = new Blob([yield d.action(score)]);
saveAs(data, `${filename} - ${partName}.${d.fileExt}`);
saveAs(data, `${fileName} - ${partName}.${d.fileExt}`);
// unlock button
initBtn();
});

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "musescore-downloader",
"version": "0.15.20",
"version": "0.16.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "musescore-downloader",
"version": "0.15.20",
"version": "0.16.0",
"description": "download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱",
"main": "dist/main.js",
"bin": "dist/cli.js",