Compare commits
76 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8e3dd94c12 | ||
|
b1a3456646 | ||
|
d269ae9be9 | ||
|
20298e5c0e | ||
|
49880d63da | ||
|
0c2106993d | ||
|
3ac3a51ad1 | ||
|
9c05dd5d3d | ||
|
ae08ff847a | ||
|
8cc089357f | ||
|
4e92c06791 | ||
|
5d1639dddf | ||
|
88fdfff054 | ||
|
893c64edda | ||
|
1bf3050513 | ||
|
ffbac084fb | ||
|
861af6d3f0 | ||
|
30514e9507 | ||
|
7a397e068f | ||
|
d97b3d86d8 | ||
|
bef85c5c3e | ||
|
b68ff38028 | ||
|
a571fed456 | ||
|
f9be984d17 | ||
|
9adba49de1 | ||
|
6d93907df0 | ||
|
c5d6d87b8c | ||
|
8475941d47 | ||
|
71def367e8 | ||
|
d1c9634331 | ||
|
49aef0ccf3 | ||
|
ec15d44811 | ||
|
4494e15540 | ||
|
6a6e2a5ea1 | ||
|
3260672412 | ||
|
004cb16fce | ||
|
ec1c1ea87c | ||
|
69e5bd0a78 | ||
|
06a91b1c2d | ||
|
41f5286d48 | ||
|
29a09c2596 | ||
|
f2a52dd514 | ||
|
1e3e2d7581 | ||
|
eda8342a3d | ||
|
e9ed0812b9 | ||
|
4bd5d55676 | ||
|
c571a49093 | ||
|
b5477a4059 | ||
|
d33c06c892 | ||
|
04884a137f | ||
|
263f72dc7a | ||
|
c46343b46c | ||
|
6202321a42 | ||
|
3c72b5a92f | ||
|
085c6a2d2a | ||
|
1eb0f35bde | ||
|
49fcb99160 | ||
|
bd943675d8 | ||
|
030d37ddc0 | ||
|
d014ade9ca | ||
|
b8181f421d | ||
|
463ea5d416 | ||
|
da5d53898a | ||
|
2b842e267f | ||
|
08294f564b | ||
|
46a7f50115 | ||
|
7bb3aaf7b1 | ||
|
dd30454b5a | ||
|
d99848c6fc | ||
|
c973d5d06f | ||
|
d919441966 | ||
|
86c15d55fe | ||
|
1faaf660c4 | ||
|
8250d80d4b | ||
|
d9d09c4e8f | ||
|
20704eba75 |
21 changed files with 345 additions and 121 deletions
|
@ -20,6 +20,8 @@
|
|||
"no-useless-constructor": "off",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"no-dupe-class-members": "off",
|
||||
"no-void": "off",
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-dupe-class-members": "error",
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"@typescript-eslint/member-delimiter-style": "warn",
|
||||
|
|
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
|
@ -53,6 +53,15 @@ jobs:
|
|||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # 0301...
|
||||
|
||||
- name: NPM Publish msdl
|
||||
run: |
|
||||
cd ./src/msdl
|
||||
sed -i "s/%VERSION%/$VERSION/" package.json
|
||||
npm publish --tag $NPM_TAG
|
||||
cd -
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish Firefox Extension
|
||||
id: web-ext-build
|
||||
uses: kewisch/action-web-ext@v1
|
||||
|
@ -69,6 +78,7 @@ jobs:
|
|||
cp dist/main.js $ARTIFACTS_DIR/musescore-downloader.user.js
|
||||
cp dist/ext.zip $ARTIFACTS_DIR/musescore-downloader.webextension.zip
|
||||
wget -q $CHROME_EXT_URL -P $ARTIFACTS_DIR/
|
||||
wget -q https://github.com/Xmader/musescore-downloader/archive/$REF.tar.gz -O $ARTIFACTS_DIR/source.tar.gz
|
||||
- run: bash ./.github/workflows/get-signed-ext.sh
|
||||
env:
|
||||
EXT_ID: musescore-downloader
|
||||
|
@ -88,6 +98,7 @@ jobs:
|
|||
IPFS_HASH: ${{ steps.ipfs.outputs.hash }}
|
||||
run: |
|
||||
cd $ARTIFACTS_DIR
|
||||
rm *.tar.gz
|
||||
|
||||
files=$(ls .)
|
||||
assets=()
|
||||
|
|
147
dist/main.js
vendored
147
dist/main.js
vendored
|
@ -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.23.1
|
||||
// @version 0.24.1
|
||||
// @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/*/*
|
||||
|
@ -65,7 +65,7 @@
|
|||
const loaderOutro = '})()'.repeat(stackN)
|
||||
const mockUrl = "https://c.amazon-adsystem.com/aax2/apstag.js"
|
||||
|
||||
setTimeout(`${loaderIntro}const d=new Image();window['${id}'](d);delete window['${id}'];document.body.prepend(d)${loaderOutro}//# sourceURL=${mockUrl}`)
|
||||
Function(`${loaderIntro}const d=new Image();window['${id}'](d);delete window['${id}'];document.body.prepend(d)${loaderOutro}//# sourceURL=${mockUrl}`)()
|
||||
}).then(d => {
|
||||
d.style.display = 'none';
|
||||
d.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||
|
@ -331,6 +331,7 @@
|
|||
typeof GM[requiredMethod] !== 'undefined';
|
||||
};
|
||||
|
||||
const DISCORD_URL = 'https://discord.gg/gSsTUvJmD8';
|
||||
const escapeFilename = (s) => {
|
||||
return s.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_');
|
||||
};
|
||||
|
@ -26439,7 +26440,7 @@ Please pipe the document into a Node stream.\
|
|||
});
|
||||
|
||||
/// <reference lib="webworker" />
|
||||
const getDataURL = (blob) => {
|
||||
const readData = (blob, type) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
|
@ -26447,19 +26448,21 @@ Please pipe the document into a Node stream.\
|
|||
resolve(result);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
if (type === 'dataUrl') {
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
else {
|
||||
reader.readAsText(blob);
|
||||
}
|
||||
});
|
||||
};
|
||||
const fetchDataURL = (imgUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const r = yield fetch(imgUrl);
|
||||
const blob = yield r.blob();
|
||||
return getDataURL(blob);
|
||||
const fetchBlob = (imgUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const r = yield fetch(imgUrl, {
|
||||
cache: 'no-cache',
|
||||
});
|
||||
return r.blob();
|
||||
});
|
||||
const fetchText = (imgUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const r = yield fetch(imgUrl);
|
||||
return r.text();
|
||||
});
|
||||
const generatePDF = (imgURLs, imgType, width, height) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const generatePDF = (imgBlobs, imgType, width, height) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
// @ts-ignore
|
||||
const pdf = new PDFDocument({
|
||||
// compress: true,
|
||||
|
@ -26469,7 +26472,7 @@ Please pipe the document into a Node stream.\
|
|||
layout: 'portrait',
|
||||
});
|
||||
if (imgType === 'png') {
|
||||
const imgDataUrlList = yield Promise.all(imgURLs.map(fetchDataURL));
|
||||
const imgDataUrlList = yield Promise.all(imgBlobs.map(b => readData(b, 'dataUrl')));
|
||||
imgDataUrlList.forEach((data) => {
|
||||
pdf.addPage();
|
||||
pdf.image(data, {
|
||||
|
@ -26479,7 +26482,7 @@ Please pipe the document into a Node stream.\
|
|||
});
|
||||
}
|
||||
else { // imgType == "svg"
|
||||
const svgList = yield Promise.all(imgURLs.map(fetchText));
|
||||
const svgList = yield Promise.all(imgBlobs.map(b => readData(b, 'text')));
|
||||
svgList.forEach((svg) => {
|
||||
pdf.addPage();
|
||||
source(pdf, svg, 0, 0, {
|
||||
|
@ -26492,8 +26495,9 @@ Please pipe the document into a Node stream.\
|
|||
return buf.buffer;
|
||||
});
|
||||
onmessage = (e) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const [imgURLs, imgType, width, height,] = e.data;
|
||||
const pdfBuf = yield generatePDF(imgURLs, imgType, width, height);
|
||||
const [imgUrls, imgType, width, height,] = e.data;
|
||||
const imgBlobs = yield Promise.all(imgUrls.map(url => fetchBlob(url)));
|
||||
const pdfBuf = yield generatePDF(imgBlobs, imgType, width, height);
|
||||
postMessage(pdfBuf, [pdfBuf]);
|
||||
});
|
||||
|
||||
|
@ -26571,7 +26575,7 @@ Please pipe the document into a Node stream.\
|
|||
}
|
||||
|
||||
/* eslint-disable no-extend-native */
|
||||
const TYPE_REG = /id=(\d+)&type=(img|mp3|midi)/;
|
||||
const TYPE_REG = /type=(img|mp3|midi)/;
|
||||
/**
|
||||
* I know this is super hacky.
|
||||
*/
|
||||
|
@ -26592,8 +26596,9 @@ Please pipe the document into a Node stream.\
|
|||
const token = (_a = init === null || init === void 0 ? void 0 : init.headers) === null || _a === void 0 ? void 0 : _a.Authorization;
|
||||
if (typeof url === 'string' && token) {
|
||||
const m = url.match(TYPE_REG);
|
||||
console$1.debug(url, token, m);
|
||||
if (m) {
|
||||
const type = m[2];
|
||||
const type = m[1];
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(_b = l[type]) === null || _b === void 0 ? void 0 : _b.call(l, token);
|
||||
}
|
||||
|
@ -26633,7 +26638,7 @@ Please pipe the document into a Node stream.\
|
|||
// force to retrieve the MAGIC
|
||||
switch (type) {
|
||||
case 'midi': {
|
||||
const el = document.querySelectorAll('.SD7H- > button')[3];
|
||||
const el = document.querySelector('button[hasaccess]');
|
||||
el.click();
|
||||
break;
|
||||
}
|
||||
|
@ -26731,7 +26736,7 @@ Please pipe the document into a Node stream.\
|
|||
// read further error msg
|
||||
const err = cidRes.Message;
|
||||
if (err.includes('no link named')) { // file not found
|
||||
throw new Error('score not in dataset');
|
||||
throw new Error('Score not in dataset');
|
||||
}
|
||||
else {
|
||||
throw new Error(err);
|
||||
|
@ -26918,12 +26923,22 @@ Please pipe the document into a Node stream.\
|
|||
return locale[key];
|
||||
}
|
||||
|
||||
var dependencies = {
|
||||
"@librescore/fonts": "^0.4.0",
|
||||
"@librescore/sf3": "^0.3.0",
|
||||
"detect-node": "^2.0.4",
|
||||
inquirer: "^7.3.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
ora: "^5.1.0",
|
||||
webmscore: "^0.18.0"
|
||||
};
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.12/webmscore.js';
|
||||
const WEBMSCORE_URL = `https://cdn.jsdelivr.net/npm/webmscore@${dependencies.webmscore}/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 FONT_URLS = ['CN', 'KR'].map(l => `https://cdn.jsdelivr.net/npm/@librescore/fonts@${dependencies['@librescore/fonts']}/SourceHanSans${l}.min.woff2`);
|
||||
const SF3_URL = `https://cdn.jsdelivr.net/npm/@librescore/sf3@${dependencies['@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
|
||||
|
@ -27022,7 +27037,8 @@ Please pipe the document into a Node stream.\
|
|||
},
|
||||
];
|
||||
|
||||
const _getLink = (scorepack) => {
|
||||
const _getLink = (indexingInfo) => {
|
||||
const { scorepack } = JSON.parse(indexingInfo);
|
||||
return `https://librescore.org/score/${scorepack}`;
|
||||
};
|
||||
const getLibreScoreLink = (scoreinfo, _fetch = getFetch()) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
|
@ -27041,7 +27057,7 @@ Please pipe the document into a Node stream.\
|
|||
return _getLink(res);
|
||||
});
|
||||
|
||||
var btnListCss = "div {\n width: 422px;\n right: 0;\n margin: 0 18px 18px 0;\n\n text-align: center;\n align-items: center;\n font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif;\n position: absolute;\n z-index: 9999;\n background: #f6f6f6;\n min-width: 230px;\n}\n\n@media screen and (max-width: 950px) {\n div {\n width: auto !important;\n }\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}";
|
||||
var btnListCss = "div {\n width: 422px;\n right: 0;\n margin: 0 18px 18px 0;\n\n text-align: center;\n align-items: center;\n font-family: 'Inter', 'Helvetica neue', Helvetica, sans-serif;\n position: absolute;\n z-index: 9999;\n background: #f6f6f6;\n min-width: 230px;\n\n /* pass the scroll event through the btns background */\n pointer-events: none;\n}\n\n@media screen and (max-width: 950px) {\n div {\n width: auto !important;\n }\n}\n\nbutton {\n width: 178px !important;\n min-width: 178px;\n height: 40px;\n\n color: #fff;\n background: #2e68c0;\n\n cursor: pointer;\n pointer-events: auto;\n\n margin-bottom: 8px;\n margin-right: 8px;\n padding: 4px 12px;\n\n justify-content: start;\n align-self: center;\n\n font-size: 16px;\n border-radius: 6px;\n border: 0;\n\n display: inline-flex;\n position: relative;\n\n font-family: inherit;\n}\n\n/* fix `View in LibreScore` button text overflow */\nbutton:last-of-type {\n width: unset !important;\n}\n\nbutton:hover {\n background: #1a4f9f;\n}\n\n/* light theme btn */\nbutton.light {\n color: #2e68c0;\n background: #e1effe;\n}\n\nbutton.light:hover {\n background: #c3ddfd;\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}";
|
||||
|
||||
var ICON;
|
||||
(function (ICON) {
|
||||
|
@ -27050,26 +27066,28 @@ Please pipe the document into a Node stream.\
|
|||
})(ICON || (ICON = {}));
|
||||
const getBtnContainer = () => {
|
||||
var _a;
|
||||
const els = [...document.querySelectorAll('*')].reverse();
|
||||
const els = [...document.querySelectorAll('span')];
|
||||
const el = els.find(b => {
|
||||
var _a;
|
||||
const text = ((_a = b === null || b === void 0 ? void 0 : b.textContent) === null || _a === void 0 ? void 0 : _a.replace(/\s/g, '')) || '';
|
||||
return text.includes('Download') || text.includes('Print');
|
||||
});
|
||||
const btnParent = (_a = el === null || el === void 0 ? void 0 : el.parentElement) === null || _a === void 0 ? void 0 : _a.parentElement;
|
||||
if (!btnParent)
|
||||
if (!btnParent || !(btnParent instanceof HTMLDivElement))
|
||||
throw new Error('btn parent not found');
|
||||
return btnParent;
|
||||
};
|
||||
const buildDownloadBtn = (icon) => {
|
||||
const buildDownloadBtn = (icon, lightTheme = false) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
if (lightTheme)
|
||||
btn.className = 'light';
|
||||
// build icon svg element
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 24 24');
|
||||
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
svgPath.setAttribute('d', icon);
|
||||
svgPath.setAttribute('fill', '#fff');
|
||||
svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff');
|
||||
svg.append(svgPath);
|
||||
const textNode = document.createElement('span');
|
||||
btn.append(svg, textNode);
|
||||
|
@ -27088,6 +27106,22 @@ Please pipe the document into a Node stream.\
|
|||
return getScrollParent(node.parentNode);
|
||||
}
|
||||
}
|
||||
function onPageRendered(getEl) {
|
||||
return new Promise((resolve) => {
|
||||
var _a;
|
||||
const observer = new MutationObserver(() => {
|
||||
try {
|
||||
const el = getEl();
|
||||
if (el) {
|
||||
observer.disconnect();
|
||||
resolve(el);
|
||||
}
|
||||
}
|
||||
catch (_a) { }
|
||||
});
|
||||
observer.observe((_a = document.querySelector('div > section')) !== null && _a !== void 0 ? _a : document.body, { childList: true, subtree: true });
|
||||
});
|
||||
}
|
||||
var BtnListMode;
|
||||
(function (BtnListMode) {
|
||||
BtnListMode[BtnListMode["InPage"] = 0] = "InPage";
|
||||
|
@ -27100,7 +27134,7 @@ Please pipe the document into a Node stream.\
|
|||
}
|
||||
add(options) {
|
||||
var _a;
|
||||
const btnTpl = buildDownloadBtn((_a = options.icon) !== null && _a !== void 0 ? _a : ICON.DOWNLOAD);
|
||||
const btnTpl = buildDownloadBtn((_a = options.icon) !== null && _a !== void 0 ? _a : ICON.DOWNLOAD, options.lightTheme);
|
||||
const setText = (btn) => {
|
||||
const textNode = btn.querySelector('span');
|
||||
return (str) => {
|
||||
|
@ -27130,10 +27164,14 @@ Please pipe the document into a Node stream.\
|
|||
return btnTpl;
|
||||
}
|
||||
_positionBtns(anchorDiv, newParent) {
|
||||
const { top } = anchorDiv.getBoundingClientRect();
|
||||
let { top } = anchorDiv.getBoundingClientRect();
|
||||
top += window.scrollY; // relative to the entire document instead of viewport
|
||||
if (top > 0) {
|
||||
newParent.style.top = `${top}px`;
|
||||
}
|
||||
else {
|
||||
newParent.style.top = '0px';
|
||||
}
|
||||
}
|
||||
_commit() {
|
||||
const btnParent = document.querySelector('div');
|
||||
|
@ -27148,20 +27186,17 @@ Please pipe the document into a Node stream.\
|
|||
const newParent = document.createElement('div');
|
||||
newParent.append(...this.list.map(e => cloneBtn(e)));
|
||||
shadow.append(newParent);
|
||||
try {
|
||||
const anchorDiv = this.getBtnParent();
|
||||
// default position
|
||||
newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`;
|
||||
void onPageRendered(this.getBtnParent).then((anchorDiv) => {
|
||||
const pos = () => this._positionBtns(anchorDiv, newParent);
|
||||
pos();
|
||||
document.addEventListener('readystatechange', pos);
|
||||
// reposition btns when window resizes
|
||||
window.addEventListener('resize', pos);
|
||||
window.addEventListener('resize', pos, { passive: true });
|
||||
// reposition btns when scrolling
|
||||
const scroll = getScrollParent(anchorDiv);
|
||||
scroll.addEventListener('scroll', pos);
|
||||
}
|
||||
catch (err) {
|
||||
console$1.error(err);
|
||||
}
|
||||
scroll.addEventListener('scroll', pos, { passive: true });
|
||||
});
|
||||
return btnParent;
|
||||
}
|
||||
/**
|
||||
|
@ -27268,6 +27303,15 @@ Please pipe the document into a Node stream.\
|
|||
}
|
||||
else {
|
||||
setText(i18n('BTN_ERROR')());
|
||||
// ask user to send Discord message
|
||||
alert('❌Download Failed!\n\n' +
|
||||
'Send your URL to the #dataset-bugs channel ' +
|
||||
'in the LibreScore Community Discord server:\n' + DISCORD_URL);
|
||||
// open Discord on 'OK'
|
||||
const a = document.createElement('a');
|
||||
a.href = DISCORD_URL;
|
||||
a.target = '_blank';
|
||||
a.dispatchEvent(new MouseEvent('click'));
|
||||
}
|
||||
}
|
||||
btn.onclick = _onclick;
|
||||
|
@ -27285,7 +27329,7 @@ Please pipe the document into a Node stream.\
|
|||
class ScoreInfo {
|
||||
constructor() {
|
||||
this.RADIX = 20;
|
||||
this.INDEX_RADIX = 40;
|
||||
this.INDEX_RADIX = 32;
|
||||
this.store = new Map();
|
||||
}
|
||||
get idLastDigit() {
|
||||
|
@ -27301,7 +27345,7 @@ Please pipe the document into a Node stream.\
|
|||
return `https://ipfs.infura.io:5001/api/v0/block/stat?arg=${this.getMsczIpfsRef(mainCid)}`;
|
||||
}
|
||||
getScorepackRef(mainCid) {
|
||||
return `/ipfs/${mainCid}/index/${(+this.id) % this.INDEX_RADIX}/${this.id}/scorepack`;
|
||||
return `/ipfs/${mainCid}/index/${(+this.id) % this.INDEX_RADIX}/${this.id}`;
|
||||
}
|
||||
}
|
||||
class ScoreInfoInPage extends ScoreInfo {
|
||||
|
@ -27336,13 +27380,22 @@ Please pipe the document into a Node stream.\
|
|||
super();
|
||||
this.document = document;
|
||||
}
|
||||
get sheet0Img() {
|
||||
return this.document.querySelector('img[src*=score_]');
|
||||
}
|
||||
get pageCount() {
|
||||
return this.document.querySelectorAll('.gXB83').length;
|
||||
var _a;
|
||||
const sheet0Div = (_a = this.sheet0Img) === null || _a === void 0 ? void 0 : _a.parentElement;
|
||||
if (!sheet0Div) {
|
||||
throw new Error('no sheet images found');
|
||||
}
|
||||
return this.document.getElementsByClassName(sheet0Div.className).length;
|
||||
}
|
||||
get thumbnailUrl() {
|
||||
var _a;
|
||||
// url to the image of the first page
|
||||
const el = this.document.querySelector('link[as=image]');
|
||||
const url = el.href;
|
||||
const url = ((el === null || el === void 0 ? void 0 : el.href) || ((_a = this.sheet0Img) === null || _a === void 0 ? void 0 : _a.src));
|
||||
return url.split('@')[0];
|
||||
}
|
||||
}
|
||||
|
@ -27391,7 +27444,7 @@ Please pipe the document into a Node stream.\
|
|||
action: BtnAction.process(() => downloadPDF(scoreinfo, new SheetInfoInPage(document)), fallback, 3 * 60 * 1000 /* 3min */),
|
||||
});
|
||||
btnList.add({
|
||||
name: i18n('DOWNLOAD')('MusicXML'),
|
||||
name: i18n('DOWNLOAD')('MXL'),
|
||||
action: BtnAction.mscoreWindow(scoreinfo, (w, score) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
const mxl = yield score.saveMxl();
|
||||
const data = new Blob([mxl]);
|
||||
|
@ -27471,7 +27524,9 @@ Please pipe the document into a Node stream.\
|
|||
action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)),
|
||||
tooltip: 'BETA',
|
||||
icon: ICON.LIBRESCORE,
|
||||
lightTheme: true,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
btnList.commit(BtnListMode.InPage);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
|
|
40
package-lock.json
generated
40
package-lock.json
generated
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"name": "musescore-downloader",
|
||||
"version": "0.23.1",
|
||||
"version": "0.24.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@librescore/fonts": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@librescore/fonts/-/fonts-0.2.1.tgz",
|
||||
"integrity": "sha512-lzEk82wZWZVA4CvE2S6Wwc6EAvFZ0G6L2ExNjpJLebxAh0k/eNpHeO9a2LFwfMVUfacVWwXhDkAbmJpvUGcqzA=="
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@librescore/fonts/-/fonts-0.4.0.tgz",
|
||||
"integrity": "sha512-T286OfxcQAYc/Bll9AtSP2ElggqTpoa08uY9Kgx6z1TcDVn7i7uMkKVO7sw/8aELWFNRmQE2vGQuEkmJNfWmBA=="
|
||||
},
|
||||
"@librescore/sf3": {
|
||||
"version": "0.3.0",
|
||||
|
@ -709,18 +709,26 @@
|
|||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
|
@ -2431,9 +2439,9 @@
|
|||
}
|
||||
},
|
||||
"webmscore": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/webmscore/-/webmscore-0.12.0.tgz",
|
||||
"integrity": "sha512-lmie1LWQueAIih93hXGuphl6D18pdZIKLCv91Hu0n5jklJw3RZactRxGujdJbMaeRW2AjIU2FLKklcGzqdrnJA=="
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/webmscore/-/webmscore-0.18.0.tgz",
|
||||
"integrity": "sha512-/J/2/KKWKST0A+Qix/SBSVtZY0C/33GQoYI3V84XEu/V3nij2ZFIcsyGQPYVr6y0HVasj6dQtvY+y7MrmYcsTw=="
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "musescore-downloader",
|
||||
"version": "0.23.1",
|
||||
"version": "0.24.1",
|
||||
"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",
|
||||
|
@ -37,13 +37,13 @@
|
|||
"dist/main.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@librescore/fonts": "^0.2.1",
|
||||
"@librescore/fonts": "^0.4.0",
|
||||
"@librescore/sf3": "^0.3.0",
|
||||
"detect-node": "^2.0.4",
|
||||
"inquirer": "^7.3.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"ora": "^5.1.0",
|
||||
"webmscore": "^0.12.0"
|
||||
"webmscore": "^0.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
|
|
38
src/btn.css
38
src/btn.css
|
@ -5,11 +5,14 @@ div {
|
|||
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif;
|
||||
font-family: 'Inter', 'Helvetica neue', Helvetica, sans-serif;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
background: #f6f6f6;
|
||||
min-width: 230px;
|
||||
|
||||
/* pass the scroll event through the btns background */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 950px) {
|
||||
|
@ -19,23 +22,25 @@ div {
|
|||
}
|
||||
|
||||
button {
|
||||
width: 205px !important;
|
||||
height: 38px;
|
||||
width: 178px !important;
|
||||
min-width: 178px;
|
||||
height: 40px;
|
||||
|
||||
color: #fff;
|
||||
background: #1f74bd;
|
||||
background: #2e68c0;
|
||||
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
|
||||
margin-bottom: 4px;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 8px;
|
||||
padding: 4px 12px;
|
||||
|
||||
justify-content: start;
|
||||
align-self: center;
|
||||
|
||||
font-size: 16px;
|
||||
border-radius: 2px;
|
||||
border-radius: 6px;
|
||||
border: 0;
|
||||
|
||||
display: inline-flex;
|
||||
|
@ -44,6 +49,25 @@ button {
|
|||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* fix `View in LibreScore` button text overflow */
|
||||
button:last-of-type {
|
||||
width: unset !important;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #1a4f9f;
|
||||
}
|
||||
|
||||
/* light theme btn */
|
||||
button.light {
|
||||
color: #2e68c0;
|
||||
background: #e1effe;
|
||||
}
|
||||
|
||||
button.light:hover {
|
||||
background: #c3ddfd;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
|
|
60
src/btn.ts
60
src/btn.ts
|
@ -1,7 +1,7 @@
|
|||
|
||||
import { ScoreInfo } from './scoreinfo'
|
||||
import { loadMscore, WebMscore } from './mscore'
|
||||
import { useTimeout, windowOpenAsync, console, attachShadow } from './utils'
|
||||
import { useTimeout, windowOpenAsync, console, attachShadow, DISCORD_URL } from './utils'
|
||||
import { isGmAvailable, _GM } from './gm'
|
||||
import i18n from './i18n'
|
||||
// @ts-ignore
|
||||
|
@ -15,26 +15,27 @@ export enum ICON {
|
|||
}
|
||||
|
||||
const getBtnContainer = (): HTMLDivElement => {
|
||||
const els = [...document.querySelectorAll('*')].reverse()
|
||||
const els = [...document.querySelectorAll('span')]
|
||||
const el = els.find(b => {
|
||||
const text = b?.textContent?.replace(/\s/g, '') || ''
|
||||
return text.includes('Download') || text.includes('Print')
|
||||
}) as HTMLDivElement | null
|
||||
const btnParent = el?.parentElement?.parentElement as HTMLDivElement | undefined
|
||||
if (!btnParent) throw new Error('btn parent not found')
|
||||
if (!btnParent || !(btnParent instanceof HTMLDivElement)) throw new Error('btn parent not found')
|
||||
return btnParent
|
||||
}
|
||||
|
||||
const buildDownloadBtn = (icon: ICON) => {
|
||||
const buildDownloadBtn = (icon: ICON, lightTheme = false) => {
|
||||
const btn = document.createElement('button')
|
||||
btn.type = 'button'
|
||||
if (lightTheme) btn.className = 'light'
|
||||
|
||||
// build icon svg element
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
svg.setAttribute('viewBox', '0 0 24 24')
|
||||
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
||||
svgPath.setAttribute('d', icon)
|
||||
svgPath.setAttribute('fill', '#fff')
|
||||
svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff')
|
||||
svg.append(svgPath)
|
||||
|
||||
const textNode = document.createElement('span')
|
||||
|
@ -57,12 +58,28 @@ function getScrollParent (node: HTMLElement): HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
function onPageRendered (getEl: () => HTMLElement) {
|
||||
return new Promise<HTMLElement>((resolve) => {
|
||||
const observer = new MutationObserver(() => {
|
||||
try {
|
||||
const el = getEl()
|
||||
if (el) {
|
||||
observer.disconnect()
|
||||
resolve(el)
|
||||
}
|
||||
} catch { }
|
||||
})
|
||||
observer.observe(document.querySelector('div > section') ?? document.body, { childList: true, subtree: true })
|
||||
})
|
||||
}
|
||||
|
||||
interface BtnOptions {
|
||||
readonly name: string;
|
||||
readonly action: BtnAction;
|
||||
readonly disabled?: boolean;
|
||||
readonly tooltip?: string;
|
||||
readonly icon?: ICON;
|
||||
readonly lightTheme?: boolean;
|
||||
}
|
||||
|
||||
export enum BtnListMode {
|
||||
|
@ -76,7 +93,7 @@ export class BtnList {
|
|||
constructor (private getBtnParent: () => HTMLDivElement = getBtnContainer) { }
|
||||
|
||||
add (options: BtnOptions): BtnElement {
|
||||
const btnTpl = buildDownloadBtn(options.icon ?? ICON.DOWNLOAD)
|
||||
const btnTpl = buildDownloadBtn(options.icon ?? ICON.DOWNLOAD, options.lightTheme)
|
||||
const setText = (btn: BtnElement) => {
|
||||
const textNode = btn.querySelector('span')
|
||||
return (str: string): void => {
|
||||
|
@ -113,9 +130,12 @@ export class BtnList {
|
|||
}
|
||||
|
||||
private _positionBtns (anchorDiv: HTMLDivElement, newParent: HTMLDivElement) {
|
||||
const { top } = anchorDiv.getBoundingClientRect()
|
||||
let { top } = anchorDiv.getBoundingClientRect()
|
||||
top += window.scrollY // relative to the entire document instead of viewport
|
||||
if (top > 0) {
|
||||
newParent.style.top = `${top}px`
|
||||
} else {
|
||||
newParent.style.top = '0px'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,21 +156,20 @@ export class BtnList {
|
|||
newParent.append(...this.list.map(e => cloneBtn(e)))
|
||||
shadow.append(newParent)
|
||||
|
||||
try {
|
||||
const anchorDiv = this.getBtnParent()
|
||||
// default position
|
||||
newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`
|
||||
|
||||
void onPageRendered(this.getBtnParent).then((anchorDiv: HTMLDivElement) => {
|
||||
const pos = () => this._positionBtns(anchorDiv, newParent)
|
||||
pos()
|
||||
document.addEventListener('readystatechange', pos)
|
||||
|
||||
// reposition btns when window resizes
|
||||
window.addEventListener('resize', pos)
|
||||
window.addEventListener('resize', pos, { passive: true })
|
||||
|
||||
// reposition btns when scrolling
|
||||
const scroll = getScrollParent(anchorDiv)
|
||||
scroll.addEventListener('scroll', pos)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
scroll.addEventListener('scroll', pos, { passive: true })
|
||||
})
|
||||
|
||||
return btnParent
|
||||
}
|
||||
|
@ -269,6 +288,17 @@ export namespace BtnAction {
|
|||
setText(name)
|
||||
} else {
|
||||
setText(i18n('BTN_ERROR')())
|
||||
// ask user to send Discord message
|
||||
alert(
|
||||
'❌Download Failed!\n\n' +
|
||||
'Send your URL to the #dataset-bugs channel ' +
|
||||
'in the LibreScore Community Discord server:\n' + DISCORD_URL,
|
||||
)
|
||||
// open Discord on 'OK'
|
||||
const a = document.createElement('a')
|
||||
a.href = DISCORD_URL
|
||||
a.target = '_blank'
|
||||
a.dispatchEvent(new MouseEvent('click'))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
23
src/cli.ts
23
src/cli.ts
|
@ -9,7 +9,8 @@ import { fetchMscz, setMscz, MSCZ_URL_SYM } from './mscz'
|
|||
import { loadMscore, INDV_DOWNLOADS, WebMscore } from './mscore'
|
||||
import { ScoreInfo, ScoreInfoHtml, ScoreInfoObj, getActualId } from './scoreinfo'
|
||||
import { getLibreScoreLink } from './librescore-link'
|
||||
import { escapeFilename } from './utils'
|
||||
import { escapeFilename, DISCORD_URL } from './utils'
|
||||
import { isNpx, getVerInfo, getSelfVer } from './npm-data'
|
||||
import i18n from './i18n'
|
||||
|
||||
const inquirer: typeof import('inquirer') = require('inquirer')
|
||||
|
@ -29,6 +30,12 @@ interface Params {
|
|||
}
|
||||
|
||||
void (async () => {
|
||||
const arg = process.argv[2]
|
||||
if (['-v', '--version'].includes(arg)) { // ran with flag -v or --version, `msdl -v`
|
||||
console.log(getSelfVer()) // print musescore-downloader version
|
||||
return // exit process
|
||||
}
|
||||
|
||||
// Determine platform and paste message
|
||||
const platform = os.platform()
|
||||
let pasteMessage = ''
|
||||
|
@ -55,7 +62,7 @@ void (async () => {
|
|||
(input.endsWith(EXT) && fs.statSync(input).isFile())
|
||||
)
|
||||
},
|
||||
default: process.argv[2],
|
||||
default: arg,
|
||||
})
|
||||
|
||||
const isLocalFile = fileInit.endsWith(EXT)
|
||||
|
@ -82,6 +89,7 @@ void (async () => {
|
|||
|
||||
// initiate LibreScore link request
|
||||
librescoreLink = getLibreScoreLink(scoreinfo)
|
||||
librescoreLink.catch(() => '') // silence this unhandled Promise rejection
|
||||
|
||||
// print a blank line to the terminal
|
||||
console.log()
|
||||
|
@ -126,6 +134,10 @@ void (async () => {
|
|||
spinner.info('Score loaded by webmscore')
|
||||
} catch (err) {
|
||||
spinner.fail(err.message)
|
||||
spinner.info(
|
||||
'Send your URL to the #dataset-bugs channel in the LibreScore Community Discord server:\n ' +
|
||||
DISCORD_URL,
|
||||
)
|
||||
return
|
||||
}
|
||||
spinner.succeed('OK\n')
|
||||
|
@ -184,4 +196,11 @@ void (async () => {
|
|||
}),
|
||||
)
|
||||
spinner.succeed('OK')
|
||||
|
||||
if (!isNpx()) {
|
||||
const { installed, latest, isLatest } = await getVerInfo()
|
||||
if (!isLatest) {
|
||||
console.log(chalk.yellowBright(`\nYour installed version (${installed}) of the musescore-downloader CLI is not the latest one (${latest})!\nRun npm i -g musescore-downloader@${latest} to update.`))
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
|
|
@ -6,7 +6,7 @@ import { console } from './utils'
|
|||
|
||||
type FileType = 'img' | 'mp3' | 'midi'
|
||||
|
||||
const TYPE_REG = /id=(\d+)&type=(img|mp3|midi)/
|
||||
const TYPE_REG = /type=(img|mp3|midi)/
|
||||
|
||||
/**
|
||||
* I know this is super hacky.
|
||||
|
@ -31,8 +31,9 @@ const magicHookConstr = (() => {
|
|||
const token = init?.headers?.Authorization
|
||||
if (typeof url === 'string' && token) {
|
||||
const m = url.match(TYPE_REG)
|
||||
console.debug(url, token, m)
|
||||
if (m) {
|
||||
const type = m[2]
|
||||
const type = m[1]
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
l[type]?.(token)
|
||||
}
|
||||
|
@ -78,7 +79,7 @@ const getApiAuth = async (type: FileType, index: number): Promise<string> => {
|
|||
// force to retrieve the MAGIC
|
||||
switch (type) {
|
||||
case 'midi': {
|
||||
const el = document.querySelectorAll('.SD7H- > button')[3] as HTMLButtonElement
|
||||
const el = document.querySelector('button[hasaccess]') as HTMLButtonElement
|
||||
el.click()
|
||||
break
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@ import { assertRes, getFetch } from './utils'
|
|||
import { getMainCid } from './mscz'
|
||||
import { ScoreInfo } from './scoreinfo'
|
||||
|
||||
const _getLink = (scorepack: string) => {
|
||||
return `https://librescore.org/score/${scorepack}`
|
||||
const _getLink = (indexingInfo: string) => {
|
||||
const { scorepack } = JSON.parse(indexingInfo)
|
||||
return `https://librescore.org/score/${scorepack as string}`
|
||||
}
|
||||
|
||||
export const getLibreScoreLink = async (scoreinfo: ScoreInfo, _fetch = getFetch()): Promise<string> => {
|
||||
|
|
|
@ -38,7 +38,7 @@ const main = (): void => {
|
|||
})
|
||||
|
||||
btnList.add({
|
||||
name: i18n('DOWNLOAD')('MusicXML'),
|
||||
name: i18n('DOWNLOAD')('MXL'),
|
||||
action: BtnAction.mscoreWindow(scoreinfo, async (w, score) => {
|
||||
const mxl = await score.saveMxl()
|
||||
const data = new Blob([mxl])
|
||||
|
@ -137,8 +137,10 @@ const main = (): void => {
|
|||
action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)),
|
||||
tooltip: 'BETA',
|
||||
icon: ICON.LIBRESCORE,
|
||||
lightTheme: true,
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
btnList.commit(BtnListMode.InPage)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// @version %VERSION%
|
||||
// @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro,免费下载 musescore.com 上的曲谱
|
||||
// @author Xmader
|
||||
// @icon https://librescore.org/img/icons/logo.svg
|
||||
// @match https://musescore.com/*/*
|
||||
// @match https://s.musescore.com/*/*
|
||||
// @license MIT
|
||||
|
|
|
@ -6,14 +6,15 @@ import { fetchData } from './utils'
|
|||
import { ScoreInfo } from './scoreinfo'
|
||||
import isNodeJs from 'detect-node'
|
||||
import i18n from './i18n'
|
||||
import { dependencies as depVers } from '../package.json'
|
||||
|
||||
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.12/webmscore.js'
|
||||
const WEBMSCORE_URL = `https://cdn.jsdelivr.net/npm/webmscore@${depVers.webmscore}/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 FONT_URLS = ['CN', 'KR'].map(l => `https://cdn.jsdelivr.net/npm/@librescore/fonts@${depVers['@librescore/fonts']}/SourceHanSans${l}.min.woff2`)
|
||||
|
||||
const SF3_URL = 'https://cdn.jsdelivr.net/npm/@librescore/sf3/FluidR3Mono_GM.sf3'
|
||||
const SF3_URL = `https://cdn.jsdelivr.net/npm/@librescore/sf3@${depVers['@librescore/sf3']}/FluidR3Mono_GM.sf3`
|
||||
const SOUND_FONT_LOADED = Symbol('SoundFont loaded')
|
||||
|
||||
export type WebMscore = import('webmscore').default
|
||||
|
|
|
@ -48,7 +48,7 @@ export const loadMsczUrl = async (scoreinfo: ScoreInfo, _fetch = getFetch()): Pr
|
|||
// read further error msg
|
||||
const err = cidRes.Message
|
||||
if (err.includes('no link named')) { // file not found
|
||||
throw new Error('score not in dataset')
|
||||
throw new Error('Score not in dataset')
|
||||
} else {
|
||||
throw new Error(err)
|
||||
}
|
||||
|
|
2
src/msdl/cli.js
Normal file
2
src/msdl/cli.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env node
|
||||
require("musescore-downloader/dist/cli.js")
|
19
src/msdl/package.json
Normal file
19
src/msdl/package.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "msdl",
|
||||
"version": "%VERSION%",
|
||||
"author": "Xmader",
|
||||
"bin": "cli.js",
|
||||
"description": "Alias for musescore-downloader",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Xmader/musescore-downloader.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Xmader/musescore-downloader/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Xmader/musescore-downloader#readme",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"musescore-downloader": "%VERSION%"
|
||||
}
|
||||
}
|
34
src/npm-data.ts
Normal file
34
src/npm-data.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { name as pkgName, version as pkgVer } from '../package.json'
|
||||
import { getFetch } from './utils'
|
||||
|
||||
const IS_NPX_REG = /_npx(\/|\\)\d+\1/
|
||||
const NPM_REGISTRY = 'https://registry.npmjs.org'
|
||||
|
||||
export function isNpx (): boolean {
|
||||
// file is in a npx cache dir
|
||||
// TODO: installed locally?
|
||||
return __dirname.match(IS_NPX_REG) !== null
|
||||
}
|
||||
|
||||
export function getSelfVer (): string {
|
||||
return pkgVer
|
||||
}
|
||||
|
||||
export async function getLatestVer (_fetch = getFetch()): Promise<string> {
|
||||
// fetch pkg info from the npm registry
|
||||
const r = await _fetch(`${NPM_REGISTRY}/${pkgName}`)
|
||||
const json = await r.json()
|
||||
return json['dist-tags'].latest as string
|
||||
}
|
||||
|
||||
export async function getVerInfo () {
|
||||
const installed = getSelfVer()
|
||||
const latest = await getLatestVer()
|
||||
return {
|
||||
installed,
|
||||
latest,
|
||||
isLatest: installed === latest,
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import { getMainCid } from './mscz'
|
|||
|
||||
export abstract class ScoreInfo {
|
||||
private readonly RADIX = 20;
|
||||
private readonly INDEX_RADIX = 40;
|
||||
private readonly INDEX_RADIX = 32;
|
||||
|
||||
abstract id: number;
|
||||
abstract title: string;
|
||||
|
@ -28,7 +28,7 @@ export abstract class ScoreInfo {
|
|||
}
|
||||
|
||||
public getScorepackRef (mainCid: string): string {
|
||||
return `/ipfs/${mainCid}/index/${(+this.id) % this.INDEX_RADIX}/${this.id}/scorepack`
|
||||
return `/ipfs/${mainCid}/index/${(+this.id) % this.INDEX_RADIX}/${this.id}`
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,14 +105,22 @@ export abstract class SheetInfo {
|
|||
export class SheetInfoInPage extends SheetInfo {
|
||||
constructor (private document: Document) { super() }
|
||||
|
||||
private get sheet0Img (): HTMLImageElement | null {
|
||||
return this.document.querySelector('img[src*=score_]')
|
||||
}
|
||||
|
||||
get pageCount (): number {
|
||||
return this.document.querySelectorAll('.gXB83').length
|
||||
const sheet0Div = this.sheet0Img?.parentElement
|
||||
if (!sheet0Div) {
|
||||
throw new Error('no sheet images found')
|
||||
}
|
||||
return this.document.getElementsByClassName(sheet0Div.className).length
|
||||
}
|
||||
|
||||
get thumbnailUrl (): string {
|
||||
// url to the image of the first page
|
||||
const el = this.document.querySelector('link[as=image]') as HTMLLinkElement
|
||||
const url = el.href
|
||||
const el = this.document.querySelector<HTMLLinkElement>('link[as=image]')
|
||||
const url = (el?.href || this.sheet0Img?.src) as string
|
||||
return url.split('@')[0]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import isNodeJs from 'detect-node'
|
||||
import { isGmAvailable, _GM } from './gm'
|
||||
|
||||
export const DISCORD_URL = 'https://discord.gg/gSsTUvJmD8'
|
||||
|
||||
export const escapeFilename = (s: string): string => {
|
||||
return s.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_')
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@ import SVGtoPDF from 'svg-to-pdfkit'
|
|||
|
||||
type ImgType = 'svg' | 'png'
|
||||
|
||||
const getDataURL = (blob: Blob): Promise<string> => {
|
||||
type DataResultType = 'dataUrl' | 'text'
|
||||
|
||||
const readData = (blob: Blob, type: DataResultType): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (): void => {
|
||||
|
@ -14,22 +16,22 @@ const getDataURL = (blob: Blob): Promise<string> => {
|
|||
resolve(result as string)
|
||||
}
|
||||
reader.onerror = reject
|
||||
reader.readAsDataURL(blob)
|
||||
if (type === 'dataUrl') {
|
||||
reader.readAsDataURL(blob)
|
||||
} else {
|
||||
reader.readAsText(blob)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const fetchDataURL = async (imgUrl: string): Promise<string> => {
|
||||
const r = await fetch(imgUrl)
|
||||
const blob = await r.blob()
|
||||
return getDataURL(blob)
|
||||
const fetchBlob = async (imgUrl: string): Promise<Blob> => {
|
||||
const r = await fetch(imgUrl, {
|
||||
cache: 'no-cache',
|
||||
})
|
||||
return r.blob()
|
||||
}
|
||||
|
||||
const fetchText = async (imgUrl: string): Promise<string> => {
|
||||
const r = await fetch(imgUrl)
|
||||
return r.text()
|
||||
}
|
||||
|
||||
const generatePDF = async (imgURLs: string[], imgType: ImgType, width: number, height: number): Promise<ArrayBuffer> => {
|
||||
const generatePDF = async (imgBlobs: Blob[], imgType: ImgType, width: number, height: number): Promise<ArrayBuffer> => {
|
||||
// @ts-ignore
|
||||
const pdf = new (PDFDocument as typeof import('pdfkit'))({
|
||||
// compress: true,
|
||||
|
@ -40,7 +42,7 @@ const generatePDF = async (imgURLs: string[], imgType: ImgType, width: number, h
|
|||
})
|
||||
|
||||
if (imgType === 'png') {
|
||||
const imgDataUrlList: string[] = await Promise.all(imgURLs.map(fetchDataURL))
|
||||
const imgDataUrlList: string[] = await Promise.all(imgBlobs.map(b => readData(b, 'dataUrl')))
|
||||
|
||||
imgDataUrlList.forEach((data) => {
|
||||
pdf.addPage()
|
||||
|
@ -50,7 +52,7 @@ const generatePDF = async (imgURLs: string[], imgType: ImgType, width: number, h
|
|||
})
|
||||
})
|
||||
} else { // imgType == "svg"
|
||||
const svgList = await Promise.all(imgURLs.map(fetchText))
|
||||
const svgList = await Promise.all(imgBlobs.map(b => readData(b, 'text')))
|
||||
|
||||
svgList.forEach((svg) => {
|
||||
pdf.addPage()
|
||||
|
@ -70,14 +72,16 @@ export type PDFWorkerMessage = [string[], ImgType, number, number];
|
|||
|
||||
onmessage = async (e): Promise<void> => {
|
||||
const [
|
||||
imgURLs,
|
||||
imgUrls,
|
||||
imgType,
|
||||
width,
|
||||
height,
|
||||
] = e.data as PDFWorkerMessage
|
||||
|
||||
const imgBlobs = await Promise.all(imgUrls.map(url => fetchBlob(url)))
|
||||
|
||||
const pdfBuf = await generatePDF(
|
||||
imgURLs,
|
||||
imgBlobs,
|
||||
imgType,
|
||||
width,
|
||||
height,
|
||||
|
|
|
@ -41,7 +41,7 @@ new Promise(resolve => {
|
|||
const loaderOutro = '})()'.repeat(stackN)
|
||||
const mockUrl = "https://c.amazon-adsystem.com/aax2/apstag.js"
|
||||
|
||||
setTimeout(`${loaderIntro}const d=new Image();window['${id}'](d);delete window['${id}'];document.body.prepend(d)${loaderOutro}//# sourceURL=${mockUrl}`)
|
||||
Function(`${loaderIntro}const d=new Image();window['${id}'](d);delete window['${id}'];document.body.prepend(d)${loaderOutro}//# sourceURL=${mockUrl}`)()
|
||||
}).then(d => {
|
||||
d.style.display = 'none';
|
||||
d.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||
|
|
Loading…
Reference in a new issue