Compare commits

...

88 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
Xmader 86c15d55fe
v0.23.2 2021-01-07 02:47:37 -05:00
Xmader 1faaf660c4
fix: pass the scroll event through the btns background
https://stackoverflow.com/questions/1009753/
2021-01-07 02:45:58 -05:00
Xmader 8250d80d4b
fix: btns position relative to the entire document instead of viewport 2021-01-07 02:38:06 -05:00
Xmader d9d09c4e8f
perf: improve scroll performance
https://developers.google.com/web/updates/2016/06/passive-event-listeners
2021-01-07 01:57:13 -05:00
Xmader 20704eba75
chore: upgrade webmscore 2021-01-05 09:13:00 -05:00
Xmader 3b329301c1
v0.23.1 2021-01-02 10:47:45 -05:00
Xmader 86c5429dce
feat(i18n): add Chinese translations 2021-01-02 10:46:14 -05:00
Xmader 7f860faf66
feat(i18n): update translations
Co-authored-by:  ClaudioAmato <32262813+ClaudioAmato@users.noreply.github.com>
2021-01-02 10:25:38 -05:00
Xmader de5ff6ff98
doc: add Italian to readme 2021-01-02 09:54:42 -05:00
Xmader 1b0d3d2eae
feat(i18n): update translations
Co-authored-by:  ClaudioAmato <32262813+ClaudioAmato@users.noreply.github.com>
2021-01-02 09:50:35 -05:00
Claudio Amato 28ac964ba3
Added italian Readme 2021-01-02 15:22:47 +01:00
Xmader 44f7edfaa3
feat(i18n): add Italian language 2021-01-02 07:41:46 -05:00
Xmader 46879ffacd
feat(i18n): add Italian language 2021-01-02 07:34:31 -05:00
Xmader 8945a53896
refactor: i18n "View in LibreScore" 2021-01-02 02:44:43 -05:00
Xmader 60d842bac7
feat: add View in LibreScore in msdl cli 2021-01-02 02:34:19 -05:00
Xmader bca9f5e150
chore: update license year 2021-01-02 01:11:54 -05:00
PaoloC95 519a5c3671
it.ts
Italian Language
2021-01-01 21:08:19 +01:00
28 changed files with 643 additions and 130 deletions

View File

@ -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",

View File

@ -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=()

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019-2020 Xmader
Copyright (c) 2019-2021 Xmader
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

123
README.md
View File

