Compare commits
No commits in common. "master" and "v0.21.3" have entirely different histories.
28 changed files with 158 additions and 886 deletions
|
@ -20,8 +20,6 @@
|
||||||
"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",
|
||||||
|
|
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
|
@ -53,15 +53,6 @@ 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
|
||||||
|
@ -78,7 +69,6 @@ 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
|
||||||
|
@ -98,7 +88,6 @@ 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=()
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019-2021 Xmader
|
Copyright (c) 2019-2020 Xmader
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
123
README.md
123
README.md
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
# musescore-downloader
|
# musescore-downloader
|
||||||
|
|
||||||
**English** | [简体中文](#musescore-downloader-1) | [Español](#musescore-downloader-2) | [Italian](#musescore-downloader-3)
|
**English** | [简体中文](#musescore-downloader-1) | [Español](#musescore-downloader-2)
|
||||||
|
|
||||||
> download sheet music from musescore.com for free, no login or Musescore Pro required
|
> download sheet music from musescore.com for free, no login or Musescore Pro required
|
||||||
|
|
||||||
|
@ -243,7 +243,6 @@ 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.
|
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.
|
> utilizo ilegalmente nuestra API privada con contenido de música licenciada.
|
||||||
|
|
||||||
No, el documento de la API está en https://developers.musescore.com/.
|
No, el documento de la API está en https://developers.musescore.com/.
|
||||||
|
@ -252,123 +251,3 @@ 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).**
|
**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.
|
**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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
333
dist/main.js
vendored
333
dist/main.js
vendored
|
@ -5,13 +5,13 @@
|
||||||
// @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.24.1
|
// @version 0.21.3
|
||||||
// @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/*/*
|
||||||
// @match https://s.musescore.com/*/*
|
// @match https://s.musescore.com/*/*
|
||||||
// @license MIT
|
// @license MIT
|
||||||
// @copyright Copyright (c) 2019-2021 Xmader
|
// @copyright Copyright (c) 2019-2020 Xmader
|
||||||
// @grant unsafeWindow
|
// @grant unsafeWindow
|
||||||
// @grant GM.registerMenuCommand
|
// @grant GM.registerMenuCommand
|
||||||
// @grant GM.addElement
|
// @grant GM.addElement
|
||||||
|
@ -32,11 +32,6 @@
|
||||||
|
|
||||||
if (_GM && _GM.registerMenuCommand && _GM.openInTab) {
|
if (_GM && _GM.registerMenuCommand && _GM.openInTab) {
|
||||||
// add buttons to the userscript manager menu
|
// add buttons to the userscript manager menu
|
||||||
_GM.registerMenuCommand(
|
|
||||||
`** Version: ${_GM.info.script.version} **`,
|
|
||||||
() => _GM.openInTab("https://github.com/Xmader/musescore-downloader/releases", { active: true })
|
|
||||||
)
|
|
||||||
|
|
||||||
_GM.registerMenuCommand(
|
_GM.registerMenuCommand(
|
||||||
'** Source Code **',
|
'** Source Code **',
|
||||||
() => _GM.openInTab(_GM.info.script.homepage, { active: true })
|
() => _GM.openInTab(_GM.info.script.homepage, { active: true })
|
||||||
|
@ -48,24 +43,17 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandL () {
|
|
||||||
return String.fromCharCode(97 + Math.floor(Math.random() * 26))
|
|
||||||
}
|
|
||||||
|
|
||||||
// script loader
|
// script loader
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
const id = '' + Math.random();
|
const id = '' + Math.random();
|
||||||
w[id] = resolve;
|
w[id] = resolve;
|
||||||
|
|
||||||
const stackN = 9
|
const stackN = 9
|
||||||
let loaderIntro = ''
|
const loaderIntro = '(function a(){'.repeat(stackN)
|
||||||
for (let i = 0; i < stackN; i++) {
|
|
||||||
loaderIntro += `(function ${getRandL()}(){`
|
|
||||||
}
|
|
||||||
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"
|
||||||
|
|
||||||
Function(`${loaderIntro}const d=new Image();window['${id}'](d);delete window['${id}'];document.body.prepend(d)${loaderOutro}//# sourceURL=${mockUrl}`)()
|
setTimeout(`${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,7 +319,6 @@
|
||||||
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, '_');
|
||||||
};
|
};
|
||||||
|
@ -423,20 +410,16 @@
|
||||||
const attachShadow = (el) => {
|
const attachShadow = (el) => {
|
||||||
return Element.prototype.attachShadow.call(el, { mode: 'closed' });
|
return Element.prototype.attachShadow.call(el, { mode: 'closed' });
|
||||||
};
|
};
|
||||||
/**
|
const waitForDocumentLoaded = () => {
|
||||||
* Run script before the page is fully loaded
|
|
||||||
*/
|
|
||||||
const waitForSheetLoaded = () => {
|
|
||||||
if (document.readyState !== 'complete') {
|
if (document.readyState !== 'complete') {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const observer = new MutationObserver(() => {
|
const cb = () => {
|
||||||
const img = document.querySelector('img');
|
if (document.readyState === 'complete') {
|
||||||
if (img) {
|
|
||||||
resolve();
|
resolve();
|
||||||
observer.disconnect();
|
document.removeEventListener('readystatechange', cb);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
observer.observe(document, { childList: true, subtree: true });
|
document.addEventListener('readystatechange', cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -26440,7 +26423,7 @@ Please pipe the document into a Node stream.\
|
||||||
});
|
});
|
||||||
|
|
||||||
/// <reference lib="webworker" />
|
/// <reference lib="webworker" />
|
||||||
const readData = (blob, type) => {
|
const getDataURL = (blob) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
|
@ -26448,21 +26431,19 @@ Please pipe the document into a Node stream.\
|
||||||
resolve(result);
|
resolve(result);
|
||||||
};
|
};
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
if (type === 'dataUrl') {
|
reader.readAsDataURL(blob);
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reader.readAsText(blob);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const fetchBlob = (imgUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
const fetchDataURL = (imgUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
const r = yield fetch(imgUrl, {
|
const r = yield fetch(imgUrl);
|
||||||
cache: 'no-cache',
|
const blob = yield r.blob();
|
||||||
});
|
return getDataURL(blob);
|
||||||
return r.blob();
|
|
||||||
});
|
});
|
||||||
const generatePDF = (imgBlobs, imgType, width, height) => __awaiter(void 0, void 0, void 0, function* () {
|
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* () {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const pdf = new PDFDocument({
|
const pdf = new PDFDocument({
|
||||||
// compress: true,
|
// compress: true,
|
||||||
|
@ -26472,7 +26453,7 @@ Please pipe the document into a Node stream.\
|
||||||
layout: 'portrait',
|
layout: 'portrait',
|
||||||
});
|
});
|
||||||
if (imgType === 'png') {
|
if (imgType === 'png') {
|
||||||
const imgDataUrlList = yield Promise.all(imgBlobs.map(b => readData(b, 'dataUrl')));
|
const imgDataUrlList = yield Promise.all(imgURLs.map(fetchDataURL));
|
||||||
imgDataUrlList.forEach((data) => {
|
imgDataUrlList.forEach((data) => {
|
||||||
pdf.addPage();
|
pdf.addPage();
|
||||||
pdf.image(data, {
|
pdf.image(data, {
|
||||||
|
@ -26482,7 +26463,7 @@ Please pipe the document into a Node stream.\
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else { // imgType == "svg"
|
else { // imgType == "svg"
|
||||||
const svgList = yield Promise.all(imgBlobs.map(b => readData(b, 'text')));
|
const svgList = yield Promise.all(imgURLs.map(fetchText));
|
||||||
svgList.forEach((svg) => {
|
svgList.forEach((svg) => {
|
||||||
pdf.addPage();
|
pdf.addPage();
|
||||||
source(pdf, svg, 0, 0, {
|
source(pdf, svg, 0, 0, {
|
||||||
|
@ -26495,9 +26476,8 @@ 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 imgBlobs = yield Promise.all(imgUrls.map(url => fetchBlob(url)));
|
const pdfBuf = yield generatePDF(imgURLs, imgType, width, height);
|
||||||
const pdfBuf = yield generatePDF(imgBlobs, imgType, width, height);
|
|
||||||
postMessage(pdfBuf, [pdfBuf]);
|
postMessage(pdfBuf, [pdfBuf]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -26575,7 +26555,7 @@ Please pipe the document into a Node stream.\
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable no-extend-native */
|
/* eslint-disable no-extend-native */
|
||||||
const TYPE_REG = /type=(img|mp3|midi)/;
|
const TYPE_REG = /id=(\d+)&type=(img|mp3|midi)/;
|
||||||
/**
|
/**
|
||||||
* I know this is super hacky.
|
* I know this is super hacky.
|
||||||
*/
|
*/
|
||||||
|
@ -26596,9 +26576,8 @@ 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[1];
|
const type = m[2];
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
|
@ -26638,7 +26617,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.querySelector('button[hasaccess]');
|
const el = document.querySelectorAll('.SD7H- > button')[3];
|
||||||
el.click();
|
el.click();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -26736,7 +26715,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);
|
||||||
|
@ -26795,9 +26774,6 @@ Please pipe the document into a Node stream.\
|
||||||
'IND_PARTS_TOOLTIP'() {
|
'IND_PARTS_TOOLTIP'() {
|
||||||
return 'Download individual parts (BETA)';
|
return 'Download individual parts (BETA)';
|
||||||
},
|
},
|
||||||
'VIEW_IN_LIBRESCORE'() {
|
|
||||||
return 'View in LibreScore';
|
|
||||||
},
|
|
||||||
'FULL_SCORE'() {
|
'FULL_SCORE'() {
|
||||||
return 'Full score';
|
return 'Full score';
|
||||||
},
|
},
|
||||||
|
@ -26811,7 +26787,7 @@ Please pipe the document into a Node stream.\
|
||||||
return '❌¡Descarga Fallida!';
|
return '❌¡Descarga Fallida!';
|
||||||
},
|
},
|
||||||
'DEPRECATION_NOTICE'(btnName) {
|
'DEPRECATION_NOTICE'(btnName) {
|
||||||
return `¡OBSOLETO!\nUtilizar \`${btnName}\` dentro de \`Partes Indivduales\` en su lugar.\n(Esto todavía puede funcionar. Pulsa \`Aceptar\` para continuar.)`;
|
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.)`;
|
||||||
},
|
},
|
||||||
'DOWNLOAD'(fileType) {
|
'DOWNLOAD'(fileType) {
|
||||||
return `Descargar ${fileType}`;
|
return `Descargar ${fileType}`;
|
||||||
|
@ -26825,79 +26801,14 @@ Please pipe the document into a Node stream.\
|
||||||
'IND_PARTS_TOOLTIP'() {
|
'IND_PARTS_TOOLTIP'() {
|
||||||
return 'Descargar partes individuales (BETA)';
|
return 'Descargar partes individuales (BETA)';
|
||||||
},
|
},
|
||||||
'VIEW_IN_LIBRESCORE'() {
|
|
||||||
return 'Visualizar en LibreScore';
|
|
||||||
},
|
|
||||||
'FULL_SCORE'() {
|
'FULL_SCORE'() {
|
||||||
return 'Partitura Completa';
|
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))({
|
const locales = ((l) => Object.freeze(l))({
|
||||||
en,
|
en,
|
||||||
es,
|
es,
|
||||||
it,
|
|
||||||
zh,
|
|
||||||
});
|
});
|
||||||
// detect browser language
|
// detect browser language
|
||||||
const lang = (() => {
|
const lang = (() => {
|
||||||
|
@ -26923,22 +26834,12 @@ 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@${dependencies.webmscore}/webmscore.js`;
|
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.10/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@${dependencies['@librescore/fonts']}/SourceHanSans${l}.min.woff2`);
|
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@${dependencies['@librescore/sf3']}/FluidR3Mono_GM.sf3`;
|
const SF3_URL = 'https://cdn.jsdelivr.net/npm/@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
|
||||||
|
@ -27020,11 +26921,6 @@ Please pipe the document into a Node stream.\
|
||||||
fileExt: 'mid',
|
fileExt: 'mid',
|
||||||
action: (score) => score.saveMidi(true, true),
|
action: (score) => score.saveMidi(true, true),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: i18n('DOWNLOAD_AUDIO')('MP3'),
|
|
||||||
fileExt: 'mp3',
|
|
||||||
action: (score) => loadSoundFont(score).then(() => score.saveAudio('mp3')),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: i18n('DOWNLOAD_AUDIO')('FLAC'),
|
name: i18n('DOWNLOAD_AUDIO')('FLAC'),
|
||||||
fileExt: 'flac',
|
fileExt: 'flac',
|
||||||
|
@ -27037,57 +26933,30 @@ Please pipe the document into a Node stream.\
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const _getLink = (indexingInfo) => {
|
var btnListCss = "div {\n flex-wrap: wrap;\n display: flex;\n align-items: center;\n font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif;\n position: absolute;\n z-index: 9999;\n background: #f6f6f6;\n min-width: 230px;\n}\n\nbutton {\n width: 205px !important;\n height: 38px;\n\n color: #fff;\n background: #1f74bd;\n\n cursor: pointer;\n\n margin-bottom: 4px;\n margin-right: 4px;\n padding: 4px 12px;\n\n justify-content: start;\n align-self: center;\n\n font-size: 16px;\n border-radius: 2px;\n border: 0;\n\n display: inline-flex;\n position: relative;\n\n font-family: inherit;\n}\n\nsvg {\n display: inline-block;\n margin-right: 5px;\n width: 20px;\n height: 20px;\n margin-top: auto;\n margin-bottom: auto;\n}\n\nspan {\n margin-top: auto;\n margin-bottom: auto;\n}";
|
||||||
const { scorepack } = JSON.parse(indexingInfo);
|
|
||||||
return `https://librescore.org/score/${scorepack}`;
|
|
||||||
};
|
|
||||||
const getLibreScoreLink = (scoreinfo, _fetch = getFetch()) => __awaiter(void 0, void 0, void 0, function* () {
|
|
||||||
const mainCid = yield getMainCid(scoreinfo, _fetch);
|
|
||||||
const ref = scoreinfo.getScorepackRef(mainCid);
|
|
||||||
const url = `https://ipfs.infura.io:5001/api/v0/dag/get?arg=${ref}`;
|
|
||||||
const r0 = yield _fetch(url);
|
|
||||||
if (r0.status !== 500) {
|
|
||||||
assertRes(r0);
|
|
||||||
}
|
|
||||||
const res = yield r0.json();
|
|
||||||
if (typeof res !== 'string') {
|
|
||||||
// read further error msg
|
|
||||||
throw new Error(res.Message);
|
|
||||||
}
|
|
||||||
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: '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) {
|
|
||||||
ICON["DOWNLOAD"] = "M9.6 2.4h4.8V12h2.784l-5.18 5.18L6.823 12H9.6V2.4zM19.2 19.2H4.8v2.4h14.4v-2.4z";
|
|
||||||
ICON["LIBRESCORE"] = "m5.4837 4.4735v10.405c-1.25-0.89936-3.0285-0.40896-4.1658 0.45816-1.0052 0.76659-1.7881 2.3316-0.98365 3.4943 1 1.1346 2.7702 0.70402 3.8817-0.02809 1.0896-0.66323 1.9667-1.8569 1.8125-3.1814v-5.4822h8.3278v9.3865h9.6438v-2.6282h-6.4567v-12.417c-4.0064-0.015181-8.0424-0.0027-12.06-0.00676zm0.54477 2.2697h8.3278v1.1258h-8.3278v-1.1258z";
|
|
||||||
})(ICON || (ICON = {}));
|
|
||||||
const getBtnContainer = () => {
|
const getBtnContainer = () => {
|
||||||
var _a;
|
var _a;
|
||||||
const els = [...document.querySelectorAll('span')];
|
const els = [...document.querySelectorAll('*')].reverse();
|
||||||
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 || !(btnParent instanceof HTMLDivElement))
|
if (!btnParent)
|
||||||
throw new Error('btn parent not found');
|
throw new Error('btn parent not found');
|
||||||
return btnParent;
|
return btnParent;
|
||||||
};
|
};
|
||||||
const buildDownloadBtn = (icon, lightTheme = false) => {
|
const buildDownloadBtn = () => {
|
||||||
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', 'M9.6 2.4h4.8V12h2.784l-5.18 5.18L6.823 12H9.6V2.4zM19.2 19.2H4.8v2.4h14.4v-2.4z');
|
||||||
svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff');
|
svgPath.setAttribute('fill', '#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);
|
||||||
|
@ -27098,30 +26967,6 @@ Please pipe the document into a Node stream.\
|
||||||
n.onclick = btn.onclick;
|
n.onclick = btn.onclick;
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
function getScrollParent(node) {
|
|
||||||
if (node.scrollHeight > node.clientHeight) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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";
|
||||||
|
@ -27133,8 +26978,7 @@ Please pipe the document into a Node stream.\
|
||||||
this.list = [];
|
this.list = [];
|
||||||
}
|
}
|
||||||
add(options) {
|
add(options) {
|
||||||
var _a;
|
const btnTpl = buildDownloadBtn();
|
||||||
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) => {
|
||||||
|
@ -27163,16 +27007,6 @@ Please pipe the document into a Node stream.\
|
||||||
}
|
}
|
||||||
return btnTpl;
|
return btnTpl;
|
||||||
}
|
}
|
||||||
_positionBtns(anchorDiv, newParent) {
|
|
||||||
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() {
|
_commit() {
|
||||||
const btnParent = document.querySelector('div');
|
const btnParent = document.querySelector('div');
|
||||||
const shadow = attachShadow(btnParent);
|
const shadow = attachShadow(btnParent);
|
||||||
|
@ -27186,17 +27020,16 @@ 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);
|
||||||
// default position
|
try {
|
||||||
newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`;
|
const anchorDiv = this.getBtnParent();
|
||||||
void onPageRendered(this.getBtnParent).then((anchorDiv) => {
|
const { width, top, left } = anchorDiv.getBoundingClientRect();
|
||||||
const pos = () => this._positionBtns(anchorDiv, newParent);
|
newParent.style.width = `${width}px`;
|
||||||
pos();
|
newParent.style.top = `${top}px`;
|
||||||
// reposition btns when window resizes
|
newParent.style.left = `${left}px`;
|
||||||
window.addEventListener('resize', pos, { passive: true });
|
}
|
||||||
// reposition btns when scrolling
|
catch (err) {
|
||||||
const scroll = getScrollParent(anchorDiv);
|
console$1.error(err);
|
||||||
scroll.addEventListener('scroll', pos, { passive: true });
|
}
|
||||||
});
|
|
||||||
return btnParent;
|
return btnParent;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -27248,17 +27081,14 @@ Please pipe the document into a Node stream.\
|
||||||
else
|
else
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
BtnAction.download = (url, fallback, timeout, target) => {
|
BtnAction.download = (url, fallback, timeout) => {
|
||||||
return BtnAction.process(() => __awaiter(this, void 0, void 0, function* () {
|
return BtnAction.process(() => __awaiter(this, void 0, void 0, function* () {
|
||||||
const _url = yield normalizeUrlInput(url);
|
const _url = yield normalizeUrlInput(url);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = _url;
|
a.href = _url;
|
||||||
if (target)
|
|
||||||
a.target = target;
|
|
||||||
a.dispatchEvent(new MouseEvent('click'));
|
a.dispatchEvent(new MouseEvent('click'));
|
||||||
}), fallback, timeout);
|
}), fallback, timeout);
|
||||||
};
|
};
|
||||||
BtnAction.openUrl = BtnAction.download;
|
|
||||||
BtnAction.mscoreWindow = (scoreinfo, fn) => {
|
BtnAction.mscoreWindow = (scoreinfo, fn) => {
|
||||||
return (btnName, btn, setText) => __awaiter(this, void 0, void 0, function* () {
|
return (btnName, btn, setText) => __awaiter(this, void 0, void 0, function* () {
|
||||||
const _onclick = btn.onclick;
|
const _onclick = btn.onclick;
|
||||||
|
@ -27303,15 +27133,6 @@ 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;
|
||||||
|
@ -27329,7 +27150,6 @@ Please pipe the document into a Node stream.\
|
||||||
class ScoreInfo {
|
class ScoreInfo {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.RADIX = 20;
|
this.RADIX = 20;
|
||||||
this.INDEX_RADIX = 32;
|
|
||||||
this.store = new Map();
|
this.store = new Map();
|
||||||
}
|
}
|
||||||
get idLastDigit() {
|
get idLastDigit() {
|
||||||
|
@ -27344,9 +27164,6 @@ Please pipe the document into a Node stream.\
|
||||||
getMsczCidUrl(mainCid) {
|
getMsczCidUrl(mainCid) {
|
||||||
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) {
|
|
||||||
return `/ipfs/${mainCid}/index/${(+this.id) % this.INDEX_RADIX}/${this.id}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
class ScoreInfoInPage extends ScoreInfo {
|
class ScoreInfoInPage extends ScoreInfo {
|
||||||
constructor(document) {
|
constructor(document) {
|
||||||
|
@ -27380,22 +27197,13 @@ 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() {
|
||||||
var _a;
|
return this.document.querySelectorAll('.gXB83').length;
|
||||||
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 === null || el === void 0 ? void 0 : el.href) || ((_a = this.sheet0Img) === null || _a === void 0 ? void 0 : _a.src));
|
const url = el.href;
|
||||||
return url.split('@')[0];
|
return url.split('@')[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27404,23 +27212,16 @@ Please pipe the document into a Node stream.\
|
||||||
// actual id already
|
// actual id already
|
||||||
return scoreinfo.id;
|
return scoreinfo.id;
|
||||||
}
|
}
|
||||||
const mainCid = yield getMainCid(scoreinfo, _fetch);
|
const jsonPUrl = new URL(`${scoreinfo.baseUrl}space.jsonp`);
|
||||||
const ref = `${mainCid}/sid2id/${scoreinfo.id}`;
|
jsonPUrl.hostname = 's.musescore.com';
|
||||||
const url = `https://ipfs.infura.io:5001/api/v0/dag/get?arg=${ref}`;
|
const r = yield _fetch(jsonPUrl.href);
|
||||||
const r0 = yield _fetch(url);
|
const text = yield r.text();
|
||||||
if (r0.status !== 500) {
|
const m = text.match(/^jsonp(\d+)/);
|
||||||
assertRes(r0);
|
const id = +m[1];
|
||||||
}
|
|
||||||
const res = yield r0.json();
|
|
||||||
if (typeof res !== 'number') {
|
|
||||||
// read further error msg
|
|
||||||
throw new Error(res.Message);
|
|
||||||
}
|
|
||||||
// assign the actual id back to scoreinfo
|
|
||||||
Object.defineProperty(scoreinfo, 'id', {
|
Object.defineProperty(scoreinfo, 'id', {
|
||||||
get() { return res; },
|
get() { return id; },
|
||||||
});
|
});
|
||||||
return res;
|
return id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { saveAs } = FileSaver_min;
|
const { saveAs } = FileSaver_min;
|
||||||
|
@ -27444,7 +27245,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')('MXL'),
|
name: i18n('DOWNLOAD')('MusicXML'),
|
||||||
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]);
|
||||||
|
@ -27519,18 +27320,10 @@ Please pipe the document into a Node stream.\
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
btnList.add({
|
|
||||||
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);
|
btnList.commit(BtnListMode.InPage);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
waitForSheetLoaded().then(main);
|
waitForDocumentLoaded().then(main);
|
||||||
|
|
||||||
}.toString() + ')()')})
|
}.toString() + ')()')})
|
||||||
|
|
||||||
|
|
40
package-lock.json
generated
40
package-lock.json
generated
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "musescore-downloader",
|
"name": "musescore-downloader",
|
||||||
"version": "0.24.1",
|
"version": "0.21.3",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@librescore/fonts": {
|
"@librescore/fonts": {
|
||||||
"version": "0.4.0",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@librescore/fonts/-/fonts-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@librescore/fonts/-/fonts-0.2.1.tgz",
|
||||||
"integrity": "sha512-T286OfxcQAYc/Bll9AtSP2ElggqTpoa08uY9Kgx6z1TcDVn7i7uMkKVO7sw/8aELWFNRmQE2vGQuEkmJNfWmBA=="
|
"integrity": "sha512-lzEk82wZWZVA4CvE2S6Wwc6EAvFZ0G6L2ExNjpJLebxAh0k/eNpHeO9a2LFwfMVUfacVWwXhDkAbmJpvUGcqzA=="
|
||||||
},
|
},
|
||||||
"@librescore/sf3": {
|
"@librescore/sf3": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
|
@ -709,26 +709,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.4",
|
"version": "6.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.11.9",
|
"bn.js": "^4.4.0",
|
||||||
"brorand": "^1.1.0",
|
"brorand": "^1.0.1",
|
||||||
"hash.js": "^1.0.0",
|
"hash.js": "^1.0.0",
|
||||||
"hmac-drbg": "^1.0.1",
|
"hmac-drbg": "^1.0.0",
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.1",
|
||||||
"minimalistic-assert": "^1.0.1",
|
"minimalistic-assert": "^1.0.0",
|
||||||
"minimalistic-crypto-utils": "^1.0.1"
|
"minimalistic-crypto-utils": "^1.0.0"
|
||||||
},
|
|
||||||
"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": {
|
||||||
|
@ -2439,9 +2431,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"webmscore": {
|
"webmscore": {
|
||||||
"version": "0.18.0",
|
"version": "0.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/webmscore/-/webmscore-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/webmscore/-/webmscore-0.10.4.tgz",
|
||||||
"integrity": "sha512-/J/2/KKWKST0A+Qix/SBSVtZY0C/33GQoYI3V84XEu/V3nij2ZFIcsyGQPYVr6y0HVasj6dQtvY+y7MrmYcsTw=="
|
"integrity": "sha512-aKFXfK5QpRfJ0xBn+zRV4/HVS4VI6tr+pLkLIHI0n0rMtSBWlkcUeP8eCfP1c1f5LRlrTIaAo4yKZ6Hxg5O7kw=="
|
||||||
},
|
},
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "musescore-downloader",
|
"name": "musescore-downloader",
|
||||||
"version": "0.24.1",
|
"version": "0.21.3",
|
||||||
"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.4.0",
|
"@librescore/fonts": "^0.2.1",
|
||||||
"@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.18.0"
|
"webmscore": "^0.10.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
|
|
51
src/btn.css
51
src/btn.css
|
@ -1,46 +1,32 @@
|
||||||
div {
|
div {
|
||||||
width: 422px;
|
flex-wrap: wrap;
|
||||||
right: 0;
|
display: flex;
|
||||||
margin: 0 18px 18px 0;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-family: 'Inter', 'Helvetica neue', Helvetica, sans-serif;
|
font-family: 'Open Sans', 'Roboto', 'Helvetica neue', Helvetica, sans-serif;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
background: #f6f6f6;
|
background: #f6f6f6;
|
||||||
min-width: 230px;
|
min-width: 230px;
|
||||||
|
|
||||||
/* pass the scroll event through the btns background */
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 950px) {
|
|
||||||
div {
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 178px !important;
|
width: 205px !important;
|
||||||
min-width: 178px;
|
height: 38px;
|
||||||
height: 40px;
|
|
||||||
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #2e68c0;
|
background: #1f74bd;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 4px;
|
||||||
margin-right: 8px;
|
margin-right: 4px;
|
||||||
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: 6px;
|
border-radius: 2px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -49,25 +35,6 @@ 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;
|
||||||
|
|
94
src/btn.ts
94
src/btn.ts
|
@ -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, DISCORD_URL } from './utils'
|
import { useTimeout, windowOpenAsync, console, attachShadow } from './utils'
|
||||||
import { isGmAvailable, _GM } from './gm'
|
import { isGmAvailable, _GM } from './gm'
|
||||||
import i18n from './i18n'
|
import i18n from './i18n'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -9,33 +9,27 @@ import btnListCss from './btn.css'
|
||||||
|
|
||||||
type BtnElement = HTMLButtonElement
|
type BtnElement = HTMLButtonElement
|
||||||
|
|
||||||
export enum ICON {
|
|
||||||
DOWNLOAD = 'M9.6 2.4h4.8V12h2.784l-5.18 5.18L6.823 12H9.6V2.4zM19.2 19.2H4.8v2.4h14.4v-2.4z',
|
|
||||||
LIBRESCORE = 'm5.4837 4.4735v10.405c-1.25-0.89936-3.0285-0.40896-4.1658 0.45816-1.0052 0.76659-1.7881 2.3316-0.98365 3.4943 1 1.1346 2.7702 0.70402 3.8817-0.02809 1.0896-0.66323 1.9667-1.8569 1.8125-3.1814v-5.4822h8.3278v9.3865h9.6438v-2.6282h-6.4567v-12.417c-4.0064-0.015181-8.0424-0.0027-12.06-0.00676zm0.54477 2.2697h8.3278v1.1258h-8.3278v-1.1258z',
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBtnContainer = (): HTMLDivElement => {
|
const getBtnContainer = (): HTMLDivElement => {
|
||||||
const els = [...document.querySelectorAll('span')]
|
const els = [...document.querySelectorAll('*')].reverse()
|
||||||
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 || !(btnParent instanceof HTMLDivElement)) throw new Error('btn parent not found')
|
if (!btnParent) throw new Error('btn parent not found')
|
||||||
return btnParent
|
return btnParent
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildDownloadBtn = (icon: ICON, lightTheme = false) => {
|
const buildDownloadBtn = () => {
|
||||||
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', 'M9.6 2.4h4.8V12h2.784l-5.18 5.18L6.823 12H9.6V2.4zM19.2 19.2H4.8v2.4h14.4v-2.4z')
|
||||||
svgPath.setAttribute('fill', lightTheme ? '#2e68c0' : '#fff')
|
svgPath.setAttribute('fill', '#fff')
|
||||||
svg.append(svgPath)
|
svg.append(svgPath)
|
||||||
|
|
||||||
const textNode = document.createElement('span')
|
const textNode = document.createElement('span')
|
||||||
|
@ -50,36 +44,11 @@ const cloneBtn = (btn: HTMLButtonElement) => {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScrollParent (node: HTMLElement): HTMLElement {
|
|
||||||
if (node.scrollHeight > node.clientHeight) {
|
|
||||||
return node
|
|
||||||
} else {
|
|
||||||
return getScrollParent(node.parentNode as 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 lightTheme?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BtnListMode {
|
export enum BtnListMode {
|
||||||
|
@ -93,7 +62,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, options.lightTheme)
|
const btnTpl = buildDownloadBtn()
|
||||||
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 => {
|
||||||
|
@ -129,16 +98,6 @@ export class BtnList {
|
||||||
return btnTpl
|
return btnTpl
|
||||||
}
|
}
|
||||||
|
|
||||||
private _positionBtns (anchorDiv: HTMLDivElement, newParent: HTMLDivElement) {
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _commit () {
|
private _commit () {
|
||||||
const btnParent = document.querySelector('div') as HTMLDivElement
|
const btnParent = document.querySelector('div') as HTMLDivElement
|
||||||
const shadow = attachShadow(btnParent)
|
const shadow = attachShadow(btnParent)
|
||||||
|
@ -156,20 +115,15 @@ 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)
|
||||||
|
|
||||||
// default position
|
try {
|
||||||
newParent.style.top = `${window.innerHeight - newParent.getBoundingClientRect().height}px`
|
const anchorDiv = this.getBtnParent()
|
||||||
|
const { width, top, left } = anchorDiv.getBoundingClientRect()
|
||||||
void onPageRendered(this.getBtnParent).then((anchorDiv: HTMLDivElement) => {
|
newParent.style.width = `${width}px`
|
||||||
const pos = () => this._positionBtns(anchorDiv, newParent)
|
newParent.style.top = `${top}px`
|
||||||
pos()
|
newParent.style.left = `${left}px`
|
||||||
|
} catch (err) {
|
||||||
// reposition btns when window resizes
|
console.error(err)
|
||||||
window.addEventListener('resize', pos, { passive: true })
|
}
|
||||||
|
|
||||||
// reposition btns when scrolling
|
|
||||||
const scroll = getScrollParent(anchorDiv)
|
|
||||||
scroll.addEventListener('scroll', pos, { passive: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
return btnParent
|
return btnParent
|
||||||
}
|
}
|
||||||
|
@ -227,18 +181,15 @@ export namespace BtnAction {
|
||||||
else return url
|
else return url
|
||||||
}
|
}
|
||||||
|
|
||||||
export const download = (url: UrlInput, fallback?: () => Promisable<void>, timeout?: number, target?: '_blank'): BtnAction => {
|
export const download = (url: UrlInput, fallback?: () => Promisable<void>, timeout?: number): BtnAction => {
|
||||||
return process(async (): Promise<void> => {
|
return process(async (): Promise<void> => {
|
||||||
const _url = await normalizeUrlInput(url)
|
const _url = await normalizeUrlInput(url)
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
a.href = _url
|
a.href = _url
|
||||||
if (target) a.target = target
|
|
||||||
a.dispatchEvent(new MouseEvent('click'))
|
a.dispatchEvent(new MouseEvent('click'))
|
||||||
}, fallback, timeout)
|
}, fallback, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const openUrl = download
|
|
||||||
|
|
||||||
export const mscoreWindow = (scoreinfo: ScoreInfo, fn: (w: Window, score: WebMscore, processingTextEl: ChildNode) => any): BtnAction => {
|
export const mscoreWindow = (scoreinfo: ScoreInfo, fn: (w: Window, score: WebMscore, processingTextEl: ChildNode) => any): BtnAction => {
|
||||||
return async (btnName, btn, setText) => {
|
return async (btnName, btn, setText) => {
|
||||||
const _onclick = btn.onclick
|
const _onclick = btn.onclick
|
||||||
|
@ -288,17 +239,6 @@ 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'))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
49
src/cli.ts
49
src/cli.ts
|
@ -4,13 +4,10 @@
|
||||||
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import os from 'os'
|
|
||||||
import { fetchMscz, setMscz, MSCZ_URL_SYM } from './mscz'
|
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 { 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')
|
||||||
|
@ -30,23 +27,7 @@ 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
|
|
||||||
const platform = os.platform()
|
|
||||||
let pasteMessage = ''
|
|
||||||
if (platform === 'win32') {
|
|
||||||
pasteMessage = 'right-click to paste'
|
|
||||||
} else if (platform === 'linux') {
|
|
||||||
pasteMessage = 'usually Ctrl+Shift+V to paste'
|
|
||||||
} // For MacOS, no hint is needed because the paste shortcut is universal.
|
|
||||||
|
|
||||||
let scoreinfo: ScoreInfo
|
let scoreinfo: ScoreInfo
|
||||||
let librescoreLink: Promise<string> | undefined
|
|
||||||
// ask for the page url or path to local file
|
// ask for the page url or path to local file
|
||||||
const { fileInit } = await inquirer.prompt<Params>({
|
const { fileInit } = await inquirer.prompt<Params>({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
@ -54,7 +35,7 @@ void (async () => {
|
||||||
message: 'Score URL or path to local MSCZ file:',
|
message: 'Score URL or path to local MSCZ file:',
|
||||||
suffix: '\n ' +
|
suffix: '\n ' +
|
||||||
`(starts with "${SCORE_URL_PREFIX}" or local filepath ends with "${EXT}") ` +
|
`(starts with "${SCORE_URL_PREFIX}" or local filepath ends with "${EXT}") ` +
|
||||||
`${chalk.bgGray(pasteMessage)}\n `,
|
`${chalk.bgGray`right-click to paste`}\n `,
|
||||||
validate (input: string) {
|
validate (input: string) {
|
||||||
return input &&
|
return input &&
|
||||||
(
|
(
|
||||||
|
@ -62,7 +43,7 @@ void (async () => {
|
||||||
(input.endsWith(EXT) && fs.statSync(input).isFile())
|
(input.endsWith(EXT) && fs.statSync(input).isFile())
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
default: arg,
|
default: process.argv[2],
|
||||||
})
|
})
|
||||||
|
|
||||||
const isLocalFile = fileInit.endsWith(EXT)
|
const isLocalFile = fileInit.endsWith(EXT)
|
||||||
|
@ -86,13 +67,7 @@ void (async () => {
|
||||||
default: true,
|
default: true,
|
||||||
})
|
})
|
||||||
if (!confirmed) return
|
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 {
|
} else {
|
||||||
scoreinfo = new ScoreInfoObj(0, path.basename(fileInit, EXT))
|
scoreinfo = new ScoreInfoObj(0, path.basename(fileInit, EXT))
|
||||||
}
|
}
|
||||||
|
@ -120,11 +95,6 @@ void (async () => {
|
||||||
if (!isLocalFile) {
|
if (!isLocalFile) {
|
||||||
spinner.info(`File URL: ${scoreinfo.store.get(MSCZ_URL_SYM) as string}`)
|
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()
|
spinner.start()
|
||||||
|
|
||||||
// load score using webmscore
|
// load score using webmscore
|
||||||
|
@ -134,10 +104,6 @@ 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')
|
||||||
|
@ -196,11 +162,4 @@ 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.`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
})()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { console } from './utils'
|
||||||
|
|
||||||
type FileType = 'img' | 'mp3' | 'midi'
|
type FileType = 'img' | 'mp3' | 'midi'
|
||||||
|
|
||||||
const TYPE_REG = /type=(img|mp3|midi)/
|
const TYPE_REG = /id=(\d+)&type=(img|mp3|midi)/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* I know this is super hacky.
|
* I know this is super hacky.
|
||||||
|
@ -31,9 +31,8 @@ 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[1]
|
const type = m[2]
|
||||||
// eslint-disable-next-line no-unused-expressions
|
// eslint-disable-next-line no-unused-expressions
|
||||||
l[type]?.(token)
|
l[type]?.(token)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +78,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.querySelector('button[hasaccess]') as HTMLButtonElement
|
const el = document.querySelectorAll('.SD7H- > button')[3] as HTMLButtonElement
|
||||||
el.click()
|
el.click()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,6 @@ export default createLocale({
|
||||||
return 'Download individual parts (BETA)' as const
|
return 'Download individual parts (BETA)' as const
|
||||||
},
|
},
|
||||||
|
|
||||||
'VIEW_IN_LIBRESCORE' () {
|
|
||||||
return 'View in LibreScore' as const
|
|
||||||
},
|
|
||||||
|
|
||||||
'FULL_SCORE' () {
|
'FULL_SCORE' () {
|
||||||
return 'Full score' as const
|
return 'Full score' as const
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default createLocale({
|
||||||
},
|
},
|
||||||
|
|
||||||
'DEPRECATION_NOTICE' (btnName: string) {
|
'DEPRECATION_NOTICE' (btnName: string) {
|
||||||
return `¡OBSOLETO!\nUtilizar \`${btnName}\` dentro de \`Partes Indivduales\` en su lugar.\n(Esto todavía puede funcionar. Pulsa \`Aceptar\` para continuar.)` as const
|
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
|
||||||
},
|
},
|
||||||
|
|
||||||
'DOWNLOAD' <T extends string> (fileType: T) {
|
'DOWNLOAD' <T extends string> (fileType: T) {
|
||||||
|
@ -27,10 +27,6 @@ export default createLocale({
|
||||||
return 'Descargar partes individuales (BETA)' as const
|
return 'Descargar partes individuales (BETA)' as const
|
||||||
},
|
},
|
||||||
|
|
||||||
'VIEW_IN_LIBRESCORE' () {
|
|
||||||
return 'Visualizar en LibreScore' as const
|
|
||||||
},
|
|
||||||
|
|
||||||
'FULL_SCORE' () {
|
'FULL_SCORE' () {
|
||||||
return 'Partitura Completa' as const
|
return 'Partitura Completa' as const
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,8 +3,6 @@ import isNodeJs from 'detect-node'
|
||||||
|
|
||||||
import en from './en'
|
import en from './en'
|
||||||
import es from './es'
|
import es from './es'
|
||||||
import it from './it'
|
|
||||||
import zh from './zh'
|
|
||||||
|
|
||||||
export interface LOCALE {
|
export interface LOCALE {
|
||||||
'PROCESSING' (): string;
|
'PROCESSING' (): string;
|
||||||
|
@ -18,16 +16,12 @@ export interface LOCALE {
|
||||||
'IND_PARTS' (): string;
|
'IND_PARTS' (): string;
|
||||||
'IND_PARTS_TOOLTIP' (): string;
|
'IND_PARTS_TOOLTIP' (): string;
|
||||||
|
|
||||||
'VIEW_IN_LIBRESCORE' (): string;
|
|
||||||
|
|
||||||
'FULL_SCORE' (): string;
|
'FULL_SCORE' (): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const locales = (<L extends { [n: string]: LOCALE } /** type checking */> (l: L) => Object.freeze(l))({
|
const locales = (<L extends { [n: string]: LOCALE } /** type checking */> (l: L) => Object.freeze(l))({
|
||||||
en,
|
en,
|
||||||
es,
|
es,
|
||||||
it,
|
|
||||||
zh,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// detect browser language
|
// detect browser language
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,37 +0,0 @@
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,26 +0,0 @@
|
||||||
import { assertRes, getFetch } from './utils'
|
|
||||||
import { getMainCid } from './mscz'
|
|
||||||
import { ScoreInfo } from './scoreinfo'
|
|
||||||
|
|
||||||
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> => {
|
|
||||||
const mainCid = await getMainCid(scoreinfo, _fetch)
|
|
||||||
const ref = scoreinfo.getScorepackRef(mainCid)
|
|
||||||
const url = `https://ipfs.infura.io:5001/api/v0/dag/get?arg=${ref}`
|
|
||||||
|
|
||||||
const r0 = await _fetch(url)
|
|
||||||
if (r0.status !== 500) {
|
|
||||||
assertRes(r0)
|
|
||||||
}
|
|
||||||
const res: { Message: string } | string = await r0.json()
|
|
||||||
if (typeof res !== 'string') {
|
|
||||||
// read further error msg
|
|
||||||
throw new Error(res.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return _getLink(res)
|
|
||||||
}
|
|
18
src/main.ts
18
src/main.ts
|
@ -1,13 +1,12 @@
|
||||||
import './meta'
|
import './meta'
|
||||||
|
|
||||||
import FileSaver from 'file-saver'
|
import FileSaver from 'file-saver'
|
||||||
import { waitForSheetLoaded, console } from './utils'
|
import { waitForDocumentLoaded, console } from './utils'
|
||||||
import { downloadPDF } from './pdf'
|
import { downloadPDF } from './pdf'
|
||||||
import { downloadMscz } from './mscz'
|
import { downloadMscz } from './mscz'
|
||||||
import { getFileUrl } from './file'
|
import { getFileUrl } from './file'
|
||||||
import { INDV_DOWNLOADS } from './mscore'
|
import { INDV_DOWNLOADS } from './mscore'
|
||||||
import { getLibreScoreLink } from './librescore-link'
|
import { BtnList, BtnAction, BtnListMode } from './btn'
|
||||||
import { BtnList, BtnAction, BtnListMode, ICON } from './btn'
|
|
||||||
import { ScoreInfoInPage, SheetInfoInPage, getActualId } from './scoreinfo'
|
import { ScoreInfoInPage, SheetInfoInPage, getActualId } from './scoreinfo'
|
||||||
import i18n from './i18n'
|
import i18n from './i18n'
|
||||||
|
|
||||||
|
@ -38,7 +37,7 @@ const main = (): void => {
|
||||||
})
|
})
|
||||||
|
|
||||||
btnList.add({
|
btnList.add({
|
||||||
name: i18n('DOWNLOAD')('MXL'),
|
name: i18n('DOWNLOAD')('MusicXML'),
|
||||||
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])
|
||||||
|
@ -132,17 +131,8 @@ const main = (): void => {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
btnList.add({
|
|
||||||
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)
|
btnList.commit(BtnListMode.InPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
waitForSheetLoaded().then(main)
|
waitForDocumentLoaded().then(main)
|
||||||
|
|
|
@ -8,11 +8,10 @@
|
||||||
// @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
|
||||||
// @copyright Copyright (c) 2019-2021 Xmader
|
// @copyright Copyright (c) 2019-2020 Xmader
|
||||||
// @grant unsafeWindow
|
// @grant unsafeWindow
|
||||||
// @grant GM.registerMenuCommand
|
// @grant GM.registerMenuCommand
|
||||||
// @grant GM.addElement
|
// @grant GM.addElement
|
||||||
|
|
|
@ -6,15 +6,14 @@ 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@${depVers.webmscore}/webmscore.js`
|
const WEBMSCORE_URL = 'https://cdn.jsdelivr.net/npm/webmscore@0.10/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@${depVers['@librescore/fonts']}/SourceHanSans${l}.min.woff2`)
|
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@${depVers['@librescore/sf3']}/FluidR3Mono_GM.sf3`
|
const SF3_URL = 'https://cdn.jsdelivr.net/npm/@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
|
||||||
|
@ -119,11 +118,6 @@ export const INDV_DOWNLOADS: IndividualDownload[] = [
|
||||||
fileExt: 'mid',
|
fileExt: 'mid',
|
||||||
action: (score) => score.saveMidi(true, true),
|
action: (score) => score.saveMidi(true, true),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: i18n('DOWNLOAD_AUDIO')('MP3'),
|
|
||||||
fileExt: 'mp3',
|
|
||||||
action: (score) => loadSoundFont(score).then(() => score.saveAudio('mp3')),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: i18n('DOWNLOAD_AUDIO')('FLAC'),
|
name: i18n('DOWNLOAD_AUDIO')('FLAC'),
|
||||||
fileExt: 'flac',
|
fileExt: 'flac',
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
require("musescore-downloader/dist/cli.js")
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"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%"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/* 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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,8 @@
|
||||||
|
|
||||||
import { getFetch, escapeFilename, assertRes } from './utils'
|
import { getFetch, escapeFilename } from './utils'
|
||||||
import { getMainCid } from './mscz'
|
|
||||||
|
|
||||||
export abstract class ScoreInfo {
|
export abstract class ScoreInfo {
|
||||||
private readonly RADIX = 20;
|
private readonly RADIX = 20;
|
||||||
private readonly INDEX_RADIX = 32;
|
|
||||||
|
|
||||||
abstract id: number;
|
abstract id: number;
|
||||||
abstract title: string;
|
abstract title: string;
|
||||||
|
@ -26,10 +24,6 @@ export abstract class ScoreInfo {
|
||||||
public getMsczCidUrl (mainCid: string): string {
|
public getMsczCidUrl (mainCid: string): string {
|
||||||
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)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
public getScorepackRef (mainCid: string): string {
|
|
||||||
return `/ipfs/${mainCid}/index/${(+this.id) % this.INDEX_RADIX}/${this.id}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScoreInfoObj extends ScoreInfo {
|
export class ScoreInfoObj extends ScoreInfo {
|
||||||
|
@ -105,22 +99,14 @@ 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 {
|
||||||
const sheet0Div = this.sheet0Img?.parentElement
|
return this.document.querySelectorAll('.gXB83').length
|
||||||
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<HTMLLinkElement>('link[as=image]')
|
const el = this.document.querySelector('link[as=image]') as HTMLLinkElement
|
||||||
const url = (el?.href || this.sheet0Img?.src) as string
|
const url = el.href
|
||||||
return url.split('@')[0]
|
return url.split('@')[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,24 +117,18 @@ export const getActualId = async (scoreinfo: ScoreInfoInPage | ScoreInfoHtml, _f
|
||||||
return scoreinfo.id
|
return scoreinfo.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainCid = await getMainCid(scoreinfo, _fetch)
|
const jsonPUrl = new URL(`${scoreinfo.baseUrl}space.jsonp`)
|
||||||
const ref = `${mainCid}/sid2id/${scoreinfo.id}`
|
jsonPUrl.hostname = 's.musescore.com'
|
||||||
const url = `https://ipfs.infura.io:5001/api/v0/dag/get?arg=${ref}`
|
|
||||||
|
|
||||||
const r0 = await _fetch(url)
|
const r = await _fetch(jsonPUrl.href)
|
||||||
if (r0.status !== 500) {
|
const text = await r.text()
|
||||||
assertRes(r0)
|
|
||||||
}
|
const m = text.match(/^jsonp(\d+)/) as RegExpMatchArray
|
||||||
const res: { Message: string } | number = await r0.json()
|
const id = +m[1]
|
||||||
if (typeof res !== 'number') {
|
|
||||||
// read further error msg
|
|
||||||
throw new Error(res.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// assign the actual id back to scoreinfo
|
|
||||||
Object.defineProperty(scoreinfo, 'id', {
|
Object.defineProperty(scoreinfo, 'id', {
|
||||||
get () { return res },
|
get () { return id },
|
||||||
})
|
})
|
||||||
|
|
||||||
return res
|
return id
|
||||||
}
|
}
|
||||||
|
|
22
src/utils.ts
22
src/utils.ts
|
@ -2,8 +2,6 @@
|
||||||
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, '_')
|
||||||
}
|
}
|
||||||
|
@ -138,23 +136,3 @@ export const waitForDocumentLoaded = (): Promise<void> => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Run script before the page is fully loaded
|
|
||||||
*/
|
|
||||||
export const waitForSheetLoaded = (): Promise<void> => {
|
|
||||||
if (document.readyState !== 'complete') {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
const img = document.querySelector('img')
|
|
||||||
if (img) {
|
|
||||||
resolve()
|
|
||||||
observer.disconnect()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
observer.observe(document, { childList: true, subtree: true })
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,9 +6,7 @@ import SVGtoPDF from 'svg-to-pdfkit'
|
||||||
|
|
||||||
type ImgType = 'svg' | 'png'
|
type ImgType = 'svg' | 'png'
|
||||||
|
|
||||||
type DataResultType = 'dataUrl' | 'text'
|
const getDataURL = (blob: Blob): Promise<string> => {
|
||||||
|
|
||||||
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 => {
|
||||||
|
@ -16,22 +14,22 @@ const readData = (blob: Blob, type: DataResultType): Promise<string> => {
|
||||||
resolve(result as string)
|
resolve(result as string)
|
||||||
}
|
}
|
||||||
reader.onerror = reject
|
reader.onerror = reject
|
||||||
if (type === 'dataUrl') {
|
reader.readAsDataURL(blob)
|
||||||
reader.readAsDataURL(blob)
|
|
||||||
} else {
|
|
||||||
reader.readAsText(blob)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchBlob = async (imgUrl: string): Promise<Blob> => {
|
const fetchDataURL = async (imgUrl: string): Promise<string> => {
|
||||||
const r = await fetch(imgUrl, {
|
const r = await fetch(imgUrl)
|
||||||
cache: 'no-cache',
|
const blob = await r.blob()
|
||||||
})
|
return getDataURL(blob)
|
||||||
return r.blob()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatePDF = async (imgBlobs: Blob[], imgType: ImgType, width: number, height: number): Promise<ArrayBuffer> => {
|
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> => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const pdf = new (PDFDocument as typeof import('pdfkit'))({
|
const pdf = new (PDFDocument as typeof import('pdfkit'))({
|
||||||
// compress: true,
|
// compress: true,
|
||||||
|
@ -42,7 +40,7 @@ const generatePDF = async (imgBlobs: Blob[], imgType: ImgType, width: number, he
|
||||||
})
|
})
|
||||||
|
|
||||||
if (imgType === 'png') {
|
if (imgType === 'png') {
|
||||||
const imgDataUrlList: string[] = await Promise.all(imgBlobs.map(b => readData(b, 'dataUrl')))
|
const imgDataUrlList: string[] = await Promise.all(imgURLs.map(fetchDataURL))
|
||||||
|
|
||||||
imgDataUrlList.forEach((data) => {
|
imgDataUrlList.forEach((data) => {
|
||||||
pdf.addPage()
|
pdf.addPage()
|
||||||
|
@ -52,7 +50,7 @@ const generatePDF = async (imgBlobs: Blob[], imgType: ImgType, width: number, he
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else { // imgType == "svg"
|
} else { // imgType == "svg"
|
||||||
const svgList = await Promise.all(imgBlobs.map(b => readData(b, 'text')))
|
const svgList = await Promise.all(imgURLs.map(fetchText))
|
||||||
|
|
||||||
svgList.forEach((svg) => {
|
svgList.forEach((svg) => {
|
||||||
pdf.addPage()
|
pdf.addPage()
|
||||||
|
@ -72,16 +70,14 @@ 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(
|
||||||
imgBlobs,
|
imgURLs,
|
||||||
imgType,
|
imgType,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
|
|
@ -8,11 +8,6 @@ w[gmId] = _GM;
|
||||||
|
|
||||||
if (_GM && _GM.registerMenuCommand && _GM.openInTab) {
|
if (_GM && _GM.registerMenuCommand && _GM.openInTab) {
|
||||||
// add buttons to the userscript manager menu
|
// add buttons to the userscript manager menu
|
||||||
_GM.registerMenuCommand(
|
|
||||||
`** Version: ${_GM.info.script.version} **`,
|
|
||||||
() => _GM.openInTab("https://github.com/Xmader/musescore-downloader/releases", { active: true })
|
|
||||||
)
|
|
||||||
|
|
||||||
_GM.registerMenuCommand(
|
_GM.registerMenuCommand(
|
||||||
'** Source Code **',
|
'** Source Code **',
|
||||||
() => _GM.openInTab(_GM.info.script.homepage, { active: true })
|
() => _GM.openInTab(_GM.info.script.homepage, { active: true })
|
||||||
|
@ -24,24 +19,17 @@ if (_GM && _GM.registerMenuCommand && _GM.openInTab) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandL () {
|
|
||||||
return String.fromCharCode(97 + Math.floor(Math.random() * 26))
|
|
||||||
}
|
|
||||||
|
|
||||||
// script loader
|
// script loader
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
const id = '' + Math.random();
|
const id = '' + Math.random();
|
||||||
w[id] = resolve;
|
w[id] = resolve;
|
||||||
|
|
||||||
const stackN = 9
|
const stackN = 9
|
||||||
let loaderIntro = ''
|
const loaderIntro = '(function a(){'.repeat(stackN)
|
||||||
for (let i = 0; i < stackN; i++) {
|
|
||||||
loaderIntro += `(function ${getRandL()}(){`
|
|
||||||
}
|
|
||||||
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"
|
||||||
|
|
||||||
Function(`${loaderIntro}const d=new Image();window['${id}'](d);delete window['${id}'];document.body.prepend(d)${loaderOutro}//# sourceURL=${mockUrl}`)()
|
setTimeout(`${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==';
|
||||||
|
|
Loading…
Reference in a new issue