commit f2c18602155d98d27f2b9228b25318fabacaf495 Author: SuperSaltyGamer Date: Fri Dec 30 02:03:51 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24d05c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode/ +.idea/ +node_modules/ +out/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..15a200b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# AME + +Various user scripts for the music hoarding community. diff --git a/dist/applemusic.user.js b/dist/applemusic.user.js new file mode 100644 index 0000000..1b23c7a --- /dev/null +++ b/dist/applemusic.user.js @@ -0,0 +1,64 @@ +// ==UserScript== +// @namespace ame-applemusic +// @name Ame (Apple Music) +// @version 1.0.0 +// @author SuperSaltyGamer +// @run-at document-start +// @match https://music.apple.com/* +// @grant GM.addStyle +// @grant GM.setClipboard +// @grant GM.xmlHttpRequest +// @downloadURL https://notabug.org/SuperSaltyGamer/ame/dist/applemusic.user.js +// @updateURL https://notabug.org/SuperSaltyGamer/ame/dist/applemusic.user.js +// ==/UserScript== + +(function(S){typeof define=="function"&&define.amd?define(S):S()})(function(){"use strict";function S(t){for(var n=[],e=0;e=48&&o<=57||o>=65&&o<=90||o>=97&&o<=122||o===95){i+=t[a++];continue}break}if(!i)throw new TypeError("Missing parameter name at ".concat(e));n.push({type:"NAME",index:e,value:i}),e=a;continue}if(r==="("){var c=1,d="",a=e+1;if(t[a]==="?")throw new TypeError('Pattern cannot start with "?" at '.concat(a));for(;a)?(?!\?)/g,r=0,i=e.exec(t.source);i;)n.push({name:i[1]||r++,prefix:"",suffix:"",modifier:"",pattern:""}),i=e.exec(t.source);return t}function ct(t,n,e){var r=t.map(function(i){return j(i,n,e).source});return new RegExp("(?:".concat(r.join("|"),")"),V(e))}function st(t,n,e){return lt(at(t,e),n,e)}function lt(t,n,e){e===void 0&&(e={});for(var r=e.strict,i=r===void 0?!1:r,a=e.start,o=a===void 0?!0:a,c=e.end,d=c===void 0?!0:c,s=e.encode,u=s===void 0?function(F){return F}:s,m=e.delimiter,l=m===void 0?"/#?":m,p=e.endsWith,_=p===void 0?"":p,q="[".concat(T(_),"]|$"),b="[".concat(T(l),"]"),v=o?"^":"",R=0,I=t;R-1:x===void 0;i||(v+="(?:".concat(b,"(?=").concat(q,"))?")),Q||(v+="(?=".concat(b,"|").concat(q,")"))}return new RegExp(v,V(e))}function j(t,n,e){return t instanceof RegExp?ot(t,n):Array.isArray(t)?ct(t,n,e):st(t,n,e)}const k=[],ut=history.pushState;history.pushState=function(t,n,e){ut.apply(history,[t,n,e]),e&&z(e.toString(),k)},addEventListener("popstate",()=>{z(location.pathname,k)});function z(t,n){for(const e of n){const r=e.matcher(t)?e.onCallbacks:e.offCallbacks;for(const i of r)i()}}function G(t){let n=k.find(e=>e.pattern===t);return n||(n={pattern:t,matcher:rt(t),onCallbacks:[],offCallbacks:[]},k.push(n),n)}function dt(t,n){const e=G(t);e.onCallbacks.push(n),e.matcher(location.pathname)&&n()}function ft(t,n){const e=G(t);e.offCallbacks.push(n),e.matcher(location.pathname)||n()}function L(t){return new Promise(n=>{setTimeout(n,t)})}function E(t){const n=document.createElement("div");return n.innerHTML=t,n.firstElementChild}function C(t,n,e=5e3,r){return new Promise(i=>{let a=0,o=0;a=setTimeout(()=>{clearInterval(o),i(null)},e),o=setInterval(()=>{let c=(r??document).querySelector(n??t);c&&(n&&(c=(r??document).querySelector(t)),c&&(i(c),clearTimeout(a),clearInterval(o)))},10)})}function mt(t,n){new MutationObserver(r=>{for(const i of r)for(const a of Array.from(i.addedNodes))if(a instanceof Element&&a.matches(t)){n(a);return}}).observe(document.body,{childList:!0,subtree:!0})}const pt='',vt='',ht='',bt='',yt='';let O="";async function D(){if(O)return O;const t=document.querySelector('script[type="module"]');if(!t)throw new Error("Failed to find script with auth token.");const r=(await(await fetch(t.src)).text()).match(new RegExp('(?<=")eyJhbGciOiJ.+?(?=")'));if(!r)throw new Error("Failed to find auth token from script.");return O=r[0],O}async function W(t,n){const e=await fetch(`https://amp-api.music.apple.com/v1/catalog/${n}/albums/${t}?extend=extendedAssetUrls`,{headers:{Authorization:`Bearer ${await D()}`}});return e.status!==200?null:(await e.json()).data[0]}async function gt(){return(await(await fetch("https://api.music.apple.com/v1/storefronts",{headers:{Authorization:`Bearer ${await D()}`}})).json()).data}const X="/:country/album/:slug/:id";function H(t){dt(X,t)}function P(t){ft(X,t)}let M=null;function $(t,n){return E(` + + `)}async function N(t,n){if(!M&&(M=await C("nav","amp-chrome-player"),!M))return;const e=Array.from(M.querySelectorAll(".ame-sidebar-button"));if(t.setAttribute("data-index",n.toString()),e.length===0){M.appendChild(t);return}let r=Number.MAX_VALUE,i=e[0];for(const a of e){const o=Math.abs(Number(a.getAttribute("data-index"))-n);o>=r||(r=o,i=a)}n>Number(i.getAttribute("data-index"))?i.after(t):i.before(t)}async function Y(t){t.remove()}const Et=`.ame-album-countries-header{margin:var(--bodyGutter) var(--bodyGutter) 1em;font-size:1.1em}.ame-album-countries-container{margin:1em var(--bodyGutter) var(--bodyGutter);font-size:0}.ame-album-countries-container div{display:contents}.ame-album-countries-container a,.ame-album-countries-container span{display:inline-block;margin-right:.5em;margin-bottom:.5em;font-size:13px} +`;GM.addStyle(Et);const Z=["jp","us","de","fr","gb","in","hk","it","es","br","au","nz"];Z.reverse();let y=null;const B=$("Check Countries",pt);B.addEventListener("click",async()=>{const t=document.querySelector(".section");t&&await K(t)}),H(async()=>{const t=await C(".page-error");t&&K(t)}),H(()=>{y==null||y.abort(),y=null}),P(()=>{y==null||y.abort(),y=null});async function K(t){if(y)return;const n=new AbortController;y=n;const e=location.pathname.split("/")[4],r=E('
Availability in the following storefronts:
'),i=E(` +
+
+
+
+
+ `),a=i.children[0],o=i.children[1],c=i.children[2];t.append(r),t.append(i);const d=await gt();d.sort((s,u)=>Math.max(Z.indexOf(u.id),0)-Math.max(Z.indexOf(s.id),0));for(const s of d){if(n.signal.aborted)break;const u=await W(e,s.id);if(!u){c.append(E(` + ${s.attributes.name}, + `)),await L(100);continue}const m=u.relationships.tracks.data.filter(l=>l.type==="songs").map((l,p)=>l.attributes.extendedAssetUrls?0:p+1).filter(Boolean);if(m.length){o.append(E(` + ${s.attributes.name}, + `)),await L(100);continue}a.append(E(` + ${s.attributes.name}, + `)),await L(100)}}const U=$("Search Covers",vt);U.addEventListener("click",()=>{const t=document.querySelector("h1.headings__title");if(!t)return;const n=t.innerText.replace(" - Single","").replace(" - EP",""),e=document.querySelectorAll(".headings__subtitles > a"),r=Array.from(e).map(i=>i.innerText).join(" ");open(`https://covers.musichoarders.xyz?artist=${encodeURIComponent(r)}&album=${encodeURIComponent(n)}`,"_blank")});const wt=[],At=[],xt=[],J=[];addEventListener("mousedown",async t=>{var u,m;const n=t.composedPath().slice(0,-5).filter(l=>l instanceof HTMLElement);if(!n.find(l=>l.matches("amp-contextual-menu-button")))return;const r=await C("amp-contextual-menu",void 0,300);if(!r)return;const i=await C("ul",void 0,300,r.shadowRoot);if(!i)return;const a=location.href.split("/").pop(),o=n.find(l=>l.classList.contains("artist-header"));if(a&&o){for(const l of wt)l(i,a);return}const c=n.find(l=>l.classList.contains("container-detail-header"));if(a&&c){if(location.href.includes("/playlist/")){for(const l of At)l(i,a);return}if(location.href.includes("/album/")){for(const l of xt)l(i,a);return}}const d=n.find(l=>l.classList.contains("songs-list-row")),s=(m=(u=d==null?void 0:d.previousElementSibling)==null?void 0:u.getAttribute("href"))==null?void 0:m.split("/").pop();if(s){for(const l of J)l(i,s);return}});function Tt(t,n){return E(` + +
  • + +
  • +
    + `)}function _t(t,n,e=Number.MAX_VALUE){n.addEventListener("click",o=>{var d,s;const c=(s=(d=t.previousElementSibling)==null?void 0:d.shadowRoot)==null?void 0:s.firstElementChild;c==null||c.click()},{once:!0});const r=Array.from(n.querySelectorAll("amp-contextual-menu-item"));if(n.setAttribute("data-priority",e.toString()),r.length===0){t.prepend(n);return}let i=Number.MAX_VALUE,a=r[0];for(const o of r){const c=Math.abs(Number(o.getAttribute("data-priority"))-e);c>=i||(i=c,a=o)}e>Number(a.getAttribute("data-priority"))?a.after(n):a.before(n)}function qt(t){J.push(t)}const tt=$("Copy Authorization",ht);tt.addEventListener("click",async()=>{GM.setClipboard(await D())}),qt((t,n)=>{const e=Tt("Copy ID",yt);e.addEventListener("click",()=>{GM.setClipboard(n)}),_t(t,e)});let g=null;const et=$("Check Qualities",bt);et.addEventListener("click",async()=>{var a;if(g)return;const t=new AbortController;g=t;const n=location.pathname.split("/")[1],e=location.pathname.split("/")[4],r=await W(e,n);if(!r)return;const i=Array.from(document.querySelectorAll(".songs-list-row__song-wrapper"));for(const o of r.relationships.tracks.data){if(t.signal.aborted)break;if(o.type!=="songs")continue;const c=i.shift();if(!c)continue;if((a=c.querySelector(".ame-track-quality"))==null||a.remove(),!o.attributes.extendedAssetUrls){c.appendChild(E('[unavailable]'));continue}const d=await(await fetch(o.attributes.extendedAssetUrls.enhancedHls)).text();let s;for(const m of d.split(` +`)){if(!m.startsWith('#EXT-X-SESSION-DATA:DATA-ID="com.apple.hls.audioAssetMetadata"'))continue;const l=m.split("VALUE=")[1].slice(1,-1);s=JSON.parse(atob(l));break}const u=Object.values(s).sort(Ct).map(Mt);c.appendChild(E(`${u[0]}`)),await L(150)}}),H(()=>{g==null||g.abort(),g=null}),P(()=>{g==null||g.abort(),g=null});const nt=["alac","aac ","aach"];function Ct(t,n){return nt.indexOf(t["AUDIO-FORMAT-ID"])-nt.indexOf(n["AUDIO-FORMAT-ID"])||t["BIT-DEPTH"]-n["BIT-DEPTH"]||t["SAMPLE-RATE"]-n["SAMPLE-RATE"]||t["BIT-RATE"]-n["BIT-RATE"]}function Mt(t){switch(t["AUDIO-FORMAT-ID"]){case"alac":return`ALAC ${t["BIT-DEPTH"]}bit ${Math.floor(Number(t["SAMPLE-RATE"])/1e3)}kHz`;case"aac ":return`AAC ${Math.floor(Number(t["BIT-RATE"])/1e3)}kbps`;case"aach":return`AAC-HE ${Math.floor(Number(t["BIT-RATE"])/1e3)}kbps`}return"Unknown"}const Rt=`.ame-color-primary{color:var(--systemPrimary)}.ame-color-secondary{color:var(--systemSecondary)}.ame-color-tertiary{color:var(--systemTertiary)}.ame-color-warning{color:var(--systemYellow)}.upsell-banner,.banner-container{display:none}@media (min-width: 484px){.header{pointer-events:none}#navigation>*{pointer-events:auto}#scrollable-page{grid-column-start:1;grid-column-end:3;padding-left:33.8843vw}}@media (min-width: 767px){#scrollable-page{padding-left:260px}}.navigation__scrollable-container+.navigation__native-cta{display:none}nav{padding-bottom:.5em;grid-template-rows:min-content min-content minmax(0,min-content) min-content min-content min-content min-content min-content min-content min-content min-content!important}.navigation__scrollable-container{margin-bottom:.5em}.navigation__native-cta{display:contents}.native-cta{padding-top:8px!important;padding-bottom:8px!important;border-top:none!important}.native-cta__button svg,.native-cta__button .native-cta__label{transition:50ms linear color,50ms linear fill}.native-cta__button:active svg,.native-cta__button:active .native-cta__label{color:#fff!important;fill:#fff!important}.page-error{width:100%!important;max-width:900px!important}.page-error__title+.button button{padding-left:1em;padding-right:1em} +`;GM.addStyle(Rt),C("nav","amp-chrome-player").then(t=>{t&&N(tt,0)}),H(async()=>{N(B,100),N(et,200),N(U,300)}),P(()=>{Y(U),Y(B)}),mt('iframe[src^="/includes/commerce/subscribe"]',()=>{const t=document.querySelector(".backdrop");t==null||t.click()})}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fb1f81a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1427 @@ +{ + "name": "ame", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ame", + "hasInstallScript": true, + "dependencies": { + "path-to-regexp": "^6.2.1" + }, + "devDependencies": { + "@types/tampermonkey": "^4.0.5", + "globby": "^13.1.3", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.11.tgz", + "integrity": "sha512-j2xsG1OETgCe+OBA54DG5vLuGjmMZtQvyxt+rTw2aYK/RqjcG/F+UDdj43uoUOv8lSRC3lM4XpKLOVZfY/82yA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.11.tgz", + "integrity": "sha512-CPwhZd15PasQSlkFuZv1st37xvuBeklztfb9y2GZWLQu59zcMIDkZVSEz/TTIxzt811+eJfblg5HhP49iVVDWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.11.tgz", + "integrity": "sha512-vbFn+0JXX6FkKq+0sNeA6aF2QhuOt9ZkBl+DSyqKIF+Ms58lUOhbqSwberKWQDm0udgOp3d/LhOFTYmpvmlZmA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.11.tgz", + "integrity": "sha512-1tqsIG6AySZ9njT8V2ddH1F/J01zX+0obPCpP0uD9TMIUlAA5WUF/+abFlnIsNY4jACcbN/13NUbLRWE9bayjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.11.tgz", + "integrity": "sha512-Gqx2/nYqnK46dwEDPGv3SwLqgLIZQJ7m2xNoNRzO50VZPvoCWSUqDaoirrZZf7uVfl+fxHoZBcdQJx2gOdxffQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.11.tgz", + "integrity": "sha512-58FTdlgIQ3ZxFtGphjbIBmo7kfDhQih/PlfAnKraAcCDZOYXWcRFmHJtW+EVg32IIxuEAqhLAzCgrqpm5o8Wlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.11.tgz", + "integrity": "sha512-L7hr6VnpqZzYEDVQeaViz1QnmfFRCRm3zVtljbYi/CU6InKs6tda1J3pAvqVsbNpbGMA9AvyiyBrgjJAFCawVg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.11.tgz", + "integrity": "sha512-Mk6TZij71alyS0FGuKEKYjTZGjUw2uXi07V/AiGZW1b5grTfGx6lpsbQdystgDJqju99Osq2Ix+C7WteSnwrHg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.11.tgz", + "integrity": "sha512-OKU0ajh9Xu7Pd1MlSq8Xqj5SJEV+4yVnALydPTDrrmTyvU72P8mTRJgZMilHw7H+Jqc0utryjNOwlJ/+fOkwGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.11.tgz", + "integrity": "sha512-pr1/tdDfgQQ9hp2IskSKMuwkx2X4jR7iHLbqEmmj/lPLKeoa6AUulnglGY4y0OPo+0eAYd6DzWp7ve3KI4lOCA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.11.tgz", + "integrity": "sha512-2MCYdDBh9R+R1xuBFiApgkbp/tW1uV+aVeefKYqWSEk3o6MHzWo1FxEGA4dSnC+kThSBOMVpCV9z4/DPouA3bQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.11.tgz", + "integrity": "sha512-IyotdnRg0J8F9FKttYe3cy/M9ZJ5W/Gm6whH08sbXMxRVKs/TyyoqFIA8oT1MzR+si6GLlRpcF7JbUnOXssjPA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.11.tgz", + "integrity": "sha512-NUMtxvb0j41UL8yf8VnTMNbCQxKqIPmF0Wf/N44UrxpKE8iCNmWT95Wt98Ivr2ebHdz+V3kptlgBuZNYcJLI6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.11.tgz", + "integrity": "sha512-03/B26az/JezvVkgck+lhauP13t6RqzCQgnrkBCBrXXpX+2r02DfSU43BEhpErJrsrDA8GXSE/rvsfbGCX6OvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.11.tgz", + "integrity": "sha512-Xs2tRB0fgly4XfC4FMv1Fd699AMEH8BClp36mzqRuVzm/285XIJaK5cPEZ9cLLn9ukNHdvvSX/83u5uS1BCd8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.11.tgz", + "integrity": "sha512-CiNialxsjJllrG3ggzOKzSaqQK/De/Mv4g/3r7jxLt01GLerPh0Q3TVTndFG9VfOrR1PdN7Fz5AOV3bE6Isd1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.11.tgz", + "integrity": "sha512-PiljZi6QZ3Pz0pL8rfJqPln8F/a3mEJwh2vhBb1kDYLniLufo9/7AInb/ZyhvgR7FxUQluUYyz64owPomgaLJA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.11.tgz", + "integrity": "sha512-Nyk8aJM+w6NoS4RGQJ0ybb516jEIbEVlLvhRIdpCssUuqKU0lr9lJPHnFY2QqyoVaJkd6VxaHOBU/v/ieuiENQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.11.tgz", + "integrity": "sha512-shxBLdNJecr7KxuyZQ19idBU8x7Mq7m+N5Fj8ROWMWQbDdjSjlBPxz7EZJIxSh7FUgSMKl7qSCCVaczXrta4MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.11.tgz", + "integrity": "sha512-vyTbfoEBn7cGXK8writbsB+G2wyRoOA+EbTNQ9cu5lyLU65sfWetCaL8T7mX338AN8tTbCYl6ce5YRKTonpA3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.11.tgz", + "integrity": "sha512-ATGCGc52LNqakUE9i54RzFC4lm70UTcTW721AFGjQotc6uCg7sf7QeRd05wD5tLBFafHdMSZv4rsU/Nh7LT/rQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.11.tgz", + "integrity": "sha512-7NcClJIctrO3iRu5CCqwdSBePm8bL2Iu1DYsuOnxuYJ+a1Kv3Wn3MzNdJIrUPLi1yADVwRliRUU/jtMC/tJnJA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/tampermonkey": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tampermonkey/-/tampermonkey-4.0.5.tgz", + "integrity": "sha512-FGPo7d+qZkDF7vyrwY1WNhcUnfDyVpt2uyL7krAu3WKCUMCfIUzOuvt8aSk8N2axHT8XPr9stAEDGVHLvag6Pw==", + "dev": true + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/esbuild": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.11.tgz", + "integrity": "sha512-Al0hhRUz/cCDvDp9VZp1L500HZZQ/HLjgTnQTmnW97+PoLmw+PuvB3e19JHYZtWnrxoh3qYrN/0tiRIbrE2oVQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.16.11", + "@esbuild/android-arm64": "0.16.11", + "@esbuild/android-x64": "0.16.11", + "@esbuild/darwin-arm64": "0.16.11", + "@esbuild/darwin-x64": "0.16.11", + "@esbuild/freebsd-arm64": "0.16.11", + "@esbuild/freebsd-x64": "0.16.11", + "@esbuild/linux-arm": "0.16.11", + "@esbuild/linux-arm64": "0.16.11", + "@esbuild/linux-ia32": "0.16.11", + "@esbuild/linux-loong64": "0.16.11", + "@esbuild/linux-mips64el": "0.16.11", + "@esbuild/linux-ppc64": "0.16.11", + "@esbuild/linux-riscv64": "0.16.11", + "@esbuild/linux-s390x": "0.16.11", + "@esbuild/linux-x64": "0.16.11", + "@esbuild/netbsd-x64": "0.16.11", + "@esbuild/openbsd-x64": "0.16.11", + "@esbuild/sunos-x64": "0.16.11", + "@esbuild/win32-arm64": "0.16.11", + "@esbuild/win32-ia32": "0.16.11", + "@esbuild/win32-x64": "0.16.11" + } + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.8.1.tgz", + "integrity": "sha512-4yh9eMW7byOroYcN8DlF9P/2jCpu6txVIHjEqquQVSx7DI0RgyCCN3tjrcy4ra6yVtV336aLBB3v2AarYAxePQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/vite": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz", + "integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==", + "dev": true, + "dependencies": { + "esbuild": "^0.16.3", + "postcss": "^8.4.20", + "resolve": "^1.22.1", + "rollup": "^3.7.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.11.tgz", + "integrity": "sha512-j2xsG1OETgCe+OBA54DG5vLuGjmMZtQvyxt+rTw2aYK/RqjcG/F+UDdj43uoUOv8lSRC3lM4XpKLOVZfY/82yA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.11.tgz", + "integrity": "sha512-CPwhZd15PasQSlkFuZv1st37xvuBeklztfb9y2GZWLQu59zcMIDkZVSEz/TTIxzt811+eJfblg5HhP49iVVDWQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.11.tgz", + "integrity": "sha512-vbFn+0JXX6FkKq+0sNeA6aF2QhuOt9ZkBl+DSyqKIF+Ms58lUOhbqSwberKWQDm0udgOp3d/LhOFTYmpvmlZmA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.11.tgz", + "integrity": "sha512-1tqsIG6AySZ9njT8V2ddH1F/J01zX+0obPCpP0uD9TMIUlAA5WUF/+abFlnIsNY4jACcbN/13NUbLRWE9bayjw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.11.tgz", + "integrity": "sha512-Gqx2/nYqnK46dwEDPGv3SwLqgLIZQJ7m2xNoNRzO50VZPvoCWSUqDaoirrZZf7uVfl+fxHoZBcdQJx2gOdxffQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.11.tgz", + "integrity": "sha512-58FTdlgIQ3ZxFtGphjbIBmo7kfDhQih/PlfAnKraAcCDZOYXWcRFmHJtW+EVg32IIxuEAqhLAzCgrqpm5o8Wlw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.11.tgz", + "integrity": "sha512-L7hr6VnpqZzYEDVQeaViz1QnmfFRCRm3zVtljbYi/CU6InKs6tda1J3pAvqVsbNpbGMA9AvyiyBrgjJAFCawVg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.11.tgz", + "integrity": "sha512-Mk6TZij71alyS0FGuKEKYjTZGjUw2uXi07V/AiGZW1b5grTfGx6lpsbQdystgDJqju99Osq2Ix+C7WteSnwrHg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.11.tgz", + "integrity": "sha512-OKU0ajh9Xu7Pd1MlSq8Xqj5SJEV+4yVnALydPTDrrmTyvU72P8mTRJgZMilHw7H+Jqc0utryjNOwlJ/+fOkwGw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.11.tgz", + "integrity": "sha512-pr1/tdDfgQQ9hp2IskSKMuwkx2X4jR7iHLbqEmmj/lPLKeoa6AUulnglGY4y0OPo+0eAYd6DzWp7ve3KI4lOCA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.11.tgz", + "integrity": "sha512-2MCYdDBh9R+R1xuBFiApgkbp/tW1uV+aVeefKYqWSEk3o6MHzWo1FxEGA4dSnC+kThSBOMVpCV9z4/DPouA3bQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.11.tgz", + "integrity": "sha512-IyotdnRg0J8F9FKttYe3cy/M9ZJ5W/Gm6whH08sbXMxRVKs/TyyoqFIA8oT1MzR+si6GLlRpcF7JbUnOXssjPA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.11.tgz", + "integrity": "sha512-NUMtxvb0j41UL8yf8VnTMNbCQxKqIPmF0Wf/N44UrxpKE8iCNmWT95Wt98Ivr2ebHdz+V3kptlgBuZNYcJLI6g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.11.tgz", + "integrity": "sha512-03/B26az/JezvVkgck+lhauP13t6RqzCQgnrkBCBrXXpX+2r02DfSU43BEhpErJrsrDA8GXSE/rvsfbGCX6OvA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.11.tgz", + "integrity": "sha512-Xs2tRB0fgly4XfC4FMv1Fd699AMEH8BClp36mzqRuVzm/285XIJaK5cPEZ9cLLn9ukNHdvvSX/83u5uS1BCd8g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.11.tgz", + "integrity": "sha512-CiNialxsjJllrG3ggzOKzSaqQK/De/Mv4g/3r7jxLt01GLerPh0Q3TVTndFG9VfOrR1PdN7Fz5AOV3bE6Isd1Q==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.11.tgz", + "integrity": "sha512-PiljZi6QZ3Pz0pL8rfJqPln8F/a3mEJwh2vhBb1kDYLniLufo9/7AInb/ZyhvgR7FxUQluUYyz64owPomgaLJA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.11.tgz", + "integrity": "sha512-Nyk8aJM+w6NoS4RGQJ0ybb516jEIbEVlLvhRIdpCssUuqKU0lr9lJPHnFY2QqyoVaJkd6VxaHOBU/v/ieuiENQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.11.tgz", + "integrity": "sha512-shxBLdNJecr7KxuyZQ19idBU8x7Mq7m+N5Fj8ROWMWQbDdjSjlBPxz7EZJIxSh7FUgSMKl7qSCCVaczXrta4MQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.11.tgz", + "integrity": "sha512-vyTbfoEBn7cGXK8writbsB+G2wyRoOA+EbTNQ9cu5lyLU65sfWetCaL8T7mX338AN8tTbCYl6ce5YRKTonpA3w==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.11.tgz", + "integrity": "sha512-ATGCGc52LNqakUE9i54RzFC4lm70UTcTW721AFGjQotc6uCg7sf7QeRd05wD5tLBFafHdMSZv4rsU/Nh7LT/rQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.11.tgz", + "integrity": "sha512-7NcClJIctrO3iRu5CCqwdSBePm8bL2Iu1DYsuOnxuYJ+a1Kv3Wn3MzNdJIrUPLi1yADVwRliRUU/jtMC/tJnJA==", + "dev": true, + "optional": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true, + "optional": true, + "peer": true + }, + "@types/tampermonkey": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tampermonkey/-/tampermonkey-4.0.5.tgz", + "integrity": "sha512-FGPo7d+qZkDF7vyrwY1WNhcUnfDyVpt2uyL7krAu3WKCUMCfIUzOuvt8aSk8N2axHT8XPr9stAEDGVHLvag6Pw==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "esbuild": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.11.tgz", + "integrity": "sha512-Al0hhRUz/cCDvDp9VZp1L500HZZQ/HLjgTnQTmnW97+PoLmw+PuvB3e19JHYZtWnrxoh3qYrN/0tiRIbrE2oVQ==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.16.11", + "@esbuild/android-arm64": "0.16.11", + "@esbuild/android-x64": "0.16.11", + "@esbuild/darwin-arm64": "0.16.11", + "@esbuild/darwin-x64": "0.16.11", + "@esbuild/freebsd-arm64": "0.16.11", + "@esbuild/freebsd-x64": "0.16.11", + "@esbuild/linux-arm": "0.16.11", + "@esbuild/linux-arm64": "0.16.11", + "@esbuild/linux-ia32": "0.16.11", + "@esbuild/linux-loong64": "0.16.11", + "@esbuild/linux-mips64el": "0.16.11", + "@esbuild/linux-ppc64": "0.16.11", + "@esbuild/linux-riscv64": "0.16.11", + "@esbuild/linux-s390x": "0.16.11", + "@esbuild/linux-x64": "0.16.11", + "@esbuild/netbsd-x64": "0.16.11", + "@esbuild/openbsd-x64": "0.16.11", + "@esbuild/sunos-x64": "0.16.11", + "@esbuild/win32-arm64": "0.16.11", + "@esbuild/win32-ia32": "0.16.11", + "@esbuild/win32-x64": "0.16.11" + } + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "postcss": { + "version": "8.4.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", + "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rollup": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.8.1.tgz", + "integrity": "sha512-4yh9eMW7byOroYcN8DlF9P/2jCpu6txVIHjEqquQVSx7DI0RgyCCN3tjrcy4ra6yVtV336aLBB3v2AarYAxePQ==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "typescript": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "dev": true + }, + "vite": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz", + "integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==", + "dev": true, + "requires": { + "esbuild": "^0.16.3", + "fsevents": "~2.3.2", + "postcss": "^8.4.20", + "resolve": "^1.22.1", + "rollup": "^3.7.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a95ac80 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "ame", + "type": "module", + "homepage": "https://notabug.org/SuperSaltyGamer/ame/dist/", + "dependencies": { + "path-to-regexp": "^6.2.1" + }, + "devDependencies": { + "@types/tampermonkey": "^4.0.5", + "globby": "^13.1.3", + "typescript": "^4.9.3", + "vite": "^4.0.0" + }, + "scripts": { + "start": "node out/build.js", + "build": "tsc && node out/build.js --production", + "postinstall": "tsc -p tsconfig.scripts.json" + } +} diff --git a/scripts/_userscript.ts b/scripts/_userscript.ts new file mode 100644 index 0000000..7d2c1b2 --- /dev/null +++ b/scripts/_userscript.ts @@ -0,0 +1,75 @@ +import { readFile } from 'fs/promises'; +import { basename, dirname, join } from 'path'; +import { LibraryFormats, Plugin, ResolvedConfig } from 'vite'; + +export interface UserScriptOptions { + entry: string; + format: LibraryFormats; + name?: string; + port?: number; + cdn?: string; +} + +export function _userscript(options: UserScriptOptions): Plugin { + let config: ResolvedConfig; + + return { + name: 'userscript', + config(config, env) { + const name = options.name ?? basename(dirname(options.entry)); + + return { + build: { + lib: { + name: name, + formats: [ options.format ], + entry: { + [name]: options.entry + } + }, + rollupOptions: { + output: { + entryFileNames: '[name].user.js' + } + } + } + }; + }, + configResolved(resolvedConfig) { + config = resolvedConfig; + }, + async generateBundle(outputOptions, bundle) { + for (const chunk of Object.values(bundle)) { + if (chunk.type !== 'chunk') continue; + if (!chunk.isEntry) continue; + if (!chunk.facadeModuleId) continue; + + const code = await readFile(chunk.facadeModuleId, { encoding: 'utf8' }); + + let header = ''; + for (const line of code.split('\n')) { + if (!line.startsWith('//')) break; + header += line + '\n'; + } + + let url = ''; + if (config.mode === 'production') { + if (options.cdn) url = `${options.cdn}${chunk.name}.user.js`; + } else { + if (options.port) url = `http://localhost:${options.port}/${chunk.name}.user.js`; + } + + if (url) { + header = header.replaceAll( + '// ==/UserScript==', + `// @downloadURL ${url}\n` + + `// @updateURL ${url}\n` + + '// ==/UserScript==' + ); + } + + chunk.code = header + '\n' + chunk.code; + } + } + }; +} diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..efb37af --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,65 @@ +import { readFile } from 'fs/promises'; +import { globbyStream } from 'globby'; +import { parseArgs } from 'util'; +import { build, createServer, InlineConfig } from 'vite'; +import { _userscript } from './_userscript.js'; + +const pkg = JSON.parse(await readFile('package.json', 'utf8')); + +const args = parseArgs({ + options: { + production: { + type: 'boolean', + default: false + } + } +}); + +const server = await createServer({ + plugins: [ + { + name: 'rewrite', + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + req.url = '/out' + req.url; + next(); + }); + } + } + ], + optimizeDeps: { + disabled: true + } +}); + +if (!args.values.production) { + await server.listen(); + await server.printUrls(); +} + +const entries: string[] = []; +for await (const path of await globbyStream('src/**/main.ts')) { + const code = await readFile(path, { encoding: 'utf8' }); + if (!code.startsWith('// ==UserScript==')) continue; + entries.push(path.toString()); +} + +const configs = entries.map(entry => ({ + mode: args.values.production ? 'production' : 'development', + plugins: [ + _userscript({ + entry: entry, + format: 'umd', + port: server.config.server.port, + cdn: args.values.production ? pkg.homepage : '', + }) + ], + build: { + watch: args.values.production ? undefined : {}, + outDir: args.values.production ? 'dist/' : 'out/', + emptyOutDir: args.values.production + } +})); + +await Promise.all(configs.map(build)); +await server.close(); diff --git a/src/applemusic/glue/routing.ts b/src/applemusic/glue/routing.ts new file mode 100644 index 0000000..9db44f0 --- /dev/null +++ b/src/applemusic/glue/routing.ts @@ -0,0 +1,12 @@ +import { offRoute, onRoute } from '../../common'; +import { Callback } from '../../common/types'; + +const ALBUM_PATTERN = '/:country/album/:slug/:id'; + +export function onAlbumRoute(cb: Callback) { + onRoute(ALBUM_PATTERN, cb); +} + +export function offAlbumRoute(cb: Callback) { + offRoute(ALBUM_PATTERN, cb); +} diff --git a/src/applemusic/glue/ui/menu.ts b/src/applemusic/glue/ui/menu.ts new file mode 100644 index 0000000..882b73c --- /dev/null +++ b/src/applemusic/glue/ui/menu.ts @@ -0,0 +1,123 @@ +import { fromHTML, waitFor } from '../../../common'; +import { Icon } from '../../icons'; + +export type MenuElement = Brand; +export type MenuItemElement = Brand; + +type MenuCallback = (menuEl: MenuElement, id: string) => any; + +const artistMenuCallbacks: MenuCallback[] = []; +const playlistMenuCallbacks: MenuCallback[] = []; +const albumMenuCallbacks: MenuCallback[] = []; +const trackMenuCallbacks: MenuCallback[] = []; + +addEventListener('mousedown', async (e) => { + const path = e.composedPath() + .slice(0, -5) + .filter(el => el instanceof HTMLElement) as HTMLElement[]; + + const buttonEl = path.find(el => el.matches('amp-contextual-menu-button')); + if (!buttonEl) return; + + const shadowMenuEl = await waitFor('amp-contextual-menu', undefined, 300); + if (!shadowMenuEl) return; + + const menuEl = await waitFor('ul', undefined, 300, shadowMenuEl.shadowRoot); + if (!menuEl) return; + + const collectionId = location.href.split('/').pop(); + + const artistHeaderEl = path.find(el => el.classList.contains('artist-header')); + if (collectionId && artistHeaderEl) { + for (const cb of artistMenuCallbacks) cb(menuEl, collectionId); + return; + } + + const collectionHeaderEl = path.find(el => el.classList.contains('container-detail-header')); + if (collectionId && collectionHeaderEl) { + if (location.href.includes('/playlist/')) { + for (const cb of playlistMenuCallbacks) cb(menuEl, collectionId); + return; + } + + if (location.href.includes('/album/')) { + for (const cb of albumMenuCallbacks) cb(menuEl, collectionId); + return; + } + } + + const trackEl = path.find(el => el.classList.contains('songs-list-row')); + const trackId = trackEl?.previousElementSibling?.getAttribute('href')?.split('/').pop(); + if (trackId) { + for (const cb of trackMenuCallbacks) cb(menuEl, trackId); + return; + } +}); + +export function createMenuItem(text: string, icon?: Icon): MenuItemElement { + return fromHTML(` + +
  • + +
  • +
    + `) as MenuItemElement; +} + +export function showMenuItem(menuEl: MenuElement, itemEl: MenuItemElement, priority: number = Number.MAX_VALUE) { + itemEl.addEventListener('click', (e) => { + const scrimButtonEl = menuEl.previousElementSibling?.shadowRoot?.firstElementChild as HTMLButtonElement | null; + scrimButtonEl?.click(); + }, { once: true }); + + const buttonEls = Array.from(itemEl.querySelectorAll('amp-contextual-menu-item')) as HTMLElement[]; + itemEl.setAttribute('data-priority', priority.toString()); + + if (buttonEls.length === 0) { + menuEl.prepend(itemEl); + return; + } + + let bestDist = Number.MAX_VALUE; + let refEl = buttonEls[0]; + + for (const buttonEl of buttonEls) { + const dist = Math.abs(Number(buttonEl.getAttribute('data-priority')) - priority); + if (dist >= bestDist) continue; + + bestDist = dist; + refEl = buttonEl; + } + + if (priority > Number(refEl.getAttribute('data-priority'))) { + refEl.after(itemEl); + } else { + refEl.before(itemEl); + } +} + +export function onArtistMenu(cb: MenuCallback) { + artistMenuCallbacks.push(cb); +} + +export function onPlaylistMenu(cb: MenuCallback) { + playlistMenuCallbacks.push(cb); +} + +export function onAlbumMenu(cb: MenuCallback) { + albumMenuCallbacks.push(cb); +} + +export function onTrackMenu(cb: MenuCallback) { + trackMenuCallbacks.push(cb); +} diff --git a/src/applemusic/glue/ui/sidebar.ts b/src/applemusic/glue/ui/sidebar.ts new file mode 100644 index 0000000..e403517 --- /dev/null +++ b/src/applemusic/glue/ui/sidebar.ts @@ -0,0 +1,62 @@ +import { fromHTML, waitFor } from '../../../common'; +import { Icon } from '../../icons'; + +export type ButtonElement = Brand; + +let navEl: HTMLElement | null = null; + +export function createButtonElement(text: string, icon: Icon): ButtonElement { + return fromHTML(` + + `) as ButtonElement; +} + +export async function showButtonElement(buttonEl: ButtonElement, index: number) { + if (!navEl) { + navEl = await waitFor('nav', 'amp-chrome-player'); + if (!navEl) return; + } + + const buttonEls = Array.from(navEl.querySelectorAll('.ame-sidebar-button')) as HTMLElement[]; + buttonEl.setAttribute('data-index', index.toString()); + + if (buttonEls.length === 0) { + navEl.appendChild(buttonEl); + return; + } + + let bestDist = Number.MAX_VALUE; + let refEl = buttonEls[0]; + + for (const buttonEl of buttonEls) { + const dist = Math.abs(Number(buttonEl.getAttribute('data-index')) - index); + if (dist >= bestDist) continue; + + bestDist = dist; + refEl = buttonEl; + } + + if (index > Number(refEl.getAttribute('data-index'))) { + refEl.after(buttonEl); + } else { + refEl.before(buttonEl); + } +} + +export async function hideButtonElement(buttonEl: ButtonElement) { + buttonEl.remove(); +} diff --git a/src/applemusic/icons.ts b/src/applemusic/icons.ts new file mode 100644 index 0000000..8b3ef63 --- /dev/null +++ b/src/applemusic/icons.ts @@ -0,0 +1,7 @@ +export type Icon = Brand; + +export const flagIcon = `` as Icon; +export const paletteIcon = `` as Icon; +export const shieldIcon = `` as Icon; +export const hqIcon = `` as Icon; +export const codeIcon = `` as Icon; diff --git a/src/applemusic/main.ts b/src/applemusic/main.ts new file mode 100644 index 0000000..7c5c263 --- /dev/null +++ b/src/applemusic/main.ts @@ -0,0 +1,49 @@ +// ==UserScript== +// @namespace ame-applemusic +// @name Ame (Apple Music) +// @version 1.0.0 +// @author SuperSaltyGamer +// @run-at document-start +// @match https://music.apple.com/* +// @grant GM.addStyle +// @grant GM.setClipboard +// @grant GM.xmlHttpRequest +// ==/UserScript== + +import { observe, waitFor } from '../common'; +import { checkCountriesButtonEl } from './modules/countries'; +import { searchCoversButtonEl } from './modules/covers'; +import { copyAuthButtonEl } from './modules/dev'; +import { checkQualitiesButtonEl } from './modules/qualities'; +import './modules/qualities'; +import styles from './style.css?inline'; +import { offAlbumRoute, onAlbumRoute } from './glue/routing'; +import { hideButtonElement, showButtonElement } from './glue/ui/sidebar'; + +GM.addStyle(styles); + +// Add sidebar button for all pages. +waitFor('nav', 'amp-chrome-player').then((navEl) => { + if (!navEl) return; + + showButtonElement(copyAuthButtonEl, 0); +}); + +// Add sidebar buttons for album page. +onAlbumRoute(async () => { + showButtonElement(checkCountriesButtonEl, 100); + showButtonElement(checkQualitiesButtonEl, 200); + showButtonElement(searchCoversButtonEl, 300); +}); + +// Remove sidebar buttons for non-album pages. +offAlbumRoute(() => { + hideButtonElement(searchCoversButtonEl); + hideButtonElement(checkCountriesButtonEl); +}); + +// Hide trial upselling modal. +observe('iframe[src^="/includes/commerce/subscribe"]', () => { + const backdropEl = document.querySelector('.backdrop'); + backdropEl?.click(); +}); diff --git a/src/applemusic/modules/countries.css b/src/applemusic/modules/countries.css new file mode 100644 index 0000000..66e8964 --- /dev/null +++ b/src/applemusic/modules/countries.css @@ -0,0 +1,21 @@ +.ame-album-countries-header { + margin: var(--bodyGutter) var(--bodyGutter) 1em; + font-size: 1.1em; +} + +.ame-album-countries-container { + margin: 1em var(--bodyGutter) var(--bodyGutter); + font-size: 0; +} + +.ame-album-countries-container div { + display: contents; +} + +.ame-album-countries-container a, +.ame-album-countries-container span { + display: inline-block; + margin-right: .5em; + margin-bottom: .5em; + font-size: 13px; +} diff --git a/src/applemusic/modules/countries.ts b/src/applemusic/modules/countries.ts new file mode 100644 index 0000000..34d4c6d --- /dev/null +++ b/src/applemusic/modules/countries.ts @@ -0,0 +1,105 @@ +import { fromHTML, sleep, waitFor } from '../../common'; +import { flagIcon } from '../icons'; +import { getAlbum, getStorefronts } from '../services/service'; +import { offAlbumRoute, onAlbumRoute } from '../glue/routing'; +import { createButtonElement } from '../glue/ui/sidebar'; +import styles from './countries.css?inline'; + +GM.addStyle(styles); + +const PREFERRED_STOREFRONTS = [ 'jp', 'us', 'de', 'fr', 'gb', 'in', 'hk', 'it', 'es', 'br', 'au', 'nz' ]; +PREFERRED_STOREFRONTS.reverse(); + +let globalJob: AbortController | null = null; + +export const checkCountriesButtonEl = createButtonElement('Check Countries', flagIcon); + +// Start checking countries when the sidebar button is clicked. +checkCountriesButtonEl.addEventListener('click', async () => { + const refEl = document.querySelector('.section'); + if (refEl) await checkCountries(refEl); +}); + +// Start checking countries when the error page is shown. +onAlbumRoute(async () => { + const errorEl = await waitFor('.page-error'); + if (errorEl) checkCountries(errorEl); +}); + +onAlbumRoute(() => { + globalJob?.abort(); + globalJob = null; +}); + +offAlbumRoute(() => { + globalJob?.abort(); + globalJob = null; +}); + +async function checkCountries(refEl: HTMLElement) { + if (globalJob) return; + const job = new AbortController(); + globalJob = job; + + const albumId = location.pathname.split('/')[4]; + + const headerEl = fromHTML(`
    Availability in the following storefronts:
    `); + const containerEl = fromHTML(` +
    +
    +
    +
    +
    + `); + + const primaryContainerEl = containerEl.children[0]; + const secondaryContainerEl = containerEl.children[1]; + const tertiaryContainerEl = containerEl.children[2]; + + refEl.append(headerEl); + refEl.append(containerEl); + + const storefronts = await getStorefronts(); + + storefronts.sort((a, b) => { + return Math.max(PREFERRED_STOREFRONTS.indexOf(b.id), 0) - Math.max(PREFERRED_STOREFRONTS.indexOf(a.id), 0); + }); + + for (const storefront of storefronts) { + if (job.signal.aborted) break; + + const album = await getAlbum(albumId, storefront.id); + + // Album totally unavailable. + if (!album) { + tertiaryContainerEl.append(fromHTML(` + ${storefront.attributes.name}, + `)); + + await sleep(100); + continue; + } + + const unavailableTracks = album.relationships.tracks.data + .filter(track => track.type === 'songs') + .map((track, i) => track.attributes.extendedAssetUrls ? 0 : i + 1) + .filter(Boolean); + + // Album partially available. + if (unavailableTracks.length) { + secondaryContainerEl.append(fromHTML(` + ${storefront.attributes.name}, + `)); + + await sleep(100); + continue; + } + + // Album fully available. + primaryContainerEl.append(fromHTML(` + ${storefront.attributes.name}, + `)); + + await sleep(100); + } +} diff --git a/src/applemusic/modules/covers.ts b/src/applemusic/modules/covers.ts new file mode 100644 index 0000000..1e3224d --- /dev/null +++ b/src/applemusic/modules/covers.ts @@ -0,0 +1,18 @@ +import { paletteIcon } from '../icons'; +import { createButtonElement } from '../glue/ui/sidebar'; + +export const searchCoversButtonEl = createButtonElement('Search Covers', paletteIcon); + +searchCoversButtonEl.addEventListener('click', () => { + const titleEl = document.querySelector('h1.headings__title'); + if (!titleEl) return; + + const title = titleEl.innerText + .replace(' - Single', '') + .replace(' - EP', ''); + + const artistEls = document.querySelectorAll('.headings__subtitles > a'); + const artist = Array.from(artistEls).map(el => el.innerText).join(' '); + + open(`https://covers.musichoarders.xyz?artist=${encodeURIComponent(artist)}&album=${encodeURIComponent(title)}`, '_blank'); +}); diff --git a/src/applemusic/modules/dev.ts b/src/applemusic/modules/dev.ts new file mode 100644 index 0000000..7ecabe2 --- /dev/null +++ b/src/applemusic/modules/dev.ts @@ -0,0 +1,20 @@ +import { createMenuItem, onTrackMenu, showMenuItem } from '../glue/ui/menu'; +import { codeIcon, shieldIcon } from '../icons'; +import { getToken } from '../services/auth'; +import { createButtonElement } from '../glue/ui/sidebar'; + +export const copyAuthButtonEl = createButtonElement('Copy Authorization', shieldIcon); + +copyAuthButtonEl.addEventListener('click', async () => { + GM.setClipboard(await getToken()); +}); + +onTrackMenu((menuEl, trackId) => { + const copyTrackIdButtonEl = createMenuItem('Copy ID', codeIcon); + + copyTrackIdButtonEl.addEventListener('click', () => { + GM.setClipboard(trackId); + }); + + showMenuItem(menuEl, copyTrackIdButtonEl); +}); diff --git a/src/applemusic/modules/qualities.ts b/src/applemusic/modules/qualities.ts new file mode 100644 index 0000000..d4d153c --- /dev/null +++ b/src/applemusic/modules/qualities.ts @@ -0,0 +1,96 @@ +import { fromHTML, sleep } from '../../common'; +import { hqIcon } from '../icons'; +import { getAlbum } from '../services/service'; +import { createMenuItem } from '../glue/ui/menu'; +import { offAlbumRoute, onAlbumRoute } from '../glue/routing'; +import { createButtonElement } from '../glue/ui/sidebar'; + +interface Quality { + 'AUDIO-FORMAT-ID': string; + 'SAMPLE-RATE': number; + 'BIT-DEPTH': number; + 'BIT-RATE': number; +} + +let globalJob: AbortController | null = null; + +export const checkQualitiesButtonEl = createButtonElement('Check Qualities', hqIcon); + +checkQualitiesButtonEl.addEventListener('click', async () => { + if (globalJob) return; + const job = new AbortController(); + globalJob = job; + + const country = location.pathname.split('/')[1]; + const albumId = location.pathname.split('/')[4]; + + const album = await getAlbum(albumId, country); + if (!album) return; + + const trackEls = Array.from(document.querySelectorAll('.songs-list-row__song-wrapper')); + + for (const track of album.relationships.tracks.data) { + if (job.signal.aborted) break; + if (track.type !== 'songs') continue; + + const trackEl = trackEls.shift(); + if (!trackEl) continue; + + trackEl.querySelector('.ame-track-quality')?.remove(); + + if (!track.attributes.extendedAssetUrls) { + trackEl.appendChild(fromHTML(`[unavailable]`)); + continue; + } + + const manifest = await (await fetch(track.attributes.extendedAssetUrls.enhancedHls)).text(); + + let data; + for (const line of manifest.split('\n')) { + if (!line.startsWith('#EXT-X-SESSION-DATA:DATA-ID="com.apple.hls.audioAssetMetadata"')) continue; + const encoded = line.split('VALUE=')[1].slice(1, -1); + data = JSON.parse(atob(encoded)); + break; + } + + const qualities = (Object.values(data) as Quality[]) + .sort(sortQuality) + .map(formatQuality); + + trackEl.appendChild(fromHTML(`${qualities[0]}`)); + + await sleep(150); + } +}); + +onAlbumRoute(() => { + globalJob?.abort(); + globalJob = null; +}); + +offAlbumRoute(() => { + globalJob?.abort(); + globalJob = null; +}); + +const formatOrder = [ 'alac', 'aac ', 'aach' ]; + +function sortQuality(a: Quality, b: Quality): number { + return formatOrder.indexOf(a['AUDIO-FORMAT-ID']) - formatOrder.indexOf(b['AUDIO-FORMAT-ID']) || + a['BIT-DEPTH'] - b['BIT-DEPTH'] || + a['SAMPLE-RATE'] - b['SAMPLE-RATE'] || + a['BIT-RATE'] - b['BIT-RATE']; +} + +function formatQuality(quality: Quality): string { + switch (quality['AUDIO-FORMAT-ID']) { + case 'alac': + return `ALAC ${quality['BIT-DEPTH']}bit ${Math.floor(Number(quality['SAMPLE-RATE']) / 1000)}kHz`; + case 'aac ': + return `AAC ${Math.floor(Number(quality['BIT-RATE']) / 1000)}kbps`; + case 'aach': + return `AAC-HE ${Math.floor(Number(quality['BIT-RATE']) / 1000)}kbps`; + } + + return 'Unknown'; +} diff --git a/src/applemusic/services/auth.ts b/src/applemusic/services/auth.ts new file mode 100644 index 0000000..fa882da --- /dev/null +++ b/src/applemusic/services/auth.ts @@ -0,0 +1,17 @@ +let cachedAuthToken = ''; + +export async function getToken(): Promise { + if (cachedAuthToken) return cachedAuthToken; + + const scriptEl = document.querySelector('script[type="module"]'); + if (!scriptEl) throw new Error('Failed to find script with auth token.'); + + const res = await fetch(scriptEl.src); + const body = await res.text(); + + const match = body.match(/(?<=")eyJhbGciOiJ.+?(?=")/); + if (!match) throw new Error('Failed to find auth token from script.'); + + cachedAuthToken = match[0]; + return cachedAuthToken; +} diff --git a/src/applemusic/services/service.ts b/src/applemusic/services/service.ts new file mode 100644 index 0000000..d992f6a --- /dev/null +++ b/src/applemusic/services/service.ts @@ -0,0 +1,59 @@ +import { getToken } from './auth'; + +interface Response { + data: Resource[]; +} + +interface Resource { + id: string; + type: string; + href: string; + attributes: T; + relationships: { + artists: Response; + tracks: Response; + } +} + +interface Artist { + name: string; +} + +interface Album { + name: string; +} + +interface Track { + name: string; + extendedAssetUrls: { + enhancedHls: string; + } +} + +interface Storefront { + name: string; +} + +export async function getAlbum(id: string, country: string): Promise | null> { + const res = await fetch(`https://amp-api.music.apple.com/v1/catalog/${country}/albums/${id}?extend=extendedAssetUrls`, { + headers: { + 'Authorization': `Bearer ${await getToken()}` + } + }); + + if (res.status !== 200) return null; + + const albums = await res.json>(); + return albums.data[0]; +} + +export async function getStorefronts(): Promise[]> { + const res = await fetch('https://api.music.apple.com/v1/storefronts', { + headers: { + 'Authorization': `Bearer ${await getToken()}` + } + }); + + const body = await res.json>(); + return body.data; +} diff --git a/src/applemusic/style.css b/src/applemusic/style.css new file mode 100644 index 0000000..1b411da --- /dev/null +++ b/src/applemusic/style.css @@ -0,0 +1,107 @@ +.ame-color-primary { + color: var(--systemPrimary); +} + +.ame-color-secondary { + color: var(--systemSecondary); +} + +.ame-color-tertiary { + color: var(--systemTertiary); +} + +.ame-color-warning { + color: var(--systemYellow); +} + +/* Hide trial upselling banner. */ + +.upsell-banner { + display: none; +} + +/* Hide foreign country banner */ + +.banner-container { + display: none; +} + +/* Make page content scrollable from the sidebar. */ + +@media (min-width: 484px) { + + .header { + pointer-events: none; + } + + #navigation > * { + pointer-events: auto; + } + + #scrollable-page { + grid-column-start: 1; + grid-column-end: 3; + padding-left: 33.8843vw; + } + +} + +@media (min-width: 767px) { + + #scrollable-page { + padding-left: 260px; + } + +} + +/* Hide Open in Music button in the sidebar. */ + +.navigation__scrollable-container + .navigation__native-cta { + display: none; +} + +/* Make sidebar buttons stick to the top and stack on top of each other. */ + +nav { + padding-bottom: .5em; + grid-template-rows: min-content min-content minmax(0, min-content) min-content min-content min-content min-content min-content min-content min-content min-content !important; +} + +.navigation__scrollable-container { + margin-bottom: .5em; +} + +.navigation__native-cta { + display: contents; +} + +/* Add focus styles to sidebar buttons. */ + +.native-cta { + padding-top: 8px !important; + padding-bottom: 8px !important; + border-top: none !important; +} + +.native-cta__button svg, +.native-cta__button .native-cta__label { + transition: 50ms linear color, 50ms linear fill; +} + +.native-cta__button:active svg, +.native-cta__button:active .native-cta__label { + color: white !important; + fill: white !important; +} + +/* Make more room for error messages and button text. */ + +.page-error { + width: 100% !important; + max-width: 900px !important; +} + +.page-error__title + .button button { + padding-left: 1em; + padding-right: 1em; +} diff --git a/src/common/fetch.ts b/src/common/fetch.ts new file mode 100644 index 0000000..619f199 --- /dev/null +++ b/src/common/fetch.ts @@ -0,0 +1,34 @@ +export function fetchCors(input: RequestInfo | URL, init?: RequestInit): Promise { + return new Promise((resolve, reject) => { + const fetchReq = new Request(input, init); + + GM.xmlHttpRequest({ + method: fetchReq.method as any, + url: fetchReq.url, + headers: Object.fromEntries(Array.from(fetchReq.headers)), + responseType: 'blob', + onload(res) { + const headers = res.responseHeaders + .split('\r\n') + .slice(0, -1) + .map(line => line.split(': ')); + + const fetchRes = new Response(res.response, { + headers: Object.fromEntries(headers), + status: res.status, + statusText: res.statusText + }); + + Object.defineProperty(fetchRes, 'url', { value: fetchReq.url }); + + resolve(fetchRes); + }, + onerror() { + reject(new TypeError('Network request errored.')); + }, + ontimeout() { + reject(new TypeError('Network request timed out.')); + } + }); + }); +} diff --git a/src/common/index.ts b/src/common/index.ts new file mode 100644 index 0000000..29491f1 --- /dev/null +++ b/src/common/index.ts @@ -0,0 +1,57 @@ +export * from './fetch'; +export * from './router'; + +export function sleep(delay: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} + +export function fromHTML(html: string): HTMLElement { + const div = document.createElement('div'); + div.innerHTML = html; + return div.firstElementChild as HTMLElement; +} + +export function waitFor(selector: string, waitSelector?: string, timeout: number = 5000, refEl?: HTMLElement | Document | ShadowRoot | null): Promise { + return new Promise((resolve) => { + let disposeTimeout = 0; + let disposeInterval = 0; + + disposeTimeout = setTimeout(() => { + clearInterval(disposeInterval); + resolve(null); + }, timeout) as any; + + disposeInterval = setInterval(() => { + let el = (refEl ?? document).querySelector(waitSelector ?? selector); + if (!el) return; + + if (waitSelector) el = (refEl ?? document).querySelector(selector); + if (!el) return; + + resolve(el); + clearTimeout(disposeTimeout); + clearInterval(disposeInterval); + }, 10) as any; + }); +} + +export function observe(selector: string, cb: (el: T) => any): void { + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const node of Array.from(mutation.addedNodes)) { + if (!(node instanceof Element)) continue; + if (!node.matches(selector)) continue; + + cb(node as T); + return; + } + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); +} diff --git a/src/common/router.ts b/src/common/router.ts new file mode 100644 index 0000000..ee90c07 --- /dev/null +++ b/src/common/router.ts @@ -0,0 +1,58 @@ +import { match, MatchFunction } from 'path-to-regexp'; +import { Callback } from './types'; + +interface Route { + pattern: string; + matcher: MatchFunction; + onCallbacks: Callback[]; + offCallbacks: Callback[]; +} + +const registeredRoutes: Route[] = []; + +const pushState = history.pushState; + +history.pushState = function(data: any, unused: string, url?: string | URL | null): void { + pushState.apply(history, [ data, unused, url ]); + if (url) checkRoutes(url.toString(), registeredRoutes); +} + +addEventListener('popstate', () => { + checkRoutes(location.pathname, registeredRoutes); +}); + +function checkRoutes(url: string, routes: Route[]): void { + for (const route of routes) { + const cbs = route.matcher(url) ? route.onCallbacks : route.offCallbacks; + for (const cb of cbs) cb(); + } +} + +function ensureRoute(pattern: string): Route { + let route = registeredRoutes.find(route => route.pattern === pattern); + if (route) return route; + + route = { + pattern, + matcher: match(pattern), + onCallbacks: [], + offCallbacks: [] + }; + + registeredRoutes.push(route); + return route; +} + +export function onRoute(pattern: string, cb: Callback): void { + const route = ensureRoute(pattern); + route.onCallbacks.push(cb); + + if (route.matcher(location.pathname)) cb(); +} + +export function offRoute(pattern: string, cb: Callback): void { + const route = ensureRoute(pattern); + route.offCallbacks.push(cb); + + if (!route.matcher(location.pathname)) cb(); +} diff --git a/src/common/types.ts b/src/common/types.ts new file mode 100644 index 0000000..7e2de0c --- /dev/null +++ b/src/common/types.ts @@ -0,0 +1 @@ +export type Callback = () => any; diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..3af61a0 --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,17 @@ +/// + +interface Branding { + + readonly __branding: T; + +} + +type Brand = T & Branding; + +interface Headers extends Iterable {} + +interface Body { + + json(): Promise; + +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4b4bf00 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "noEmit": true, + "lib": [ + "esnext", + "dom" + ] + }, + "include": [ + "src/" + ] +} diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 0000000..3178403 --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "outDir": "out/" + }, + "include": [ + "scripts/" + ] +}