Compare commits

..

71 commits

Author SHA1 Message Date
Wenzheng Tang
8e3dd94c12
feat: add @icon metadata
Merge pull request #125 from Wupb/patch-1
2021-06-18 18:31:05 -04:00
Wupb
b1a3456646
Update @icon metadata 2021-06-18 13:09:15 -07:00
Wupb
d269ae9be9
Add @icon metadata 2021-06-18 11:46:01 -07:00
Xmader
20298e5c0e
v0.24.1 2021-06-06 12:48:20 -04:00
Xmader
49880d63da
fix: mp3 & midi download 2021-06-06 12:47:21 -04:00
Xmader
0c2106993d
chore: source tarball name consistency 2021-05-21 17:55:14 -04:00
Xmader
3ac3a51ad1
v0.24.0 2021-05-21 17:44:53 -04:00
Xmader
9c05dd5d3d
fix: btn position when logged in 2021-05-21 17:41:18 -04:00
Xmader
ae08ff847a
chore: .eslintrc 2021-05-21 17:40:52 -04:00
Xmader
8cc089357f
fix: default btn position 2021-05-21 16:50:04 -04:00
Xmader
4e92c06791
refactor: btn color theme 2021-05-19 02:10:28 -04:00
Xmader
5d1639dddf
feat: View in LibreScore btn light color 2021-05-19 02:02:52 -04:00
Xmader
88fdfff054
feat: match the new design 2021-05-19 01:59:10 -04:00
Xmader
893c64edda
v0.23.15 2021-05-13 23:56:00 -04:00
Xmader
1bf3050513
feat: update librescore link 2021-05-13 23:54:39 -04:00
Xmader
ffbac084fb
feat: direct discord url to the #dataset-bugs channel 2021-05-13 16:43:40 -04:00
Xmader
861af6d3f0
v0.23.13 2021-05-11 17:03:38 -04:00
Xmader
30514e9507
fix: tampermonkey script on Chrome
fixes: #121
2021-05-11 17:02:32 -04:00
Wenzheng Tang
7a397e068f
fix: discord message formatting
Merge pull request #120 from PeterNjeim/master
2021-05-10 21:23:42 -04:00
Peter Njeim
d97b3d86d8 fix: discord message formatting 2021-05-10 14:08:15 -03:00
Xmader
bef85c5c3e
v0.23.12 2021-05-10 10:57:16 -04:00
Xmader
b68ff38028
fix: mp3 & midi download
The result differed as connection speed differs.

