From 2c8b3c136072a3c2c0218704b1ebb2c012f2fc34 Mon Sep 17 00:00:00 2001 From: Xmader Date: Sun, 3 Nov 2019 14:13:06 -0500 Subject: [PATCH] initial commit --- .eslintignore | 1 + .eslintrc | 35 ++++++++++++++++++++ .gitignore | 66 +++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++++++++++ dist/main.js | 53 ++++++++++++++++++++++++++++++ package.json | 28 ++++++++++++++++ rollup.config.js | 24 ++++++++++++++ src/main.ts | 41 +++++++++++++++++++++++ src/meta.js | 11 +++++++ src/types.ts | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.ts | 8 +++++ 11 files changed, 372 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 dist/main.js create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/main.ts create mode 100644 src/meta.js create mode 100644 src/types.ts create mode 100644 src/utils.ts diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..a261f29 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +dist/* diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..a867b94 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,35 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2019, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + "indent": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "warn", + "double" + ], + "semi": [ + "warn", + "never" + ], + "no-console": "off" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc20593 --- /dev/null +++ b/.gitignore @@ -0,0 +1,66 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# parcel build cache +.cache + +package-lock.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6de4a1b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Xmader + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dist/main.js b/dist/main.js new file mode 100644 index 0000000..3515391 --- /dev/null +++ b/dist/main.js @@ -0,0 +1,53 @@ +// ==UserScript== +// @name musescore-downloader +// @namespace https://www.xmader.com/ +// @version 0.1.0 +// @description 免登录、免 Musescore Pro,下载 musescore.com 上的曲谱 +// @author Xmader +// @match https://musescore.com/user/*/scores/* +// @license MIT +// @copyright Copyright (c) 2019 Xmader +// @grant none +// ==/UserScript== + +(function () { + 'use strict'; + + const getIndexPath = (id) => { + const idStr = String(id); + // 获取最后三位,倒序排列 + // "5449062" -> ["2", "6", "0"] + const indexN = idStr.split("").reverse().slice(0, 3); + return indexN.join("/"); + }; + + // @ts-ignore + const scorePlayer = window.UGAPP.store.jmuse_settings.score_player; + const { id, vid } = scorePlayer.json; + const baseURL = scorePlayer.urls.image_path; + const scoreHexId = baseURL.split("/").filter(Boolean).reverse()[1]; + const msczURL = `https://musescore.com/static/musescore/scoredata/score/${getIndexPath}/${id}/score_${vid}_${scoreHexId}.mscz`; + const pdfURL = baseURL + "score_full.pdf"; + const mxlURL = baseURL + "score.mxl"; + const { midi: midiURL, mp3: mp3URL } = scorePlayer.urls; + const btnsDiv = document.querySelectorAll("aside section > div")[3]; + const downloadBtn = btnsDiv.querySelector("button"); + downloadBtn.onclick = null; + const downloadURLs = { + "Musescore": msczURL, + "PDF": pdfURL, + "MusicXML": mxlURL, + "MIDI": midiURL, + "MP3": mp3URL, + }; + const newDownloadBtns = Object.keys(downloadURLs).map((name) => { + const url = downloadURLs[name]; + const btn = downloadBtn.cloneNode(true); + btn.onclick = () => { + window.open(url); + }; + return btn; + }); + downloadBtn.replaceWith(...newDownloadBtns); + +}()); diff --git a/package.json b/package.json new file mode 100644 index 0000000..61913fd --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "musescore-downloader", + "version": "0.1.0", + "description": "免登录、免 Musescore Pro,下载 musescore.com 上的曲谱", + "main": "dist/main.js", + "repository": { + "type": "git", + "url": "git+https://github.com/Xmader/musescore-downloader.git" + }, + "author": "Xmader", + "license": "MIT", + "bugs": { + "url": "https://github.com/Xmader/musescore-downloader/issues" + }, + "homepage": "https://github.com/Xmader/musescore-downloader#readme", + "devDependencies": { + "rollup": "^1.26.3", + "rollup-plugin-typescript": "^1.0.1", + "tslib": "^1.10.0", + "typescript": "^3.6.4" + }, + "scripts": { + "build": "rollup -c", + "watch": "npm run build -- --watch", + "bump-version:patch": "npm version patch --no-git-tag", + "bump-version:minor": "npm version minor --no-git-tag" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..e4c5b76 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,24 @@ +import typescript from "rollup-plugin-typescript" +import fs from "fs" +import { version } from "./package.json" + +let bannerText = fs.readFileSync("./src/meta.js", "utf-8") +bannerText = bannerText.replace("%VERSION%", version) + +export default { + input: "src/main.ts", + output: { + file: "dist/main.js", + format: "iife", + banner: bannerText, + }, + plugins: [ + typescript({ + target: "ES6", + lib: [ + "ES6", + "dom" + ], + }) + ] +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..ac842e4 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,41 @@ +import "./meta" + +import { ScorePlayerData } from "./types" +import { getIndexPath } from "./utils" + +// @ts-ignore +const scorePlayer: ScorePlayerData = window.UGAPP.store.jmuse_settings.score_player + +const { id, vid } = scorePlayer.json +const baseURL = scorePlayer.urls.image_path +const scoreHexId = baseURL.split("/").filter(Boolean).reverse()[1] + +const msczURL = `https://musescore.com/static/musescore/scoredata/score/${getIndexPath}/${id}/score_${vid}_${scoreHexId}.mscz` +const pdfURL = baseURL + "score_full.pdf" +const mxlURL = baseURL + "score.mxl" +const { midi: midiURL, mp3: mp3URL } = scorePlayer.urls + +const btnsDiv = document.querySelectorAll("aside section > div")[3] +const downloadBtn = btnsDiv.querySelector("button") +downloadBtn.onclick = null + +const downloadURLs = { + "Musescore": msczURL, + "PDF": pdfURL, + "MusicXML": mxlURL, + "MIDI": midiURL, + "MP3": mp3URL, +} + +const newDownloadBtns = Object.keys(downloadURLs).map((name) => { + const url = downloadURLs[name] + + const btn = downloadBtn.cloneNode(true) as HTMLButtonElement + btn.onclick = () => { + window.open(url) + } + + return btn +}) + +downloadBtn.replaceWith(...newDownloadBtns) diff --git a/src/meta.js b/src/meta.js new file mode 100644 index 0000000..c16a509 --- /dev/null +++ b/src/meta.js @@ -0,0 +1,11 @@ +// ==UserScript== +// @name musescore-downloader +// @namespace https://www.xmader.com/ +// @version %VERSION% +// @description 免登录、免 Musescore Pro,下载 musescore.com 上的曲谱 +// @author Xmader +// @match https://musescore.com/user/*/scores/* +// @license MIT +// @copyright Copyright (c) 2019 Xmader +// @grant none +// ==/UserScript== diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..15b63cd --- /dev/null +++ b/src/types.ts @@ -0,0 +1,84 @@ + +interface SourceData { + type: string; // "audio" + title: string; // "Musescore audio" + nid: number; +} + +type CommentData = any; + +interface PartData { + part: { + name: string; + program: number; + } +} + +interface Metadata { + title: string; + subtitle?: string; + composer?: string; + poet?: string; + pages: number; + measures: number; + lyrics: number; + chordnames: number; + keysig: number; + duration: number; + dimensions: number; + parts: PartData[]; +} + +interface ScoreJson { + id: number; + vid: number; + dates: { + revised: number; + }; + secret: string; + permalink: string; + custom_url: string; + format: string; // "0" + has_custom_audio: 0 | 1; + metadata: Metadata; +} + +interface UrlsData { + midi: string; + mp3: string; + space: string; + image_path: string; + media?: string[]; +} + +interface AccessControlData { + enabled: boolean; + hasAccess: boolean; +} + +interface PianoKeyboardData extends AccessControlData { + midiUrl: string; +} + +interface PianoRollData extends AccessControlData { + resourcesUrl: string; + feedbackUrl: string; + forceShow: boolean; +} + +export interface ScorePlayerData { + embed: boolean; + sources: SourceData[]; + default_source?: SourceData; + mixer?: string; + secondaryMixer?: string; + bucket?: string; // "https://musescore.com/static/musescore/scoredata" + json: ScoreJson; + render_vector: boolean; + comments: CommentData[]; + score_id: number; + urls: UrlsData; + sendEvents?: boolean; + pianoKeyboard: PianoKeyboardData; + pianoRoll: PianoRollData; +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..6e413c8 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,8 @@ + +export const getIndexPath = (id: number) => { + const idStr = String(id) + // 获取最后三位,倒序排列 + // "5449062" -> ["2", "6", "0"] + const indexN = idStr.split("").reverse().slice(0, 3) + return indexN.join("/") +}