Compare commits

..

2 Commits

6 changed files with 539 additions and 14 deletions

View File

@ -67,7 +67,7 @@
"@electron-toolkit/tsconfig": "1.0.1",
"@electron/remote": "2.1.3",
"@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.13.1",
"@ffmpeg.wasm/main": "0.12.0",
"@floating-ui/dom": "1.7.4",
"@foobar404/wave": "2.0.5",
"@ghostery/adblocker-electron": "2.11.6",
@ -90,6 +90,7 @@
"butterchurn-presets": "3.0.0-beta.4",
"color": "5.0.0",
"conf": "14.0.0",
"crypto-js": "^4.2.0",
"custom-electron-prompt": "1.5.8",
"deepmerge-ts": "7.1.5",
"delay": "6.0.0",
@ -144,6 +145,7 @@
"@playwright/test": "1.55.0",
"@stylistic/eslint-plugin": "5.3.1",
"@total-typescript/ts-reset": "0.6.1",
"@types/crypto-js": "^4.2.2",
"@types/electron-localshortcut": "3.1.3",
"@types/howler": "2.2.12",
"@types/html-to-text": "9.0.4",

203
pnpm-lock.yaml generated
View File

@ -49,8 +49,8 @@ importers:
specifier: 0.12.0
version: 0.12.0
'@ffmpeg.wasm/main':
specifier: 0.13.1
version: 0.13.1
specifier: 0.12.0
version: 0.12.0
'@floating-ui/dom':
specifier: 1.7.4
version: 1.7.4
@ -117,6 +117,9 @@ importers:
conf:
specifier: 14.0.0
version: 14.0.0
crypto-js:
specifier: ^4.2.0
version: 4.2.0
custom-electron-prompt:
specifier: 1.5.8
version: 1.5.8(electron@38.2.0)
@ -271,6 +274,9 @@ importers:
'@total-typescript/ts-reset':
specifier: 0.6.1
version: 0.6.1
'@types/crypto-js':
specifier: ^4.2.2
version: 4.2.2
'@types/electron-localshortcut':
specifier: 3.1.3
version: 3.1.3
@ -775,9 +781,9 @@ packages:
'@ffmpeg.wasm/core-mt@0.12.0':
resolution: {integrity: sha512-M9pjL7JQX4AYl3WI8vGcPGPTz/O7JmhW8ac/fHA3oXTxoRAPwYSY/OsY1N9C0XahIM0+fxa1QSLN9Ekz8sBM/Q==}
'@ffmpeg.wasm/main@0.13.1':
resolution: {integrity: sha512-WoEd9xp/N9VWddZ3y1cdRK/el52ZKLoqOS+BNQZcsbLQpkQuHrFG93+zY4VjMZ0aWno4pQG4TSgLzsexpukpHw==}
engines: {node: '>=14.0.0'}
'@ffmpeg.wasm/main@0.12.0':
resolution: {integrity: sha512-LILAKTrU3Rga2iXLsF9jeFxe2hNQFjWlrKuXPWSdCFeQ7Kg69fO4WwjNJ0CzjOyO6qtndRQMNKqf//N4fLYUBA==}
engines: {node: '>=12.16.1'}
'@floating-ui/core@1.7.3':
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
@ -1298,6 +1304,9 @@ packages:
'@types/cacheable-request@6.0.3':
resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
'@types/crypto-js@4.2.2':
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@ -1379,16 +1388,32 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/project-service@8.42.0':
resolution: {integrity: sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/project-service@8.43.0':
resolution: {integrity: sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/scope-manager@8.42.0':
resolution: {integrity: sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/scope-manager@8.43.0':
resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.42.0':
resolution: {integrity: sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/tsconfig-utils@8.43.0':
resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1402,16 +1427,33 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/types@8.42.0':
resolution: {integrity: sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/types@8.43.0':
resolution: {integrity: sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.42.0':
resolution: {integrity: sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/typescript-estree@8.43.0':
resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/utils@8.42.0':
resolution: {integrity: sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/utils@8.43.0':
resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1419,6 +1461,10 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/visitor-keys@8.42.0':
resolution: {integrity: sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/visitor-keys@8.43.0':
resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -2017,6 +2063,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
css-select@5.2.2:
resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
@ -2032,6 +2081,10 @@ packages:
peerDependencies:
electron: '>=10.0.0'
data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
engines: {node: '>= 0.4'}
@ -2527,6 +2580,9 @@ packages:
exif-parser@0.1.12:
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
exponential-backoff@3.1.2:
resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==}
exponential-backoff@3.1.3:
resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
@ -2584,6 +2640,10 @@ packages:
picomatch:
optional: true
fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
@ -2633,6 +2693,10 @@ packages:
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
engines: {node: '>= 6'}
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
from@0.1.7:
resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
@ -3101,6 +3165,9 @@ packages:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
is-url@1.2.4:
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
@ -3600,6 +3667,15 @@ packages:
node-api-version@0.2.1:
resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-gyp-build@4.8.4:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
@ -3958,6 +4034,9 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
@ -4418,6 +4497,10 @@ packages:
tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
tinyglobby@0.2.14:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
@ -4677,6 +4760,10 @@ packages:
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
webrtc-adapter@9.0.3:
resolution: {integrity: sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==}
engines: {node: '>=6.0.0', npm: '>=3.10.0'}
@ -5240,7 +5327,11 @@ snapshots:
'@ffmpeg.wasm/core-mt@0.12.0': {}
'@ffmpeg.wasm/main@0.13.1': {}
'@ffmpeg.wasm/main@0.12.0':
dependencies:
is-url: 1.2.4
node-fetch: 3.3.2
regenerator-runtime: 0.13.11
'@floating-ui/core@1.7.3':
dependencies:
@ -5783,7 +5874,7 @@ snapshots:
'@stylistic/eslint-plugin@5.3.1(eslint@9.35.0)':
dependencies:
'@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0)
'@typescript-eslint/types': 8.43.0
'@typescript-eslint/types': 8.42.0
eslint: 9.35.0
eslint-visitor-keys: 4.2.1
espree: 10.4.0
@ -5804,7 +5895,7 @@ snapshots:
dependencies:
minimatch: 10.0.3
path-browserify: 1.0.1
tinyglobby: 0.2.15
tinyglobby: 0.2.14
'@tybys/wasm-util@0.10.0':
dependencies:
@ -5839,6 +5930,8 @@ snapshots:
'@types/node': 24.3.0
'@types/responselike': 1.0.3
'@types/crypto-js@4.2.2': {}
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@ -5938,6 +6031,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.42.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2)
'@typescript-eslint/types': 8.42.0
debug: 4.4.1
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.43.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2)
@ -5947,11 +6049,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.42.0':
dependencies:
'@typescript-eslint/types': 8.42.0
'@typescript-eslint/visitor-keys': 8.42.0
'@typescript-eslint/scope-manager@8.43.0':
dependencies:
'@typescript-eslint/types': 8.43.0
'@typescript-eslint/visitor-keys': 8.43.0
'@typescript-eslint/tsconfig-utils@8.42.0(typescript@5.9.2)':
dependencies:
typescript: 5.9.2
'@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.9.2)':
dependencies:
typescript: 5.9.2
@ -5968,8 +6079,26 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.42.0': {}
'@typescript-eslint/types@8.43.0': {}
'@typescript-eslint/typescript-estree@8.42.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/project-service': 8.42.0(typescript@5.9.2)
'@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2)
'@typescript-eslint/types': 8.42.0
'@typescript-eslint/visitor-keys': 8.42.0
debug: 4.4.1
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.7.2
ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/project-service': 8.43.0(typescript@5.9.2)
@ -5986,6 +6115,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.42.0(eslint@9.35.0)(typescript@5.9.2)':
dependencies:
'@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0)
'@typescript-eslint/scope-manager': 8.42.0
'@typescript-eslint/types': 8.42.0
'@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2)
eslint: 9.35.0
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.43.0(eslint@9.35.0)(typescript@5.9.2)':
dependencies:
'@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0)
@ -5997,6 +6137,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.42.0':
dependencies:
'@typescript-eslint/types': 8.42.0
eslint-visitor-keys: 4.2.1
'@typescript-eslint/visitor-keys@8.43.0':
dependencies:
'@typescript-eslint/types': 8.43.0
@ -6664,6 +6809,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crypto-js@4.2.0: {}
css-select@5.2.2:
dependencies:
boolbase: 1.0.0
@ -6680,6 +6827,8 @@ snapshots:
dependencies:
electron: 38.2.0
data-uri-to-buffer@4.0.1: {}
data-view-buffer@1.0.2:
dependencies:
call-bound: 1.0.4
@ -7208,7 +7357,7 @@ snapshots:
get-tsconfig: 4.10.1
is-bun-module: 2.0.0
stable-hash-x: 0.2.0
tinyglobby: 0.2.15
tinyglobby: 0.2.14
unrs-resolver: 1.11.1
optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.9.2))(eslint-import-resolver-typescript@4.4.4)(eslint@9.35.0)
@ -7266,7 +7415,7 @@ snapshots:
eslint-plugin-solid@0.14.5(eslint@9.35.0)(typescript@5.9.2):
dependencies:
'@typescript-eslint/utils': 8.43.0(eslint@9.35.0)(typescript@5.9.2)
'@typescript-eslint/utils': 8.42.0(eslint@9.35.0)(typescript@5.9.2)
eslint: 9.35.0
estraverse: 5.3.0
is-html: 2.0.0
@ -7374,6 +7523,8 @@ snapshots:
exif-parser@0.1.12: {}
exponential-backoff@3.1.2: {}
exponential-backoff@3.1.3: {}
extract-zip@2.0.1:
@ -7425,6 +7576,11 @@ snapshots:
optionalDependencies:
picomatch: 4.0.3
fetch-blob@3.2.0:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
fflate@0.8.2: {}
file-entry-cache@8.0.0:
@ -7480,6 +7636,10 @@ snapshots:
hasown: 2.0.2
mime-types: 2.1.35
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
from@0.1.7: {}
fs-extra@10.1.0:
@ -7981,6 +8141,8 @@ snapshots:
is-unicode-supported@0.1.0: {}
is-url@1.2.4: {}
is-weakmap@2.0.2: {}
is-weakref@1.1.1:
@ -8465,19 +8627,27 @@ snapshots:
dependencies:
semver: 7.7.2
node-domexception@1.0.0: {}
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
node-gyp-build@4.8.4: {}
node-gyp@11.4.2:
dependencies:
env-paths: 2.2.1
exponential-backoff: 3.1.3
exponential-backoff: 3.1.2
graceful-fs: 4.2.11
make-fetch-happen: 14.0.3
nopt: 8.1.0
proc-log: 5.0.0
semver: 7.7.2
tar: 7.4.3
tinyglobby: 0.2.15
tinyglobby: 0.2.14
which: 5.0.0
transitivePeerDependencies:
- supports-color
@ -8827,6 +8997,8 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
regenerator-runtime@0.13.11: {}
regexp.prototype.flags@1.5.4:
dependencies:
call-bind: 1.0.8
@ -9336,6 +9508,11 @@ snapshots:
tinycolor2@1.6.0: {}
tinyglobby@0.2.14:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
@ -9613,6 +9790,8 @@ snapshots:
dependencies:
defaults: 1.0.4
web-streams-polyfill@3.3.3: {}
webrtc-adapter@9.0.3:
dependencies:
sdp: 3.2.1

View File

@ -77,10 +77,11 @@ export class LRCLib implements LyricProvider {
}
const filteredResults = [];
const artists = artist.split(/[&,]/g).map((i) => i.trim());
for (const item of data) {
const { artistName } = item;
const artists = artist.split(/[&,]/g).map((i) => i.trim());
const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim());
// Try to match using artist name first

View File

@ -0,0 +1,340 @@
// Code adapted from https://greasyfork.org/en/scripts/548724-youtube-music-spotify-%E7%BD%91%E6%98%93%E4%BA%91%E6%AD%8C%E8%AF%8D%E6%98%BE%E7%A4%BA
// which is licenced under the MIT licence
import CryptoJS from 'crypto-js';
import { jaroWinkler } from '@skyra/jaro-winkler';
import { z } from 'zod';
import { LRC } from '../parsers/lrc';
import type { LyricProvider, LyricResult, SearchSongInfo } from '../types';
const EAPI_AES_KEY = 'e82ckenh8dichen8';
const EAPI_ENCODE_KEY = '3go8&$8*3*3h0k(2)2';
const EAPI_CHECK_TOKEN =
'9ca17ae2e6ffcda170e2e6ee8ad85dba908ca4d74da9ac8ea2d44e938f9eadc66da5a8979af572a5a9b68ac12af0feaec3b92aa69af9b1d372f6b8adccb35e968b9bb6c14f908d0099fb6ff48efdacd361f5b6ee9e';
const EAPI_BASE_HEADERS = {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) NeteaseMusicDesktop/3.0.14.2534',
};
const EAPI_BASE_COOKIES = {
os: 'osx',
appver: '3.0.14',
requestId: 0,
osver: '15.6.1',
};
const artistSchema = z.object({ id: z.number(), name: z.string() });
const songSchema = z.object({
resourceId: z.coerce.number(),
baseInfo: z.object({
simpleSongData: z.object({
name: z.string(),
ar: z.array(artistSchema).optional(),
dt: z.number(),
}),
}),
});
const searchResponseDataSchema = z.object({
resources: z.array(songSchema).default([]),
});
const searchResponseSchema = z.object({
code: z.number(),
message: z.string(),
data: searchResponseDataSchema,
});
type Song = z.infer<typeof songSchema>;
const lyricPartSchema = z.object({ lyric: z.string().nullable() });
const lyricResponseSchema = z.object({
lrc: lyricPartSchema.optional(),
tlyric: lyricPartSchema.optional(),
romalrc: lyricPartSchema.optional(),
});
export class Netease implements LyricProvider {
name = 'Netease';
baseUrl = 'https://interface.music.163.com';
cookies: Record<string, string> = {};
initialized = false;
private encode(id: string): string {
// XOR step (unchanged)
let xoredString = '';
for (let i = 0; i < id.length; i++) {
const charCode =
id.charCodeAt(i) ^
EAPI_ENCODE_KEY.charCodeAt(i % EAPI_ENCODE_KEY.length);
xoredString += String.fromCharCode(charCode);
}
// MD5 -> Base64 using crypto-js
const hash = CryptoJS.MD5(CryptoJS.enc.Latin1.parse(xoredString)).toString(
CryptoJS.enc.Base64,
);
// Build a binary WordArray for "id hash"
const combinedWordArray = CryptoJS.enc.Latin1.parse(id + ' ' + hash);
// Convert to Base64 (replaces Buffer.from(...).toString("base64"))
return CryptoJS.enc.Base64.stringify(combinedWordArray);
}
private async register() {
const deviceId = '7B79802670C7A45DB9091976D71E0AE829E28926C6C34A1B8644';
const username = this.encode(deviceId);
try {
await this.eapi('/register/anonimous', { username }, { _nmclfl: '1' });
this.initialized = true;
} catch (e) {
throw new Error(`Registration failed: ${e}`);
}
}
private async eapi(
path: string,
data: Record<string, unknown> = {},
params: Record<string, string> = {},
) {
const header = { ...EAPI_BASE_COOKIES };
const bodyData = { ...data, header: JSON.stringify(header) };
const body = JSON.stringify(bodyData);
const sign = CryptoJS.MD5(
`nobody/api${path}use${body}md5forencrypt`,
).toString();
const payload = `/api${path}-36cd479b6b5-${body}-36cd479b6b5-${sign}`;
const key = CryptoJS.enc.Utf8.parse(EAPI_AES_KEY);
const encrypted = CryptoJS.AES.encrypt(payload, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).ciphertext.toString(CryptoJS.enc.Hex);
const cookieString = Object.entries({ ...this.cookies })
.map(([k, v]) => `${k}=${v}`)
.join('; ');
const queryStr = new URLSearchParams(params).toString();
const url = `${this.baseUrl}/eapi${path}${queryStr ? `?${queryStr}` : ''}`;
const response = await fetch(url, {
method: 'POST',
headers: {
...EAPI_BASE_HEADERS,
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookieString,
},
body: `params=${encodeURIComponent(encrypted.toUpperCase())}`,
});
const setCookieHeader = response.headers.get('set-cookie');
if (setCookieHeader) {
const cookieStrings = setCookieHeader.split(/,(?=\s*[^=;\s]+=)/);
for (const cookieStr of cookieStrings) {
const parts = cookieStr.split(';')[0].split('=');
if (parts.length === 2) {
this.cookies[parts[0].trim()] = parts[1].trim();
}
}
}
if (!response.ok) {
throw new Error(`bad HTTPStatus(${response.statusText})`);
}
const json = await response.json();
z.object({ code: z.literal(200) }).parse(json);
return json;
}
private async searchSongs(keyword: string, limit = 10): Promise<Song[]> {
const response = await this.eapi(
'/search/song/list/page',
{
offset: '0',
scene: 'NORMAL',
needCorrect: 'true',
checkToken: EAPI_CHECK_TOKEN,
keyword,
limit: limit.toString(),
verifyId: 1,
},
{
_nmclfl: '1',
},
);
const parsed = searchResponseSchema.parse(response);
return parsed.data?.resources || [];
}
private async getLyric(id: number) {
const response = await this.eapi(
'/song/lyric/v1',
{
id,
tv: '-1',
yv: '-1',
rv: '-1',
lv: '-1',
verifyId: 1,
},
{
_nmclfl: '1',
},
);
return lyricResponseSchema.parse(response);
}
private splitTitle(title: string): string[] {
const masterPattern =
/(?:[「『](?<content>.+?)[」』])|(?:【.*?】|〖.*?〗|\(.*?\)|.*?)|(?<delimiter>\s+-\s+|\s*[/|:|│]\s*)/i;
const noiseWords = /\b(MV|PV)\b|\b(?:covered by|feat?|ft?)\b.+/gi;
const parse = (str: string): string[] => {
if (!str?.trim()) return [];
const match = str.match(masterPattern);
if (!match || match.index === undefined) return [str];
const before = str.substring(0, match.index);
const after = str.substring(match.index + match[0].length);
const { delimiter, content } = match.groups || {};
if (delimiter && (before.trim().length < 2 || after.trim().length < 2)) {
const remaining = parse(after);
return [
before + match[0] + (remaining[0] || ''),
...remaining.slice(1),
];
}
return [...parse(before), ...(content ? [content] : []), ...parse(after)];
};
return [
...new Set(
parse(title)
.map((p) => p.replace(noiseWords, '').trim())
.filter((p) => p.length > 0),
),
];
}
async search({
title,
artist,
songDuration,
}: SearchSongInfo): Promise<LyricResult | null> {
if (!this.initialized) {
await this.register();
}
const parts = this.splitTitle(title);
if (parts.length === 0) {
parts.push(title);
}
const keywords = [...parts];
if (parts[0] !== artist) keywords.push(`${parts[0]} ${artist}`);
const results = await Promise.all(
keywords.map((kw) => this.searchSongs(kw, 10)),
);
const calcTitleScore = (searchTitle: string) => {
let avgScore = 0;
parts.forEach((part, idx) => {
let weight = 1 / (idx * 2 + 1); // Earlier parts have higher weight
if (searchTitle.startsWith(part)) weight *= 2;
// Bonus for prefix match
else if (searchTitle.includes(part)) weight *= 1.5; // Bonus for substring match
avgScore += (jaroWinkler(part, searchTitle) * weight) / parts.length;
});
const score = Math.max(jaroWinkler(title, searchTitle), avgScore);
return score;
};
const artists = artist.split(/[&,]/g).map((i) => i.trim());
const filteredResults = [];
for (const result of results.flat()) {
const {
baseInfo: {
simpleSongData: { name, ar: itemArtists },
},
} = result;
const permutations = [];
for (const artistA of artists) {
for (const artistB of itemArtists ?? []) {
permutations.push([
artistA.toLowerCase(),
artistB.name.toLowerCase(),
]);
}
}
for (const artistA of itemArtists ?? []) {
for (const artistB of artists) {
permutations.push([
artistA.name.toLowerCase(),
artistB.toLowerCase(),
]);
}
}
const ratio =
calcTitleScore(name) +
Math.max(...permutations.map(([x, y]) => jaroWinkler(x, y)));
if (ratio < 1.8) continue;
filteredResults.push(result);
}
const closestResult = filteredResults[0];
if (!closestResult) {
return null;
}
if (
Math.abs(closestResult.baseInfo.simpleSongData.dt / 1000 - songDuration) >
15
) {
return null;
}
const lyric = await this.getLyric(closestResult.resourceId);
if (!lyric || !lyric.lrc?.lyric) return null;
const lyrics = stripMetadata(lyric.lrc.lyric);
const lines = LRC.parse(lyrics).lines.map((l) => ({
...l,
status: 'upcoming' as const,
}));
if (lines.length === 0 && !lyrics.trim()) return null;
return {
title: closestResult.baseInfo.simpleSongData.name,
artists:
closestResult.baseInfo.simpleSongData.ar?.map((a) => a.name) ?? [],
lines,
lyrics: lyrics,
};
}
}
const stripMetadata = (lyrics: string) => {
return lyrics
.split('\n')
.filter((line) => {
if (!line.includes('{')) return true;
try {
JSON.parse(line);
return false;
} catch {}
return true;
})
.join('\n');
};

View File

@ -7,6 +7,7 @@ export enum ProviderNames {
LRCLib = 'LRCLib',
MusixMatch = 'MusixMatch',
LyricsGenius = 'LyricsGenius',
NetEase = 'NetEase',
// Megalobiz = 'Megalobiz',
}

View File

@ -3,11 +3,13 @@ import { YTMusic } from './YTMusic';
import { LRCLib } from './LRCLib';
import { MusixMatch } from './MusixMatch';
import { LyricsGenius } from './LyricsGenius';
import { Netease } from './NetEase';
export const providers = {
[ProviderNames.YTMusic]: new YTMusic(),
[ProviderNames.LRCLib]: new LRCLib(),
[ProviderNames.MusixMatch]: new MusixMatch(),
[ProviderNames.LyricsGenius]: new LyricsGenius(),
[ProviderNames.NetEase]: new Netease(),
// [ProviderNames.Megalobiz]: new Megalobiz(), // Disabled because it is too unstable and slow
} as const;