race condition: this script code and `webpackChunkmusescore_es6` chunks
2021-05-10 10:55:24 -04:00
Xmader
a571fed456
v0.23.11 2021-05-09 14:29:26 -04:00
Xmader
f9be984d17
refactor: discord url 2021-05-09 14:27:33 -04:00
Xmader
9adba49de1
feat: ask user to send Discord message on error 2021-05-09 14:25:43 -04:00
Xmader
6d93907df0
chore: include source tarballs in release mirrors on ipfs
https://discord.com/channels/774491656643674122/774491656643674128/838118504535949312
2021-05-02 18:09:12 -04:00
Wenzheng Tang
c5d6d87b8c
Merge pull request #113 from RubenVerg/patch-2
Update cli.ts
By default, `npm i -g (already installed package)` only installs new patches, not minors or majors.
2021-05-02 18:01:24 -04:00
Ruben Vergani
8475941d47
Update cli.ts
by default, `npm i -g (already installed package)` only installs new patches, not minors or majors.
2021-04-10 19:07:50 +02:00
Xmader
71def367e8
v0.23.10 2021-03-13 01:29:30 -05:00
Xmader
d1c9634331
fix: ask user to send Discord message when score is missing from dataset 2021-03-13 01:28:37 -05:00
Wenzheng Tang
49aef0ccf3
feat: Ask user to send Discord message when score is missing from dataset
Merge pull request #110 from PeterNjeim/master
2021-03-12 21:24:54 -05:00
Peter Njeim
ec15d44811 revert: dist/main.js 2021-03-12 20:20:27 -04:00
Peter Njeim
4494e15540 feat: include discord link in message, npm audit fixes, and typescript handbook standards 2021-03-12 20:10:50 -04:00
Peter Njeim
6a6e2a5ea1 fix: packages and unnecessary if statement 2021-03-12 19:35:41 -04:00
Xmader
3260672412
v0.23.9 2021-02-06 05:15:31 -05:00
Xmader
004cb16fce
fix: button min width 2021-02-06 05:12:34 -05:00
Xmader
ec1c1ea87c
chore(deps): upgrade @librescore/fonts 2021-02-06 05:06:21 -05:00
Xmader
69e5bd0a78
fix(i18n): button text overflow
https://discord.com/channels/774491656643674122/776293233382653963/806828497242816522
2021-02-05 18:07:24 -05:00
Xmader
06a91b1c2d
v0.23.8 2021-01-27 14:03:58 -05:00
Xmader
41f5286d48
fix: CORS issue for PDF download
CORS requests would fail after a cached no-CORS request of the same urls
2021-01-27 12:57:35 -05:00
Xmader
29a09c2596
refactor: pdf worker fetch blobs 2021-01-27 12:35:52 -05:00
Xmader
f2a52dd514
refactor: pdf worker 2021-01-27 12:26:32 -05:00
Xmader
1e3e2d7581
v0.23.7 2021-01-27 11:41:14 -05:00
Xmader
eda8342a3d
fix: PDF download when the piano keyboard is open 2021-01-27 11:31:09 -05:00
Xmader
e9ed0812b9
fix: PDF page count
This will emit a malformed 642 byte PDF file.
2021-01-27 11:20:29 -05:00
Xmader
4bd5d55676
fix: MIDI download 2021-01-27 11:09:06 -05:00
Xmader
c571a49093
v0.23.6 2021-01-21 23:43:40 -05:00
Xmader
b5477a4059
fix: btns position 2021-01-21 23:40:53 -05:00
Xmader
d33c06c892
feat(cli): print musescore-downloader version, msdl -v 2021-01-21 21:24:08 -05:00
Xmader
04884a137f
chore(deps): upgrade webmscore to v0.18.0 2021-01-21 01:58:20 -05:00
Xmader
263f72dc7a
feat: lock downloaded dependency versions 2021-01-21 01:44:13 -05:00
Xmader
c46343b46c
v0.23.5 2021-01-20 11:32:57 -05:00
Xmader
6202321a42
refactor(npm-data): avoid running external npm cmd 2021-01-20 11:29:49 -05:00
Xmader
3c72b5a92f
refactor: check version info 2021-01-20 11:05:56 -05:00
Xmader
085c6a2d2a
refactor: add check for latest version 2021-01-20 10:52:08 -05:00
Xmader
1eb0f35bde
feat: add check for latest version
Merge pull request #106 from RubenVerg/master
2021-01-20 10:45:36 -05:00
Xmader
49fcb99160
fix: MIDI or PDF download not working (#107) 2021-01-20 10:44:54 -05:00
Xmader
bd943675d8
refactor: check for latest version 2021-01-20 10:44:00 -05:00
Xmader
030d37ddc0
refactor: read installed version using the package.json 2021-01-20 10:42:33 -05:00
Xmader
d014ade9ca
feat: msdl package to alias a specific version of musescore-downloader 2021-01-15 07:28:57 -05:00
Ruben Vergani
b8181f421d bump npm 2021-01-15 09:08:40 +01:00
Ruben Vergani
463ea5d416 for reasons to me unknown, eslint doesn't work,
so manually fixed the file
2021-01-15 09:06:31 +01:00
Ruben Vergani
da5d53898a add the check to the CLI 2021-01-15 09:02:48 +01:00
Ruben Vergani
2b842e267f expose the installed and latest version functions 2021-01-15 09:01:11 +01:00
Ruben Vergani
08294f564b spaces, not tabs 2021-01-15 08:55:06 +01:00
Ruben Vergani
46a7f50115 add functions to check if msdl is running 2021-01-15 08:54:01 +01:00
Xmader
7bb3aaf7b1
v0.23.4 2021-01-12 04:04:58 -05:00
Xmader
dd30454b5a
feat: update scorepack index 2021-01-12 04:02:52 -05:00
Xmader
d99848c6fc
v0.23.3 2021-01-07 03:39:56 -05:00
Xmader
c973d5d06f
chore: lock webmscore version to v0.16.2 2021-01-07 03:39:32 -05:00
Xmader
d919441966
chore: upgrade webmscore
There was a critical RuntimeError due to compiling bug in webmscore v0.16.1
2021-01-07 03:29:38 -05:00
21 changed files with 328 additions and 113 deletions

View file

@ -20,6 +20,8 @@
"no-useless-constructor": "off", "no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "error", "@typescript-eslint/no-useless-constructor": "error",
"no-dupe-class-members": "off", "no-dupe-class-members": "off",
"no-void": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-dupe-class-members": "error", "@typescript-eslint/no-dupe-class-members": "error",
"@typescript-eslint/no-floating-promises": "warn", "@typescript-eslint/no-floating-promises": "warn",
"@typescript-eslint/member-delimiter-style": "warn", "@typescript-eslint/member-delimiter-style": "warn",

View file

@ -53,6 +53,15 @@ jobs:
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # 0301... 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 - name: Publish Firefox Extension
id: web-ext-build id: web-ext-build
uses: kewisch/action-web-ext@v1 uses: kewisch/action-web-ext@v1
@ -69,6 +78,7 @@ jobs:
cp dist/main.js $ARTIFACTS_DIR/musescore-downloader.user.js cp dist/main.js $ARTIFACTS_DIR/musescore-downloader.user.js
cp dist/ext.zip $ARTIFACTS_DIR/musescore-downloader.webextension.zip cp dist/ext.zip $ARTIFACTS_DIR/musescore-downloader.webextension.zip
wget -q $CHROME_EXT_URL -P $ARTIFACTS_DIR/ 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 - run: bash ./.github/workflows/get-signed-ext.sh
env: env:
EXT_ID: musescore-downloader EXT_ID: musescore-downloader
@ -88,6 +98,7 @@ jobs:
IPFS_HASH: ${{ steps.ipfs.outputs.hash }} IPFS_HASH: ${{ steps.ipfs.outputs.hash }}
run: | run: |
cd $ARTIFACTS_DIR cd $ARTIFACTS_DIR
rm *.tar.gz
files=$(ls .) files=$(ls .)
assets=() assets=()

136
dist/main.js vendored
View file

@ -5,7 +5,7 @@
// @supportURL https://github.com/Xmader/musescore-downloader/issues // @supportURL https://github.com/Xmader/musescore-downloader/issues
// @updateURL https://msdl.librescore.org/install.user.js // @updateURL https://msdl.librescore.org/install.user.js
// @downloadURL https://msdl.librescore.org/install.user.js // @downloadURL https://msdl.librescore.org/install.user.js
// @version 0.23.2 // @version 0.24.1
// @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱 // @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱
// @author Xmader // @author Xmader
// @match https://musescore.com/*/* // @match https://musescore.com/*/*
@ -65,7 +65,7 @@
const loaderOutro = '})()'.repeat(stackN) const loaderOutro = '})()'.repeat(stackN)
const mockUrl = "https://c.amazon-adsystem.com/aax2/apstag.js" 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 => { }).then(d => {
d.style.display = 'none'; d.style.display = 'none';
d.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; d.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
@ -331,6 +331,7 @@
typeof GM[requiredMethod] !== 'undefined'; typeof GM[requiredMethod] !== 'undefined';
}; };
const DISCORD_URL = 'https://discord.gg/gSsTUvJmD8';
const escapeFilename = (s) => { const escapeFilename = (s) => {
return s.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_'); return s.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_');
}; };
@ -26439,7 +26440,7 @@ Please pipe the document into a Node stream.\
}); });
/// <reference lib="webworker" /> /// <reference lib="webworker" />
const getDataURL = (blob) => { const readData = (blob, type) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = () => {
@ -26447,19 +26448,21 @@ Please pipe the document into a Node stream.\
resolve(result); resolve(result);
}; };
reader.onerror = reject; 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 fetchBlob = (imgUrl) => __awaiter(void 0, void 0, void 0, function* () {
const r = yield fetch(imgUrl); const r = yield fetch(imgUrl, {
const blob = yield r.blob(); cache: 'no-cache',
return getDataURL(blob); });
return r.blob();
}); });
const fetchText = (imgUrl) => __awaiter(void 0, void 0, void 0, function* () { const generatePDF = (imgBlobs, imgType, width, height) => __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* () {
// @ts-ignore // @ts-ignore
const pdf = new PDFDocument({ const pdf = new PDFDocument({
// compress: true, // compress: true,
@ -26469,7 +26472,7 @@ Please pipe the document into a Node stream.\
layout: 'portrait', layout: 'portrait',
}); });
if (imgType === 'png') { 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) => { imgDataUrlList.forEach((data) => {
pdf.addPage(); pdf.addPage();
pdf.image(data, { pdf.image(data, {
@ -26479,7 +26482,7 @@ Please pipe the document into a Node stream.\
}); });
} }
else { // imgType == "svg" 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) => { svgList.forEach((svg) => {
pdf.addPage(); pdf.addPage();
source(pdf, svg, 0, 0, { source(pdf, svg, 0, 0, {
@ -26492,8 +26495,9 @@ Please pipe the document into a Node stream.\
return buf.buffer; return buf.buffer;
}); });
onmessage = (e) => __awaiter(void 0, void 0, void 0, function* () { onmessage = (e) => __awaiter(void 0, void 0, void 0, function* () {
const [imgURLs, imgType, width, height,] = e.data; const [imgUrls, imgType, width, height,] = e.data;
const pdfBuf = yield generatePDF(imgURLs, imgType, width, height); const imgBlobs = yield Promise.all(imgUrls.map(url => fetchBlob(url)));
const pdfBuf = yield generatePDF(imgBlobs, imgType, width, height);
postMessage(pdfBuf, [pdfBuf]); postMessage(pdfBuf, [pdfBuf]);
}); });
@ -26571,7 +26575,7 @@ Please pipe the document into a Node stream.\
} }
/* eslint-disable no-extend-native */ /* 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. * 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; 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) { if (typeof url === 'string' && token) {
const m = url.match(TYPE_REG); const m = url.match(TYPE_REG);
console$1.debug(url, token, m);
if (m) { if (m) {
const type = m[2]; const type = m[1];
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
(_b = l[type]) === null || _b === void 0 ? void 0 : _b.call(l, token); (_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 // force to retrieve the MAGIC
switch (type) { switch (type) {
case 'midi': { case 'midi': {
const el = document.querySelectorAll('.SD7H- > button')[3]; const el = document.querySelector('button[hasaccess]');
el.click(); el.click();
break; break;
} }
@ -26731,7 +26736,7 @@ Please pipe the document into a Node stream.\
// read further error msg // read further error msg
const err = cidRes.Message; const err = cidRes.Message;
if (err.includes('no link named')) { // file not found if (err.includes('no link named')) { // file not found
throw new Error('score not in dataset'); throw new Error('Score not in dataset');
} }
else { else {
throw new Error(err); throw new Error(err);
@ -26918,12 +26923,22 @@ Please pipe the document into a Node stream.\
return locale[key]; 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 */ /* eslint-disable @typescript-eslint/no-var-requires */
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.16/webmscore.js'; const WEBMSCORE_URL = `https://cdn.jsdelivr.net/npm/webmscore@${dependencies.webmscore}/webmscore.js`;
// fonts for Chinese characters (CN) and Korean hangul (KR) // fonts for Chinese characters (CN) and Korean hangul (KR)
// JP characters are included in the CN font // 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@${dependencies['@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@${dependencies['@librescore/sf3']}/FluidR3Mono_GM.sf3`;
const SOUND_FONT_LOADED = Symbol('SoundFont loaded'); const SOUND_FONT_LOADED = Symbol('SoundFont loaded');
const initMscore = (w) => __awaiter(void 0, void 0, void 0, function* () { const initMscore = (w) => __awaiter(void 0, void 0, void 0, function* () {
if (!detectNode) { // attached to a page 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}`; return `https://librescore.org/score/${scorepack}`;
}; };
const getLibreScoreLink = (scoreinfo, _fetch = getFetch()) => __awaiter(void 0, void 0, void 0, function* () { 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); 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 /* 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: 205px !important;\n height: 38px;\n\n color: #fff;\n background: #1f74bd;\n\n cursor: pointer;\n pointer-events: auto;\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; var ICON;
(function (ICON) { (function (ICON) {
@ -27050,26 +27066,28 @@ Please pipe the document into a Node stream.\
})(ICON || (ICON = {})); })(ICON || (ICON = {}));
const getBtnContainer = () => { const getBtnContainer = () => {
var _a; var _a;
const els = [...document.querySelectorAll('*')].reverse(); const els = [...document.querySelectorAll('span')];
const el = els.find(b => { const el = els.find(b => {
var _a; var _a;
const text = ((_a = b === null || b === void 0 ? void 0 : b.textContent) === null || _a === void 0 ? void 0 : _a.replace(/\s/g, '')) || ''; 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'); 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; 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'); throw new Error('btn parent not found');
return btnParent; return btnParent;
}; };
const buildDownloadBtn = (icon) => { const buildDownloadBtn = (icon, lightTheme = false) => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.type = 'button'; btn.type = 'button';
if (lightTheme)
btn.className = 'light';
// build icon svg element // build icon svg element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('viewBox', '0 0 24 24');
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
svgPath.setAttribute('d', icon); svgPath.setAttribute('d', icon);
svgPath.setAttribute('fill', '#fff'); svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff');
svg.append(svgPath); svg.append(svgPath);
const textNode = document.createElement('span'); const textNode = document.createElement('span');
btn.append(svg, textNode); btn.append(svg, textNode);
@ -27088,6 +27106,22 @@ Please pipe the document into a Node stream.\
return getScrollParent(node.parentNode); 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; var BtnListMode;
(function (BtnListMode) { (function (BtnListMode) {
BtnListMode[BtnListMode["InPage"] = 0] = "InPage"; BtnListMode[BtnListMode["InPage"] = 0] = "InPage";
@ -27100,7 +27134,7 @@ Please pipe the document into a Node stream.\
} }
add(options) { add(options) {
var _a; 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 setText = (btn) => {
const textNode = btn.querySelector('span'); const textNode = btn.querySelector('span');
return (str) => { return (str) => {
@ -27152,8 +27186,9 @@ Please pipe the document into a Node stream.\
const newParent = document.createElement('div'); const newParent = document.createElement('div');
newParent.append(...this.list.map(e => cloneBtn(e))); newParent.append(...this.list.map(e => cloneBtn(e)));
shadow.append(newParent); shadow.append(newParent);
try { // default position
const anchorDiv = this.getBtnParent(); newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`;
void onPageRendered(this.getBtnParent).then((anchorDiv) => {
const pos = () => this._positionBtns(anchorDiv, newParent); const pos = () => this._positionBtns(anchorDiv, newParent);
pos(); pos();
// reposition btns when window resizes // reposition btns when window resizes
@ -27161,10 +27196,7 @@ Please pipe the document into a Node stream.\
// reposition btns when scrolling // reposition btns when scrolling
const scroll = getScrollParent(anchorDiv); const scroll = getScrollParent(anchorDiv);
scroll.addEventListener('scroll', pos, { passive: true }); scroll.addEventListener('scroll', pos, { passive: true });
} });
catch (err) {
console$1.error(err);
}
return btnParent; return btnParent;
} }
/** /**
@ -27271,6 +27303,15 @@ Please pipe the document into a Node stream.\
} }
else { else {
setText(i18n('BTN_ERROR')()); 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; btn.onclick = _onclick;
@ -27288,7 +27329,7 @@ Please pipe the document into a Node stream.\
class ScoreInfo { class ScoreInfo {
constructor() { constructor() {
this.RADIX = 20; this.RADIX = 20;
this.INDEX_RADIX = 40; this.INDEX_RADIX = 32;
this.store = new Map(); this.store = new Map();
} }
get idLastDigit() { get idLastDigit() {
@ -27304,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)}`; return `https://ipfs.infura.io:5001/api/v0/block/stat?arg=${this.getMsczIpfsRef(mainCid)}`;
} }
getScorepackRef(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 { class ScoreInfoInPage extends ScoreInfo {
@ -27339,13 +27380,22 @@ Please pipe the document into a Node stream.\
super(); super();
this.document = document; this.document = document;
} }
get sheet0Img() {
return this.document.querySelector('img[src*=score_]');
}
get pageCount() { 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() { get thumbnailUrl() {
var _a;
// url to the image of the first page // url to the image of the first page
const el = this.document.querySelector('link[as=image]'); 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]; return url.split('@')[0];
} }
} }
@ -27394,7 +27444,7 @@ Please pipe the document into a Node stream.\
action: BtnAction.process(() => downloadPDF(scoreinfo, new SheetInfoInPage(document)), fallback, 3 * 60 * 1000 /* 3min */), action: BtnAction.process(() => downloadPDF(scoreinfo, new SheetInfoInPage(document)), fallback, 3 * 60 * 1000 /* 3min */),
}); });
btnList.add({ btnList.add({
name: i18n('DOWNLOAD')('MusicXML'), name: i18n('DOWNLOAD')('MXL'),
action: BtnAction.mscoreWindow(scoreinfo, (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 mxl = yield score.saveMxl();
const data = new Blob([mxl]); const data = new Blob([mxl]);
@ -27474,7 +27524,9 @@ Please pipe the document into a Node stream.\
action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)), action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)),
tooltip: 'BETA', tooltip: 'BETA',
icon: ICON.LIBRESCORE, icon: ICON.LIBRESCORE,
lightTheme: true,
}); });
// eslint-disable-next-line @typescript-eslint/no-floating-promises
btnList.commit(BtnListMode.InPage); btnList.commit(BtnListMode.InPage);
}; };
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises

40
package-lock.json generated
View file

@ -1,13 +1,13 @@
{ {
"name": "musescore-downloader", "name": "musescore-downloader",
"version": "0.23.2", "version": "0.24.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@librescore/fonts": { "@librescore/fonts": {
"version": "0.2.1", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@librescore/fonts/-/fonts-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@librescore/fonts/-/fonts-0.4.0.tgz",
"integrity": "sha512-lzEk82wZWZVA4CvE2S6Wwc6EAvFZ0G6L2ExNjpJLebxAh0k/eNpHeO9a2LFwfMVUfacVWwXhDkAbmJpvUGcqzA==" "integrity": "sha512-T286OfxcQAYc/Bll9AtSP2ElggqTpoa08uY9Kgx6z1TcDVn7i7uMkKVO7sw/8aELWFNRmQE2vGQuEkmJNfWmBA=="
}, },
"@librescore/sf3": { "@librescore/sf3": {
"version": "0.3.0", "version": "0.3.0",
@ -709,18 +709,26 @@
} }
}, },
"elliptic": { "elliptic": {
"version": "6.5.3", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.11.9",
"brorand": "^1.0.1", "brorand": "^1.1.0",
"hash.js": "^1.0.0", "hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0", "hmac-drbg": "^1.0.1",
"inherits": "^2.0.1", "inherits": "^2.0.4",
"minimalistic-assert": "^1.0.0", "minimalistic-assert": "^1.0.1",
"minimalistic-crypto-utils": "^1.0.0" "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": { "emoji-regex": {
@ -2431,9 +2439,9 @@
} }
}, },
"webmscore": { "webmscore": {
"version": "0.16.1", "version": "0.18.0",
"resolved": "https://registry.npmjs.org/webmscore/-/webmscore-0.16.1.tgz", "resolved": "https://registry.npmjs.org/webmscore/-/webmscore-0.18.0.tgz",
"integrity": "sha512-ul6Kx9zCYLZIrdkPsMjE520hIoMg/jQ7WYpIulSCknUgqe7Ou5tjNR9wRY6AgocyX9wUYKqpG+IilpcCdFxDpA==" "integrity": "sha512-/J/2/KKWKST0A+Qix/SBSVtZY0C/33GQoYI3V84XEu/V3nij2ZFIcsyGQPYVr6y0HVasj6dQtvY+y7MrmYcsTw=="
}, },
"word-wrap": { "word-wrap": {
"version": "1.2.3", "version": "1.2.3",

View file

@ -1,6 +1,6 @@
{ {
"name": "musescore-downloader", "name": "musescore-downloader",
"version": "0.23.2", "version": "0.24.1",
"description": "download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱", "description": "download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱",
"main": "dist/main.js", "main": "dist/main.js",
"bin": "dist/cli.js", "bin": "dist/cli.js",
@ -37,13 +37,13 @@
"dist/main.js" "dist/main.js"
], ],
"dependencies": { "dependencies": {
"@librescore/fonts": "^0.2.1", "@librescore/fonts": "^0.4.0",
"@librescore/sf3": "^0.3.0", "@librescore/sf3": "^0.3.0",
"detect-node": "^2.0.4", "detect-node": "^2.0.4",
"inquirer": "^7.3.3", "inquirer": "^7.3.3",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"ora": "^5.1.0", "ora": "^5.1.0",
"webmscore": "^0.16.1" "webmscore": "^0.18.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",

View file

@ -5,7 +5,7 @@ div {
text-align: center; text-align: center;
align-items: center; align-items: center;
font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif; font-family: 'Inter', 'Helvetica neue', Helvetica, sans-serif;
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
background: #f6f6f6; background: #f6f6f6;
@ -22,24 +22,25 @@ div {
} }
button { button {
width: 205px !important; width: 178px !important;
height: 38px; min-width: 178px;
height: 40px;
color: #fff; color: #fff;
background: #1f74bd; background: #2e68c0;
cursor: pointer; cursor: pointer;
pointer-events: auto; pointer-events: auto;
margin-bottom: 4px; margin-bottom: 8px;
margin-right: 4px; margin-right: 8px;
padding: 4px 12px; padding: 4px 12px;
justify-content: start; justify-content: start;
align-self: center; align-self: center;
font-size: 16px; font-size: 16px;
border-radius: 2px; border-radius: 6px;
border: 0; border: 0;
display: inline-flex; display: inline-flex;
@ -48,6 +49,25 @@ button {
font-family: inherit; 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 { svg {
display: inline-block; display: inline-block;
margin-right: 5px; margin-right: 5px;

View file

@ -1,7 +1,7 @@
import { ScoreInfo } from './scoreinfo' import { ScoreInfo } from './scoreinfo'
import { loadMscore, WebMscore } from './mscore' 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 { isGmAvailable, _GM } from './gm'
import i18n from './i18n' import i18n from './i18n'
// @ts-ignore // @ts-ignore
@ -15,26 +15,27 @@ export enum ICON {
} }
const getBtnContainer = (): HTMLDivElement => { const getBtnContainer = (): HTMLDivElement => {
const els = [...document.querySelectorAll('*')].reverse() const els = [...document.querySelectorAll('span')]
const el = els.find(b => { const el = els.find(b => {
const text = b?.textContent?.replace(/\s/g, '') || '' const text = b?.textContent?.replace(/\s/g, '') || ''
return text.includes('Download') || text.includes('Print') return text.includes('Download') || text.includes('Print')
}) as HTMLDivElement | null }) as HTMLDivElement | null
const btnParent = el?.parentElement?.parentElement as HTMLDivElement | undefined 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 return btnParent
} }
const buildDownloadBtn = (icon: ICON) => { const buildDownloadBtn = (icon: ICON, lightTheme = false) => {
const btn = document.createElement('button') const btn = document.createElement('button')
btn.type = 'button' btn.type = 'button'
if (lightTheme) btn.className = 'light'
// build icon svg element // build icon svg element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
svg.setAttribute('viewBox', '0 0 24 24') svg.setAttribute('viewBox', '0 0 24 24')
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path') const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
svgPath.setAttribute('d', icon) svgPath.setAttribute('d', icon)
svgPath.setAttribute('fill', '#fff') svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff')
svg.append(svgPath) svg.append(svgPath)
const textNode = document.createElement('span') 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 { interface BtnOptions {
readonly name: string; readonly name: string;
readonly action: BtnAction; readonly action: BtnAction;
readonly disabled?: boolean; readonly disabled?: boolean;
readonly tooltip?: string; readonly tooltip?: string;
readonly icon?: ICON; readonly icon?: ICON;
readonly lightTheme?: boolean;
} }
export enum BtnListMode { export enum BtnListMode {
@ -76,7 +93,7 @@ export class BtnList {
constructor (private getBtnParent: () => HTMLDivElement = getBtnContainer) { } constructor (private getBtnParent: () => HTMLDivElement = getBtnContainer) { }
add (options: BtnOptions): BtnElement { 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 setText = (btn: BtnElement) => {
const textNode = btn.querySelector('span') const textNode = btn.querySelector('span')
return (str: string): void => { return (str: string): void => {
@ -139,8 +156,10 @@ export class BtnList {
newParent.append(...this.list.map(e => cloneBtn(e))) newParent.append(...this.list.map(e => cloneBtn(e)))
shadow.append(newParent) shadow.append(newParent)
try { // default position
const anchorDiv = this.getBtnParent() newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`
void onPageRendered(this.getBtnParent).then((anchorDiv: HTMLDivElement) => {
const pos = () => this._positionBtns(anchorDiv, newParent) const pos = () => this._positionBtns(anchorDiv, newParent)
pos() pos()
@ -150,9 +169,7 @@ export class BtnList {
// reposition btns when scrolling // reposition btns when scrolling
const scroll = getScrollParent(anchorDiv) const scroll = getScrollParent(anchorDiv)
scroll.addEventListener('scroll', pos, { passive: true }) scroll.addEventListener('scroll', pos, { passive: true })
} catch (err) { })
console.error(err)
}
return btnParent return btnParent
} }
@ -271,6 +288,17 @@ export namespace BtnAction {
setText(name) setText(name)
} else { } else {
setText(i18n('BTN_ERROR')()) 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'))
} }
} }

View file

@ -9,7 +9,8 @@ import { fetchMscz, setMscz, MSCZ_URL_SYM } from './mscz'
import { loadMscore, INDV_DOWNLOADS, WebMscore } from './mscore' import { loadMscore, INDV_DOWNLOADS, WebMscore } from './mscore'
import { ScoreInfo, ScoreInfoHtml, ScoreInfoObj, getActualId } from './scoreinfo' import { ScoreInfo, ScoreInfoHtml, ScoreInfoObj, getActualId } from './scoreinfo'
import { getLibreScoreLink } from './librescore-link' 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' import i18n from './i18n'
const inquirer: typeof import('inquirer') = require('inquirer') const inquirer: typeof import('inquirer') = require('inquirer')
@ -29,6 +30,12 @@ interface Params {
} }
void (async () => { 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 // Determine platform and paste message
const platform = os.platform() const platform = os.platform()
let pasteMessage = '' let pasteMessage = ''
@ -55,7 +62,7 @@ void (async () => {
(input.endsWith(EXT) && fs.statSync(input).isFile()) (input.endsWith(EXT) && fs.statSync(input).isFile())
) )
}, },
default: process.argv[2], default: arg,
}) })
const isLocalFile = fileInit.endsWith(EXT) const isLocalFile = fileInit.endsWith(EXT)
@ -82,6 +89,7 @@ void (async () => {
// initiate LibreScore link request // initiate LibreScore link request
librescoreLink = getLibreScoreLink(scoreinfo) librescoreLink = getLibreScoreLink(scoreinfo)
librescoreLink.catch(() => '') // silence this unhandled Promise rejection
// print a blank line to the terminal // print a blank line to the terminal
console.log() console.log()
@ -126,6 +134,10 @@ void (async () => {
spinner.info('Score loaded by webmscore') spinner.info('Score loaded by webmscore')
} catch (err) { } catch (err) {
spinner.fail(err.message) spinner.fail(err.message)
spinner.info(
'Send your URL to the #dataset-bugs channel in the LibreScore Community Discord server:\n ' +
DISCORD_URL,
)
return return
} }
spinner.succeed('OK\n') spinner.succeed('OK\n')
@ -184,4 +196,11 @@ void (async () => {
}), }),
) )
spinner.succeed('OK') 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.`))
}
}
})() })()

View file

@ -6,7 +6,7 @@ import { console } from './utils'
type FileType = 'img' | 'mp3' | 'midi' 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. * I know this is super hacky.
@ -31,8 +31,9 @@ const magicHookConstr = (() => {
const token = init?.headers?.Authorization const token = init?.headers?.Authorization
if (typeof url === 'string' && token) { if (typeof url === 'string' && token) {
const m = url.match(TYPE_REG) const m = url.match(TYPE_REG)
console.debug(url, token, m)
if (m) { if (m) {
const type = m[2] const type = m[1]
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
l[type]?.(token) l[type]?.(token)
} }
@ -78,7 +79,7 @@ const getApiAuth = async (type: FileType, index: number): Promise<string> => {
// force to retrieve the MAGIC // force to retrieve the MAGIC
switch (type) { switch (type) {
case 'midi': { case 'midi': {
const el = document.querySelectorAll('.SD7H- > button')[3] as HTMLButtonElement const el = document.querySelector('button[hasaccess]') as HTMLButtonElement
el.click() el.click()
break break
} }

View file

@ -2,8 +2,9 @@ import { assertRes, getFetch } from './utils'
import { getMainCid } from './mscz' import { getMainCid } from './mscz'
import { ScoreInfo } from './scoreinfo' import { ScoreInfo } from './scoreinfo'
const _getLink = (scorepack: string) => { const _getLink = (indexingInfo: string) => {
return `https://librescore.org/score/${scorepack}` const { scorepack } = JSON.parse(indexingInfo)
return `https://librescore.org/score/${scorepack as string}`
} }
export const getLibreScoreLink = async (scoreinfo: ScoreInfo, _fetch = getFetch()): Promise<string> => { export const getLibreScoreLink = async (scoreinfo: ScoreInfo, _fetch = getFetch()): Promise<string> => {

View file

@ -38,7 +38,7 @@ const main = (): void => {
}) })
btnList.add({ btnList.add({
name: i18n('DOWNLOAD')('MusicXML'), name: i18n('DOWNLOAD')('MXL'),
action: BtnAction.mscoreWindow(scoreinfo, async (w, score) => { action: BtnAction.mscoreWindow(scoreinfo, async (w, score) => {
const mxl = await score.saveMxl() const mxl = await score.saveMxl()
const data = new Blob([mxl]) const data = new Blob([mxl])
@ -137,8 +137,10 @@ const main = (): void => {
action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)), action: BtnAction.openUrl(() => getLibreScoreLink(scoreinfo)),
tooltip: 'BETA', tooltip: 'BETA',
icon: ICON.LIBRESCORE, icon: ICON.LIBRESCORE,
lightTheme: true,
}) })
// eslint-disable-next-line @typescript-eslint/no-floating-promises
btnList.commit(BtnListMode.InPage) btnList.commit(BtnListMode.InPage)
} }

View file

@ -8,6 +8,7 @@
// @version %VERSION% // @version %VERSION%
// @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱 // @description download sheet music from musescore.com for free, no login or Musescore Pro required | 免登录、免 Musescore Pro免费下载 musescore.com 上的曲谱
// @author Xmader // @author Xmader
// @icon https://librescore.org/img/icons/logo.svg
// @match https://musescore.com/*/* // @match https://musescore.com/*/*
// @match https://s.musescore.com/*/* // @match https://s.musescore.com/*/*
// @license MIT // @license MIT

View file

@ -6,14 +6,15 @@ import { fetchData } from './utils'
import { ScoreInfo } from './scoreinfo' import { ScoreInfo } from './scoreinfo'
import isNodeJs from 'detect-node' import isNodeJs from 'detect-node'
import i18n from './i18n' import i18n from './i18n'
import { dependencies as depVers } from '../package.json'
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.16/webmscore.js' const WEBMSCORE_URL = `https://cdn.jsdelivr.net/npm/webmscore@${depVers.webmscore}/webmscore.js`
// fonts for Chinese characters (CN) and Korean hangul (KR) // fonts for Chinese characters (CN) and Korean hangul (KR)
// JP characters are included in the CN font // 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') const SOUND_FONT_LOADED = Symbol('SoundFont loaded')
export type WebMscore = import('webmscore').default export type WebMscore = import('webmscore').default

View file

@ -48,7 +48,7 @@ export const loadMsczUrl = async (scoreinfo: ScoreInfo, _fetch = getFetch()): Pr
// read further error msg // read further error msg
const err = cidRes.Message const err = cidRes.Message
if (err.includes('no link named')) { // file not found if (err.includes('no link named')) { // file not found
throw new Error('score not in dataset') throw new Error('Score not in dataset')
} else { } else {
throw new Error(err) throw new Error(err)
} }

2
src/msdl/cli.js Normal file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env node
require("musescore-downloader/dist/cli.js")

19
src/msdl/package.json Normal file
View 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
View 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,
}
}

View file

@ -4,7 +4,7 @@ import { getMainCid } from './mscz'
export abstract class ScoreInfo { export abstract class ScoreInfo {
private readonly RADIX = 20; private readonly RADIX = 20;
private readonly INDEX_RADIX = 40; private readonly INDEX_RADIX = 32;
abstract id: number; abstract id: number;
abstract title: string; abstract title: string;
@ -28,7 +28,7 @@ export abstract class ScoreInfo {
} }
public getScorepackRef (mainCid: string): string { 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 { export class SheetInfoInPage extends SheetInfo {
constructor (private document: Document) { super() } constructor (private document: Document) { super() }
private get sheet0Img (): HTMLImageElement | null {
return this.document.querySelector('img[src*=score_]')
}
get pageCount (): number { 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 { get thumbnailUrl (): string {
// url to the image of the first page // url to the image of the first page
const el = this.document.querySelector('link[as=image]') as HTMLLinkElement const el = this.document.querySelector<HTMLLinkElement>('link[as=image]')
const url = el.href const url = (el?.href || this.sheet0Img?.src) as string
return url.split('@')[0] return url.split('@')[0]
} }
} }

View file

@ -2,6 +2,8 @@
import isNodeJs from 'detect-node' import isNodeJs from 'detect-node'
import { isGmAvailable, _GM } from './gm' import { isGmAvailable, _GM } from './gm'
export const DISCORD_URL = 'https://discord.gg/gSsTUvJmD8'
export const escapeFilename = (s: string): string => { export const escapeFilename = (s: string): string => {
return s.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_') return s.replace(/[\s<>:{}"/\\|?*~.\0\cA-\cZ]+/g, '_')
} }

View file

@ -6,7 +6,9 @@ import SVGtoPDF from 'svg-to-pdfkit'
type ImgType = 'svg' | 'png' 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) => { return new Promise((resolve, reject) => {
const reader = new FileReader() const reader = new FileReader()
reader.onload = (): void => { reader.onload = (): void => {
@ -14,22 +16,22 @@ const getDataURL = (blob: Blob): Promise<string> => {
resolve(result as string) resolve(result as string)
} }
reader.onerror = reject reader.onerror = reject
reader.readAsDataURL(blob) if (type === 'dataUrl') {
reader.readAsDataURL(blob)
} else {
reader.readAsText(blob)
}
}) })
} }
const fetchDataURL = async (imgUrl: string): Promise<string> => { const fetchBlob = async (imgUrl: string): Promise<Blob> => {
const r = await fetch(imgUrl) const r = await fetch(imgUrl, {
const blob = await r.blob() cache: 'no-cache',
return getDataURL(blob) })
return r.blob()
} }
const fetchText = async (imgUrl: string): Promise<string> => { const generatePDF = async (imgBlobs: Blob[], imgType: ImgType, width: number, height: number): Promise<ArrayBuffer> => {
const r = await fetch(imgUrl)
return r.text()
}
const generatePDF = async (imgURLs: string[], imgType: ImgType, width: number, height: number): Promise<ArrayBuffer> => {
// @ts-ignore // @ts-ignore
const pdf = new (PDFDocument as typeof import('pdfkit'))({ const pdf = new (PDFDocument as typeof import('pdfkit'))({
// compress: true, // compress: true,
@ -40,7 +42,7 @@ const generatePDF = async (imgURLs: string[], imgType: ImgType, width: number, h
}) })
if (imgType === 'png') { 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) => { imgDataUrlList.forEach((data) => {
pdf.addPage() pdf.addPage()
@ -50,7 +52,7 @@ const generatePDF = async (imgURLs: string[], imgType: ImgType, width: number, h
}) })
}) })
} else { // imgType == "svg" } 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) => { svgList.forEach((svg) => {
pdf.addPage() pdf.addPage()
@ -70,14 +72,16 @@ export type PDFWorkerMessage = [string[], ImgType, number, number];
onmessage = async (e): Promise<void> => { onmessage = async (e): Promise<void> => {
const [ const [
imgURLs, imgUrls,
imgType, imgType,
width, width,
height, height,
] = e.data as PDFWorkerMessage ] = e.data as PDFWorkerMessage
const imgBlobs = await Promise.all(imgUrls.map(url => fetchBlob(url)))
const pdfBuf = await generatePDF( const pdfBuf = await generatePDF(
imgURLs, imgBlobs,
imgType, imgType,
width, width,
height, height,

View file

@ -41,7 +41,7 @@ new Promise(resolve => {
const loaderOutro = '})()'.repeat(stackN) const loaderOutro = '})()'.repeat(stackN)
const mockUrl = "https://c.amazon-adsystem.com/aax2/apstag.js" 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 => { }).then(d => {
d.style.display = 'none'; d.style.display = 'none';
d.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; d.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';