@ -1,7 +1,7 @@
# musescore-downloader
**English** | [简体中文](#musescore-downloader-1) | [Español](#musescore-downloader-2)
**English** | [简体中文](#musescore-downloader-1) | [Español](#musescore-downloader-2) | [Italian](#musescore-downloader-3)
> download sheet music from musescore.com for free, no login or Musescore Pro required
@ -243,6 +243,7 @@ En tercer lugar, la propiedad de los derechos de autor de los contenidos de muse
Si no podemos ver pruebas de que musescore.com realmente paga la tarifa de licencia a los propietarios de los derechos de autor, podemos pensar que es solo una excusa para obtener ganancias robando.
> utilizo ilegalmente nuestra API privada con contenido de música licenciada.
No, el documento de la API está en https://developers.musescore.com/.
@ -251,3 +252,123 @@ No, el documento de la API está en https://developers.musescore.com/.
**Lanzaré una alternativa de código abierto (GPLv3), sin servidor, offline, y totalmente gratuita a musescore.com, [LibreScore](https://github.com/LibreScore). ETodos son bienvenidos a unirse al desarrollo del proyecto abriendo un problema o [enviándome un correo electrónico.](mailto:i@xmader.com).**
**Además, estoy desarrollando musescore.js. Podría convertir un archivo mscz en cualquier formato que admita el software Musescore, y en el navegador.** Dado que el software Musescore es de código abierto bajo [GPL](https://github.com/musescore/MuseScore/blob/master/LICENSE.GPL), Podría traducir el código fuente a js o compilarlo en asm.js/WASM.
---
# musescore-downloader
[English](#musescore-downloader) | [简体中文](#musescore-downloader-1) | [Español](#musescore-downloader-2) | **Italian**
> Scarica spartiti da musescore.com gratuitamente, non è richisto login o Musescore Pro.
**Avvia questo progetto su [Github](https://github.com/Xmader/musescore-downloader) e [Gitlab](https://gitlab.com/Xmader/musescore-downloader)** (Mirror)
[![Discord](https://img.shields.io/discord/774491656643674122?color=7289da&label=Discord&logo=discord)](https://discord.gg/DKu7cUZ4XQ)
Hai bisogno di datset di musescore.com per l'analisi/machine learning? Prova [musescore-dataset](https://github.com/Xmader/musescore-dataset).
![](https://cdn.statically.io/gh/Xmader/musescore-downloader/master/screenshot.png?env=dev)
## Utilizzo leale
Solo per scopi di ricerca e studio
## In breve
Di recente è necessario un account Musescore Pro ($6,99/mese) per scaricare spartiti da musescore.com.
(Tuttavia, alcuni mesi fa, era possibile scaricare gratuitamente.)
La società Musescore ha affermato che a causa di copyright e licenze, devono pagare i proprietari del copyright.
Molte musiche su musescore.com sono già di **Pubblico Dominio**, il che significa che o l'autore le ha pubblicate in pubblico dominio o l'autore è morto da oltre 70 anni.
Devono pagare anche i compositori che sono morti centinaia di anni fa?
*Aggiornamento: gli spartiti di Dominio Pubblico, ora possono essere scaricati senza Musescore Pro, ma hai ancora bisogno di un account per poter scaricare.*
Inoltre, ci sono molti autori di spartiti su musescore.com che hanno creato le proprie canzoni e le hanno pubblicate sotto licenza [CC-BY-**NC** (Creative Commons Attribution-**NonCommercial**)](https://creativecommons.org/licenses/by-nc/4.0/).
È illegale venderli **a scopo di lucro**?
*Nota: Mettere annunci (per vendere Musescore Pro) sul sito web significa anche che lo usano per generare entrate.*
Questo è assolutamente inaccettabile e l'unico scopo è trarre profitto dal furto.
C'è un articolo sul loro sito web: [Il download degli spartiti diventa parte dell'abbonamento Pro](https://musescore.com/groups/improving-musescore-com/discuss/5044610)
## Installazione
### Utilizzo della CLI
(consigliato)
1. Installa Node.js LTS (https://nodejs.org/)
2. Apri il terminale CMD o Powershell
3. Digita `npx msdl`, e premi invio
(`npx msdl` eseguirà sempre l'ultima versione)
4. Seguire le istruzioni
[codice sorgente](/src/cli.ts)
### Installa come Userscript
Questo script è disponibile come [Userscript](https://en.wikipedia.org/wiki/Userscript). Per utilizzare questo Userscript, è necessario prima installare un [gestore di script utente].(https://greasyfork.org/en/help/installing-user-scripts), come Tampermonkey.
1. Installa [Tampermonkey](https://www.tampermonkey.net/)
2. ~~Installa da [Greasy Fork](https://greasyfork.org/scripts/391931).~~ [#42](https://github.com/Xmader/musescore-downloader/issues/42)
Installa lo script da <https://msdl.librescore.org/install.user.js>
### Installa come estensione web
Il metodo alternativo consiste nell'installare questo script come estensione per Chrome o Firefox.
Puoi installare l'estensione del browser direttamente da [addons.mozilla.org (per Firefox)](https://addons.mozilla.org/en-US/firefox/addon/musescore-downloader/) o dal [web store di Chrome (per browser basati su Chrome e Chromium)](https://chrome.google.com/webstore/detail/mhdlcdhakmmikknpefblmnhdhjloanjc).
La versione aggiornata può essere trovata nella pagina [Github Releases](https://github.com/Xmader/musescore-downloader/releases).
## Istruzioni per il building
Assicurati di avere installato [Node.js](https://nodejs.org/en/).
```bash
npm install
npm run build # build come script utente
npm run pack:ext # pack Web Extension
```
## Mirrors
* Visualizza questo progetto su [Github](https://github.com/Xmader/musescore-downloader) (Repo principale) | [Gitlab](https://gitlab.com/Xmader/musescore-downloader) (Mirror)
* Questo repo è disponibile anche su IPFS per evitare la rimozione DMCA: [ipns://msdl.librescore.org](https://ipfs.io/ipns/msdl.librescore.org/)
## Feedback
[Problemi con GitHub](https://github.com/Xmader/musescore-downloader/issues)
## Licenza
MIT
## Informazioni sulla richiesta di rimozione
Ho ricevuto un'e-mail di [richiesta di rimozione](https://github.com/Xmader/musescore-downloader/issues/5)da uno degli sviluppatori di Musescore, ma ho qualcosa da ridire.
> Non tutti i contenuti di pubblico dominio su musescore.com sono concessi in licenza dai principali editori musicali (Alfred, EMI, Sony, ecc.). State distribuendo gratuitamente contenuti musicali con licenza da Musescore.com violando i loro diritti.
In primo luogo, se violi i diritti dei principali editori musicali, la richiesta di rimozione dovrebbe essere inviata da loro invece che dagli sviluppatori di Musescore.
In secondo luogo, musescore.com non è un semplice sito di condivisione di musica. Gli autori di spartiti devono trascrivere e riorganizzare le canzoni originali in spartiti, non solo copiare file da qualche altra parte su musescore.com. Di conseguenza, la licenza dovrebbe concentrarsi sui diritti di trascrizione / riorganizzazione degli autori di spartiti, invece che sui diritti di condivisione della musica su alcuni siti web.
In terzo luogo, la proprietà del copyright dei contenuti su musescore.com non è chiara. Non tutte le canzoni di pubblico dominio su musescore.com sono di proprietà dei principali editori musicali. Ci sono molti piccoli editori musicali e cantautori indipendenti. Le canzoni potrebbero essere concesse in licenza con licenze gratuite come Creative Commons. Inoltre, ci sono molti autori che hanno creato le proprie canzoni e pubblicato gli spartiti su musescore.com; Musescore.com paga questi autori?
Se ci sono prove che musescore.com paga davvero la tassa di licenza ai proprietari del copyright, potremmo pensare che sia solo una scusa per ottenere profitto dal furto.
> utilizzi illegalmente la nostra API privata con contenuti musicali con licenza.
No, il documento API è su https://developers.musescore.com/.
**Avvierò un'alternativa open source (GPLv3), serverless, offline-first, frontend-first e totalmente gratuita a musescore.com, [LibreScore](https://github.com/LibreScore). Tutti sono invitati a partecipare allo sviluppo del progetto aprendo una issue o [inviandomi un'e-mail](mailto:i@xmader.com).**
**Inoltre, sto sviluppando musescore.js. Potrebbe convertire un file mscz in qualsiasi formato supportato dal software Musescore e nel browser.** Poiché il software Musescore è open source sotto [GPL](https://github.com/musescore/MuseScore/blob/master/LICENSE.GPL), potrei tradurre il codice sorgente in js o compilarlo in asm.js/WASM.
---

221
dist/main.js vendored
View File

@ -5,13 +5,13 @@
// @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.0
// @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/*/*
// @match https://s.musescore.com/*/*
// @license MIT
// @copyright Copyright (c) 2019-2020 Xmader
// @copyright Copyright (c) 2019-2021 Xmader
// @grant unsafeWindow
// @grant GM.registerMenuCommand
// @grant GM.addElement
@ -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);
@ -26790,6 +26795,9 @@ Please pipe the document into a Node stream.\
'IND_PARTS_TOOLTIP'() {
return 'Download individual parts (BETA)';
},
'VIEW_IN_LIBRESCORE'() {
return 'View in LibreScore';
},
'FULL_SCORE'() {
return 'Full score';
},
@ -26803,7 +26811,7 @@ Please pipe the document into a Node stream.\
return '❌¡Descarga Fallida!';
},
'DEPRECATION_NOTICE'(btnName) {
return `¡OBSOLETO!\nParecer ser que \`${btnName}\` no funciona correctamente, use \`Partes Indivduales\` en su lugar.\n(Esto todavía puede funcionar. Haga click en \`Aceptar\` para continuar.)`;
return `¡OBSOLETO!\nUtilizar \`${btnName}\` dentro de \`Partes Indivduales\` en su lugar.\n(Esto todavía puede funcionar. Pulsa \`Aceptar\` para continuar.)`;
},
'DOWNLOAD'(fileType) {
return `Descargar ${fileType}`;
@ -26817,14 +26825,79 @@ Please pipe the document into a Node stream.\
'IND_PARTS_TOOLTIP'() {
return 'Descargar partes individuales (BETA)';
},
'VIEW_IN_LIBRESCORE'() {
return 'Visualizar en LibreScore';
},
'FULL_SCORE'() {
return 'Partitura Completa';
},
});
var it = createLocale({
'PROCESSING'() {
return 'Caricamento…';
},
'BTN_ERROR'() {
return '❌Download Fallito!';
},
'DEPRECATION_NOTICE'(btnName) {
return `¡DEPRECATO!\nUtilizzare \`${btnName}\` all'interno di \`Parti Indivduali\`.\n(Qusto potrebbe funzionare. Cliccare \`Ok\` per continuare.)`;
},
'DOWNLOAD'(fileType) {
return `Scaricare ${fileType}`;
},
'DOWNLOAD_AUDIO'(fileType) {
return `Scaricare ${fileType} Audio`;
},
'IND_PARTS'() {
return 'Parti Singole';
},
'IND_PARTS_TOOLTIP'() {
return 'Scaricare Parti Singole (BETA)';
},
'VIEW_IN_LIBRESCORE'() {
return 'Visualizzare in LibreScore';
},
'FULL_SCORE'() {
return 'Spartito Completo';
},
});
var zh = createLocale({
'PROCESSING'() {
return '处理中…';
},
'BTN_ERROR'() {
return '❌下载失败!';
},
'DEPRECATION_NOTICE'(btnName) {
return `不建议使用\n请使用 \`单独分谱\` 里的 \`${btnName}\` 按钮代替\n(这也许仍会起作用。单击\`确定\`以继续。)`;
},
'DOWNLOAD'(fileType) {
return `下载 ${fileType}`;
},
'DOWNLOAD_AUDIO'(fileType) {
return `下载 ${fileType} 音频`;
},
'IND_PARTS'() {
return '单独分谱';
},
'IND_PARTS_TOOLTIP'() {
return '下载单独分谱 (BETA)';
},
'VIEW_IN_LIBRESCORE'() {
return '在 LibreScore 中查看';
},
'FULL_SCORE'() {
return '完整乐谱';
},
});
const locales = ((l) => Object.freeze(l))({
en,
es,
it,
zh,
});
// detect browser language
const lang = (() => {
@ -26850,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
@ -26954,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* () {
@ -26973,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) {
@ -26982,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);
@ -27020,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";
@ -27032,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) => {
@ -27062,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');
@ -27080,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;
}
/**
@ -27200,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;
@ -27217,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() {
@ -27233,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 {
@ -27268,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];
}
}
@ -27323,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]);
@ -27399,11 +27520,13 @@ Please pipe the document into a Node stream.\
})),
});
btnList.add({
name: 'View in LibreScore',
name: i18n('VIEW_IN_LIBRESCORE')(),
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
View File

@ -1,13 +1,13 @@
{
"name": "musescore-downloader",
"version": "0.23.0",
"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",

View File

@ -1,6 +1,6 @@
{
"name": "musescore-downloader",
"version": "0.23.0",
"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",

View File

@ -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;

View File

@ -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'))
}
}

View File

@ -8,7 +8,9 @@ import os from 'os'
import { fetchMscz, setMscz, MSCZ_URL_SYM } from './mscz'
import { loadMscore, INDV_DOWNLOADS, WebMscore } from './mscore'
import { ScoreInfo, ScoreInfoHtml, ScoreInfoObj, getActualId } from './scoreinfo'
import { escapeFilename } from './utils'
import { getLibreScoreLink } from './librescore-link'
import { escapeFilename, DISCORD_URL } from './utils'
import { isNpx, getVerInfo, getSelfVer } from './npm-data'
import i18n from './i18n'
const inquirer: typeof import('inquirer') = require('inquirer')
@ -28,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 = ''
@ -38,6 +46,7 @@ void (async () => {
} // For MacOS, no hint is needed because the paste shortcut is universal.
let scoreinfo: ScoreInfo
let librescoreLink: Promise<string> | undefined
// ask for the page url or path to local file
const { fileInit } = await inquirer.prompt<Params>({
type: 'input',
@ -53,7 +62,7 @@ void (async () => {
(input.endsWith(EXT) && fs.statSync(input).isFile())
)
},
default: process.argv[2],
default: arg,
})
const isLocalFile = fileInit.endsWith(EXT)
@ -77,7 +86,13 @@ void (async () => {
default: true,
})
if (!confirmed) return
console.log() // print a blank line to the terminal
// initiate LibreScore link request
librescoreLink = getLibreScoreLink(scoreinfo)
librescoreLink.catch(() => '') // silence this unhandled Promise rejection
// print a blank line to the terminal
console.log()
} else {
scoreinfo = new ScoreInfoObj(0, path.basename(fileInit, EXT))
}
@ -105,6 +120,11 @@ void (async () => {
if (!isLocalFile) {
spinner.info(`File URL: ${scoreinfo.store.get(MSCZ_URL_SYM) as string}`)
}
if (librescoreLink) {
try {
spinner.info(`${i18n('VIEW_IN_LIBRESCORE')()}: ${await librescoreLink}`)
} catch { } // it doesn't affect the main feature
}
spinner.start()
// load score using webmscore
@ -114,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')
@ -172,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.`))
}
}
})()

View File

@ -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
}

View File

@ -27,6 +27,10 @@ export default createLocale({
return 'Download individual parts (BETA)' as const
},
'VIEW_IN_LIBRESCORE' () {
return 'View in LibreScore' as const
},
'FULL_SCORE' () {
return 'Full score' as const
},

View File

@ -10,7 +10,7 @@ export default createLocale({
},
'DEPRECATION_NOTICE' (btnName: string) {
return `¡OBSOLETO!\nParecer ser que \`${btnName}\` no funciona correctamente, use \`Partes Indivduales\` en su lugar.\n(Esto todavía puede funcionar. Haga click en \`Aceptar\` para continuar.)` as const
return `¡OBSOLETO!\nUtilizar \`${btnName}\` dentro de \`Partes Indivduales\` en su lugar.\n(Esto todavía puede funcionar. Pulsa \`Aceptar\` para continuar.)` as const
},
'DOWNLOAD' <T extends string> (fileType: T) {
@ -27,6 +27,10 @@ export default createLocale({
return 'Descargar partes individuales (BETA)' as const
},
'VIEW_IN_LIBRESCORE' () {
return 'Visualizar en LibreScore' as const
},
'FULL_SCORE' () {
return 'Partitura Completa' as const
},

View File

@ -3,6 +3,8 @@ import isNodeJs from 'detect-node'
import en from './en'
import es from './es'
import it from './it'
import zh from './zh'
export interface LOCALE {
'PROCESSING' (): string;
@ -16,12 +18,16 @@ export interface LOCALE {
'IND_PARTS' (): string;
'IND_PARTS_TOOLTIP' (): string;
'VIEW_IN_LIBRESCORE' (): string;
'FULL_SCORE' (): string;
}
const locales = (<L extends { [n: string]: LOCALE } /** type checking */> (l: L) => Object.freeze(l))({
en,
es,
it,
zh,
})
// detect browser language

37
src/i18n/it.ts Normal file
View File

@ -0,0 +1,37 @@
import { createLocale } from './utils'
export default createLocale({
'PROCESSING' () {
return 'Caricamento…' as const
},
'BTN_ERROR' () {
return '❌Download Fallito!' as const
},
'DEPRECATION_NOTICE' (btnName: string) {
return `¡DEPRECATO!\nUtilizzare \`${btnName}\` all'interno di \`Parti Indivduali\`.\n(Qusto potrebbe funzionare. Cliccare \`Ok\` per continuare.)` as const
},
'DOWNLOAD' <T extends string> (fileType: T) {
return `Scaricare ${fileType}` as const
},
'DOWNLOAD_AUDIO' <T extends string> (fileType: T) {
return `Scaricare ${fileType} Audio` as const
},
'IND_PARTS' () {
return 'Parti Singole' as const
},
'IND_PARTS_TOOLTIP' () {
return 'Scaricare Parti Singole (BETA)' as const
},
'VIEW_IN_LIBRESCORE' () {
return 'Visualizzare in LibreScore' as const
},
'FULL_SCORE' () {
return 'Spartito Completo' as const
},
})

37
src/i18n/zh.ts Normal file
View File

@ -0,0 +1,37 @@
import { createLocale } from './utils'
export default createLocale({
'PROCESSING' () {
return '处理中…' as const
},
'BTN_ERROR' () {
return '❌下载失败!' as const
},
'DEPRECATION_NOTICE' (btnName: string) {
return `不建议使用\n请使用 \`单独分谱\` 里的 \`${btnName}\` 按钮代替\n这也许仍会起作用。单击\`确定\`以继续。)` as const
},
'DOWNLOAD' <T extends string> (fileType: T) {
return `下载 ${fileType}` as const
},
'DOWNLOAD_AUDIO' <T extends string> (fileType: T) {
return `下载 ${fileType} 音频` as const
},
'IND_PARTS' () {
return '单独分谱' as const
},
'IND_PARTS_TOOLTIP' () {
return '下载单独分谱 (BETA)' as const
},
'VIEW_IN_LIBRESCORE' () {
return '在 LibreScore 中查看' as const
},
'FULL_SCORE' () {
return '完整乐谱' as const
},
})

View File

@ -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> => {

View File

@ -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])
@ -133,12 +133,14 @@ const main = (): void => {
})
btnList.add({
name: 'View in LibreScore',
name: i18n('VIEW_IN_LIBRESCORE')(),
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)
}

View File

@ -8,10 +8,11 @@
// @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
// @copyright Copyright (c) 2019-2020 Xmader
// @copyright Copyright (c) 2019-2021 Xmader
// @grant unsafeWindow
// @grant GM.registerMenuCommand
// @grant GM.addElement

View File

@ -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

View File

@ -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
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 {
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]
}
}

View File

@ -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, '_')
}

View File

@ -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,

View File

@ -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==';