Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21619e72dc | ||
|
|
33742bca6b | ||
|
|
e0575fc179 | ||
|
|
97fe5fa496 | ||
|
|
97533cd4aa | ||
|
|
a7672168c6 | ||
|
|
5c46ca648d | ||
|
|
38c7bc0d0d | ||
|
|
cb2798cba9 | ||
|
|
f0342b852b | ||
|
|
97346617a5 | ||
|
|
ecdc018fa1 | ||
|
|
cbcbe3e8c8 | ||
|
|
b12c5ed12b | ||
|
|
96921b255f | ||
|
|
2df1077ee4 | ||
|
|
4937ddb09c | ||
|
|
4b00a06a88 | ||
|
|
be48e78a6f | ||
|
|
b7a3663078 | ||
|
|
9c364b482e | ||
|
|
355c5e584b | ||
|
|
f956fd93d5 | ||
|
|
69e285d3ca | ||
|
|
728fed2ed3 | ||
|
|
bc0d2264a3 | ||
|
|
ba8a6bf76c | ||
|
|
2cf84a796d | ||
|
|
ba62ae27af | ||
|
|
e35895d752 | ||
|
|
cc192cab44 | ||
|
|
e36792b76a | ||
|
|
61231ab99e | ||
|
|
ae2ee0e61e | ||
|
|
a339f0fd0c | ||
|
|
b9c3c3506e | ||
|
|
44d96264fb | ||
|
|
19eab0418a | ||
|
|
0695bbaa70 | ||
|
|
8d6b207833 | ||
|
|
3371a42822 | ||
|
|
fe712318b8 | ||
|
|
593d8327b2 | ||
|
|
d5c54b770f | ||
|
|
32a9a21be1 | ||
|
|
e0735f8928 | ||
|
|
c6dc962134 | ||
|
|
e068103e4d | ||
|
|
6eb5ade9ea | ||
|
|
c40168cacd |
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
"@babel/preset-typescript",
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
"corejs": "3",
|
|
||||||
"useBuiltIns": "usage"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
// [
|
|
||||||
// "minify",
|
|
||||||
// {
|
|
||||||
// "builtIns": false,
|
|
||||||
// "evaluate": false,
|
|
||||||
// "mangle": false
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
|
||||||
"@babel/plugin-transform-modules-umd",
|
|
||||||
"@babel/plugin-transform-runtime",
|
|
||||||
"@babel/plugin-transform-class-properties"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -55,7 +55,6 @@ const vueRule = {
|
|||||||
exports.base = {
|
exports.base = {
|
||||||
extends: ['standard'],
|
extends: ['standard'],
|
||||||
rules: baseRule,
|
rules: baseRule,
|
||||||
parser: '@babel/eslint-parser',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.html = {
|
exports.html = {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ runs:
|
|||||||
# https://docs.npmjs.com/cli/v10/configuring-npm/folders#cache
|
# https://docs.npmjs.com/cli/v10/configuring-npm/folders#cache
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
id: cache-npm
|
id: cache-npm
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ${{ env.NPM_CACHE }}
|
path: ${{ env.NPM_CACHE }}
|
||||||
key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }}
|
key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Check out git repository
|
# - name: Check out git repository
|
||||||
# uses: actions/checkout@v4
|
# uses: actions/checkout@v6
|
||||||
|
|
||||||
# - name: Install Node.js
|
# - name: Install Node.js
|
||||||
# uses: actions/setup-node@v4
|
# uses: actions/setup-node@v4
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
# needs: CheckCode
|
# needs: CheckCode
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get npm cache directory
|
- name: Get npm cache directory
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
- name: Build Package Setup x64
|
- name: Build Package Setup x64
|
||||||
run: npm run pack:win:setup:x64
|
run: npm run pack:win:setup:x64
|
||||||
- name: Upload Artifact Setup x64
|
- name: Upload Artifact Setup x64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-x64-Setup
|
name: lx-music-desktop-x64-Setup
|
||||||
path: build/*-x64-Setup.exe
|
path: build/*-x64-Setup.exe
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
- name: Build Package 7z x64
|
- name: Build Package 7z x64
|
||||||
run: npm run pack:win:7z:x64
|
run: npm run pack:win:7z:x64
|
||||||
- name: Upload Artifact 7z x64
|
- name: Upload Artifact 7z x64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-win_x64-green
|
name: lx-music-desktop-win_x64-green
|
||||||
path: build/*win_x64-green.7z
|
path: build/*win_x64-green.7z
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
- name: Build Package Setup arm64
|
- name: Build Package Setup arm64
|
||||||
run: npm run pack:win:setup:arm64
|
run: npm run pack:win:setup:arm64
|
||||||
- name: Upload Artifact Setup arm64
|
- name: Upload Artifact Setup arm64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-arm64-Setup
|
name: lx-music-desktop-arm64-Setup
|
||||||
path: build/*-arm64-Setup.exe
|
path: build/*-arm64-Setup.exe
|
||||||
@@ -89,22 +89,56 @@ jobs:
|
|||||||
- name: Build Package 7z arm64
|
- name: Build Package 7z arm64
|
||||||
run: npm run pack:win:7z:arm64
|
run: npm run pack:win:7z:arm64
|
||||||
- name: Upload Artifact 7z arm64
|
- name: Upload Artifact 7z arm64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-win_arm64-green
|
name: lx-music-desktop-win_arm64-green
|
||||||
path: build/*win_arm64-green.7z
|
path: build/*win_arm64-green.7z
|
||||||
|
|
||||||
|
- name: Generate file MD5
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
|
||||||
|
|
||||||
- name: Install old electron
|
Windows_7:
|
||||||
run: npm install electron@22
|
name: Windows_7
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
BUILD_WIN7: true
|
||||||
|
# needs: CheckCode
|
||||||
|
steps:
|
||||||
|
- name: Check out git repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install python setuptools
|
- name: Get npm cache directory
|
||||||
run: pip.exe install setuptools
|
shell: pwsh
|
||||||
|
run: echo "NPM_CACHE=$(npm config get cache)" >> $env:GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Setup Node Env
|
||||||
|
env:
|
||||||
|
NPM_CACHE: ${{ env.NPM_CACHE }}
|
||||||
|
uses: ./.github/actions/setup
|
||||||
|
|
||||||
|
- name: Prepare win7 undici env
|
||||||
|
run: |
|
||||||
|
npm install undici@5
|
||||||
|
Set-Content -Path .\src\common\utils\request.ts -Value "export * from './request_node16'"
|
||||||
|
|
||||||
|
- name: Build src code
|
||||||
|
env:
|
||||||
|
BUILD_WIN7: true
|
||||||
|
run: |
|
||||||
|
git status --porcelain
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
- name: Prepare win7 electron env
|
||||||
|
run: |
|
||||||
|
npm install electron@22
|
||||||
|
pip.exe install setuptools
|
||||||
|
|
||||||
- name: Build Package win7 Setup x64
|
- name: Build Package win7 Setup x64
|
||||||
run: npm run pack:win7:setup:x64
|
run: npm run pack:win7:setup:x64
|
||||||
- name: Upload Artifact win7 Setup x64
|
- name: Upload Artifact win7 Setup x64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-win7_x64-Setup
|
name: lx-music-desktop-win7_x64-Setup
|
||||||
path: build/*win7_x64-Setup.exe
|
path: build/*win7_x64-Setup.exe
|
||||||
@@ -112,7 +146,7 @@ jobs:
|
|||||||
- name: Build Package win7 7z x64
|
- name: Build Package win7 7z x64
|
||||||
run: npm run pack:win7:7z:x64
|
run: npm run pack:win7:7z:x64
|
||||||
- name: Upload Artifact win7 7z x64
|
- name: Upload Artifact win7 7z x64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-win7_x64-green
|
name: lx-music-desktop-win7_x64-green
|
||||||
path: build/*win7_x64-green.7z
|
path: build/*win7_x64-green.7z
|
||||||
@@ -120,7 +154,7 @@ jobs:
|
|||||||
- name: Build Package win7 7z x86
|
- name: Build Package win7 7z x86
|
||||||
run: npm run pack:win7:7z:x86
|
run: npm run pack:win7:7z:x86
|
||||||
- name: Upload Artifact win7 7z x86
|
- name: Upload Artifact win7 7z x86
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-win7_x86-green
|
name: lx-music-desktop-win7_x86-green
|
||||||
path: build/*win7_x86-green.7z
|
path: build/*win7_x86-green.7z
|
||||||
@@ -136,7 +170,7 @@ jobs:
|
|||||||
# needs: CheckCode
|
# needs: CheckCode
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install python setuptools
|
- name: Install python setuptools
|
||||||
run: brew install python-setuptools
|
run: brew install python-setuptools
|
||||||
@@ -161,14 +195,14 @@ jobs:
|
|||||||
npm run pack:mac:dmg:arm64
|
npm run pack:mac:dmg:arm64
|
||||||
|
|
||||||
- name: Upload Artifact dmg
|
- name: Upload Artifact dmg
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-mac-dmg
|
name: lx-music-desktop-mac-dmg
|
||||||
path: |
|
path: |
|
||||||
build/*.dmg
|
build/*.dmg
|
||||||
!build/*-arm64.dmg
|
!build/*-arm64.dmg
|
||||||
- name: Upload Artifact dmg
|
- name: Upload Artifact dmg
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-mac-dmg-arm64
|
name: lx-music-desktop-mac-dmg-arm64
|
||||||
path: build/*-arm64.dmg
|
path: build/*-arm64.dmg
|
||||||
@@ -187,7 +221,7 @@ jobs:
|
|||||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||||
|
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get npm cache directory
|
- name: Get npm cache directory
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -206,7 +240,7 @@ jobs:
|
|||||||
- name: Build Package deb amd64
|
- name: Build Package deb amd64
|
||||||
run: npm run pack:linux:deb:amd64
|
run: npm run pack:linux:deb:amd64
|
||||||
- name: Upload Artifact deb amd64
|
- name: Upload Artifact deb amd64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-deb-amd64
|
name: lx-music-desktop-deb-amd64
|
||||||
path: build/*_amd64.deb
|
path: build/*_amd64.deb
|
||||||
@@ -214,7 +248,7 @@ jobs:
|
|||||||
- name: Build Package deb arm64
|
- name: Build Package deb arm64
|
||||||
run: npm run pack:linux:deb:arm64
|
run: npm run pack:linux:deb:arm64
|
||||||
- name: Upload Artifact deb arm64
|
- name: Upload Artifact deb arm64
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-deb-arm64
|
name: lx-music-desktop-deb-arm64
|
||||||
path: build/*_arm64.deb
|
path: build/*_arm64.deb
|
||||||
@@ -222,7 +256,7 @@ jobs:
|
|||||||
- name: Build Package deb armv7l
|
- name: Build Package deb armv7l
|
||||||
run: npm run pack:linux:deb:armv7l
|
run: npm run pack:linux:deb:armv7l
|
||||||
- name: Upload Artifact deb armv7l
|
- name: Upload Artifact deb armv7l
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-deb-armv7l
|
name: lx-music-desktop-deb-armv7l
|
||||||
path: build/*_armv7l.deb
|
path: build/*_armv7l.deb
|
||||||
@@ -230,7 +264,7 @@ jobs:
|
|||||||
- name: Build Package x64 appImage
|
- name: Build Package x64 appImage
|
||||||
run: npm run pack:linux:appImage
|
run: npm run pack:linux:appImage
|
||||||
- name: Upload Artifact x64 appImage
|
- name: Upload Artifact x64 appImage
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-x64-appImage
|
name: lx-music-desktop-x64-appImage
|
||||||
path: build/*_x64.AppImage
|
path: build/*_x64.AppImage
|
||||||
@@ -238,7 +272,7 @@ jobs:
|
|||||||
- name: Build Package x64 rpm
|
- name: Build Package x64 rpm
|
||||||
run: npm run pack:linux:rpm
|
run: npm run pack:linux:rpm
|
||||||
- name: Upload Artifact x64 rpm
|
- name: Upload Artifact x64 rpm
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-x64-rpm
|
name: lx-music-desktop-x64-rpm
|
||||||
path: build/*.x64.rpm
|
path: build/*.x64.rpm
|
||||||
@@ -246,7 +280,7 @@ jobs:
|
|||||||
- name: Build Package x64 pacman
|
- name: Build Package x64 pacman
|
||||||
run: npm run pack:linux:pacman
|
run: npm run pack:linux:pacman
|
||||||
- name: Upload Artifact x64 pacman
|
- name: Upload Artifact x64 pacman
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: lx-music-desktop-x64-pacman
|
name: lx-music-desktop-x64-pacman
|
||||||
path: build/*_x64.pacman
|
path: build/*_x64.pacman
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Repository Dispatch
|
- name: Repository Dispatch
|
||||||
uses: peter-evans/repository-dispatch@v2
|
uses: peter-evans/repository-dispatch@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
repository: lyswhut/lx-music-desktop-version-info
|
repository: lyswhut/lx-music-desktop-version-info
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Check out git repository
|
# - name: Check out git repository
|
||||||
# uses: actions/checkout@v4
|
# uses: actions/checkout@v6
|
||||||
|
|
||||||
# - name: Install Node.js
|
# - name: Install Node.js
|
||||||
# uses: actions/setup-node@v4
|
# uses: actions/setup-node@v4
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
# needs: CheckCode
|
# needs: CheckCode
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get npm cache directory
|
- name: Get npm cache directory
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -87,7 +87,7 @@ jobs:
|
|||||||
# needs: CheckCode
|
# needs: CheckCode
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get npm cache directory
|
- name: Get npm cache directory
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -98,7 +98,14 @@ jobs:
|
|||||||
NPM_CACHE: ${{ env.NPM_CACHE }}
|
NPM_CACHE: ${{ env.NPM_CACHE }}
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
|
|
||||||
|
- name: Prepare win7 undici env
|
||||||
|
run: |
|
||||||
|
npm install undici@5
|
||||||
|
Set-Content -Path .\src\common\utils\request.ts -Value "export * from './request_node16'"
|
||||||
|
|
||||||
- name: Build src code
|
- name: Build src code
|
||||||
|
env:
|
||||||
|
BUILD_WIN7: true
|
||||||
run: |
|
run: |
|
||||||
git status --porcelain
|
git status --porcelain
|
||||||
npm run build
|
npm run build
|
||||||
@@ -128,7 +135,7 @@ jobs:
|
|||||||
# needs: CheckCode
|
# needs: CheckCode
|
||||||
steps:
|
steps:
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install python3 setuptools
|
- name: Install python3 setuptools
|
||||||
run: brew install python-setuptools
|
run: brew install python-setuptools
|
||||||
@@ -171,7 +178,7 @@ jobs:
|
|||||||
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
|
||||||
|
|
||||||
- name: Check out git repository
|
- name: Check out git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Get npm cache directory
|
- name: Get npm cache directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ module.exports = {
|
|||||||
'@types/node',
|
'@types/node',
|
||||||
'electron-debug',
|
'electron-debug',
|
||||||
'eslint-webpack-plugin',
|
'eslint-webpack-plugin',
|
||||||
|
'typescript',
|
||||||
|
'undici',
|
||||||
|
|
||||||
'eslint-plugin-vue',
|
'eslint-plugin-vue',
|
||||||
'vue-eslint-parser',
|
'vue-eslint-parser',
|
||||||
@@ -35,11 +37,13 @@ module.exports = {
|
|||||||
|
|
||||||
// target: 'minor',
|
// target: 'minor',
|
||||||
// filter: [
|
// filter: [
|
||||||
// // 'electron',
|
// 'electron',
|
||||||
// 'eslint',
|
// 'eslint',
|
||||||
// 'eslint-webpack-plugin',
|
// 'eslint-webpack-plugin',
|
||||||
// 'electron-debug',
|
// 'electron-debug',
|
||||||
|
// 'typescript',
|
||||||
// '@types/node',
|
// '@types/node',
|
||||||
|
// 'undici',
|
||||||
|
|
||||||
// 'eslint-plugin-vue',
|
// 'eslint-plugin-vue',
|
||||||
// 'vue-eslint-parser',
|
// 'vue-eslint-parser',
|
||||||
|
|||||||
@@ -6,6 +6,40 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
|
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
|
||||||
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||||
|
|
||||||
|
## [2.12.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.1...v2.12.2) - 2026-05-01
|
||||||
|
|
||||||
|
我们很高兴地宣布新项目 Any Listen 的桌面版已发布,目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能,更多功能仍在积极开发中,桌面版与Web版将同步更新。
|
||||||
|
对于有播放本地音乐或播放服务器上音乐需求的人可以试试,若遇到任何问题可以发 issue 反馈。
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
|
||||||
|
- 优化歌单内歌曲搜索结果排序 (#2734)
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- 修复桌面歌词的 鼠标移入歌词区域时提高透明度 设置不稳定的问题(#2679, @Little100)
|
||||||
|
- 修复某些情况下可能播放没有声音的问题(#2693)
|
||||||
|
- 修复 tx 搜索结果显示异常的问题(#2753)
|
||||||
|
- 修复音乐名称和歌手信息格式化问题(#2733)
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
|
||||||
|
- 更新 Electron 到 40.8.3
|
||||||
|
|
||||||
|
## [2.12.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.12.0...v2.12.1) - 2026-02-16
|
||||||
|
|
||||||
|
我们很高兴地宣布新项目 Any Listen 的桌面版已发布,目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能,更多功能仍在积极开发中,桌面版与Web版将同步更新。
|
||||||
|
对于有播放本地音乐或播放服务器上音乐需求的人可以试试,若遇到任何问题可以发 issue 反馈。
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
|
||||||
|
- 优化托盘图标行为:在非 Windows 系统中,点击托盘图标时不再显示主窗口
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- 修复音量条在调整音量时实际音量与显示的数值不一致的问题(#2606)
|
||||||
|
- 修复某些情况下搜索框的搜索按钮布局错位的问题(#2622)
|
||||||
|
|
||||||
## [2.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.11.0...v2.12.0) - 2025-11-29
|
## [2.12.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.11.0...v2.12.0) - 2025-11-29
|
||||||
|
|
||||||
我们很高兴地宣布新项目 Any Listen 的桌面版已发布,目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能,更多功能仍在积极开发中,桌面版与Web版将同步更新。
|
我们很高兴地宣布新项目 Any Listen 的桌面版已发布,目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能,更多功能仍在积极开发中,桌面版与Web版将同步更新。
|
||||||
|
|||||||
@@ -10,23 +10,6 @@ const better_sqlite3_fileNameMap = {
|
|||||||
[Arch.armv7l]: 'linux-arm',
|
[Arch.armv7l]: 'linux-arm',
|
||||||
}
|
}
|
||||||
|
|
||||||
const qrc_decode_fileNameMap = {
|
|
||||||
win32: {
|
|
||||||
[Arch.x64]: 'win32-x64',
|
|
||||||
[Arch.ia32]: 'win32-ia32',
|
|
||||||
[Arch.arm64]: 'win32-arm64',
|
|
||||||
},
|
|
||||||
linux: {
|
|
||||||
[Arch.x64]: 'linux-x64',
|
|
||||||
[Arch.arm64]: 'linux-arm64',
|
|
||||||
[Arch.armv7l]: 'linux-arm',
|
|
||||||
},
|
|
||||||
darwin: {
|
|
||||||
[Arch.x64]: 'darwin-x64',
|
|
||||||
[Arch.arm64]: 'darwin-arm64',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const replaceSqliteLib = async(electronNodeAbi, arch) => {
|
const replaceSqliteLib = async(electronNodeAbi, arch) => {
|
||||||
// console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
|
// console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
|
||||||
// if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
|
// if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
|
||||||
@@ -40,22 +23,11 @@ const replaceSqliteLib = async(electronNodeAbi, arch) => {
|
|||||||
await fsPromises.copyFile(filePath, targetPath)
|
await fsPromises.copyFile(filePath, targetPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceQrcDecodeLib = async(electronNodeAbi, platform, arch) => {
|
|
||||||
console.log('replace qrc_decode lib...', platform, electronNodeAbi, qrc_decode_fileNameMap[platform][arch])
|
|
||||||
const filePath = path.join(__dirname, `./lib/qrc_decode_electron-v${electronNodeAbi}-${qrc_decode_fileNameMap[platform][arch]}.node`)
|
|
||||||
const targetPath = path.join(__dirname, '../build/Release/qrc_decode.node')
|
|
||||||
const targetDir = path.dirname(targetPath)
|
|
||||||
if (fs.existsSync(targetDir)) await fsPromises.unlink(targetPath).catch(_ => _)
|
|
||||||
else await fsPromises.mkdir(targetDir, { recursive: true })
|
|
||||||
await fsPromises.copyFile(filePath, targetPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = async(context) => {
|
module.exports = async(context) => {
|
||||||
const { electronPlatformName, arch } = context
|
const { electronPlatformName, arch } = context
|
||||||
const electronVersion = context.packager?.info?._framework?.version ?? require('../package.json').devDependencies.electron.replace(/^[^\d]*?(\d+)/, '$1')
|
const electronVersion = context.packager?.info?._framework?.version ?? require('../package.json').devDependencies.electron.replace(/^[^\d]*?(\d+)/, '$1')
|
||||||
const electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron')
|
const electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron')
|
||||||
await replaceQrcDecodeLib(electronNodeAbi, electronPlatformName, arch)
|
|
||||||
if (electronPlatformName !== 'linux' || process.env.FORCE) return
|
if (electronPlatformName !== 'linux' || process.env.FORCE) return
|
||||||
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
|
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
|
||||||
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
|
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ const options = {
|
|||||||
'node_modules/node-gyp-build',
|
'node_modules/node-gyp-build',
|
||||||
'node_modules/bufferutil',
|
'node_modules/bufferutil',
|
||||||
'node_modules/utf-8-validate',
|
'node_modules/utf-8-validate',
|
||||||
'build/Release/qrc_decode.node',
|
|
||||||
'dist/**/*',
|
'dist/**/*',
|
||||||
],
|
],
|
||||||
asar: {
|
asar: {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ const unzip = async(filePath) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const files = [
|
const files = [
|
||||||
'qrc_decode',
|
|
||||||
'better_sqlite3',
|
'better_sqlite3',
|
||||||
]
|
]
|
||||||
const moveFile = async(filePath) => {
|
const moveFile = async(filePath) => {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,6 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
target: 'electron-main',
|
target: 'electron-main',
|
||||||
output: {
|
output: {
|
||||||
@@ -18,7 +16,6 @@ module.exports = {
|
|||||||
'electron-font-manager': 'electron-font-manager',
|
'electron-font-manager': 'electron-font-manager',
|
||||||
bufferutil: 'bufferutil',
|
bufferutil: 'bufferutil',
|
||||||
'utf-8-validate': 'utf-8-validate',
|
'utf-8-validate': 'utf-8-validate',
|
||||||
'qrc_decode.node': isDev ? path.join(__dirname, '../../build/Release/qrc_decode.node') : path.join('../build/Release/qrc_decode.node'),
|
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -19,15 +19,6 @@ module.exports = merge(baseConfig, {
|
|||||||
externals: [
|
externals: [
|
||||||
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||||
],
|
],
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
|
|||||||
@@ -18,15 +18,6 @@ module.exports = merge(baseConfig, {
|
|||||||
externals: [
|
externals: [
|
||||||
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||||
],
|
],
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ const gitInfo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!execSync('git status --porcelain').toString().trim()) {
|
let isClean = !execSync('git status --porcelain').toString().trim()
|
||||||
|
if (process.env.BUILD_WIN7) {
|
||||||
|
console.warn('BUILD_WIN7 is set, skipping git status check.')
|
||||||
|
console.log('Workspace status:', execSync('git status --porcelain').toString().trim())
|
||||||
|
isClean = true
|
||||||
|
}
|
||||||
|
if (isClean) {
|
||||||
gitInfo.commit_id = execSync('git log -1 --pretty=format:"%H"').toString().trim()
|
gitInfo.commit_id = execSync('git log -1 --pretty=format:"%H"').toString().trim()
|
||||||
gitInfo.commit_date = execSync('git log -1 --pretty=format:"%ad" --date=iso-strict').toString().trim()
|
gitInfo.commit_date = execSync('git log -1 --pretty=format:"%ad" --date=iso-strict').toString().trim()
|
||||||
} else if (process.env.IS_CI) {
|
} else if (process.env.IS_CI) {
|
||||||
@@ -33,15 +39,6 @@ module.exports = merge(baseConfig, {
|
|||||||
externals: [
|
externals: [
|
||||||
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
|
||||||
],
|
],
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
|
|||||||
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
"rootDir": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@main/*": ["src/main/*"],
|
"@main/*": ["./src/main/*"],
|
||||||
"@renderer/*": ["src/renderer/*"],
|
"@renderer/*": ["./src/renderer/*"],
|
||||||
"@lyric/*": ["src/renderer-lyric/*"],
|
"@lyric/*": ["./src/renderer-lyric/*"],
|
||||||
"@static/*": ["src/static/*"],
|
"@static/*": ["./src/static/*"],
|
||||||
"@common/*": ["src/common/*"],
|
"@common/*": ["./src/common/*"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vueCompilerOptions": {
|
"vueCompilerOptions": {
|
||||||
|
|||||||
Generated
+2954
-5486
File diff suppressed because it is too large
Load Diff
+47
-51
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lx-music-desktop",
|
"name": "lx-music-desktop",
|
||||||
"version": "2.12.0",
|
"version": "2.12.3-beta.5",
|
||||||
"description": "一个免费的音乐查找助手",
|
"description": "一个免费的音乐查找助手",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -107,96 +107,87 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.28.5",
|
|
||||||
"@babel/eslint-parser": "^7.28.5",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
||||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
|
||||||
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.28.5",
|
|
||||||
"@babel/preset-env": "^7.28.5",
|
|
||||||
"@babel/preset-typescript": "^7.28.5",
|
|
||||||
"@babel/runtime": "^7.28.4",
|
|
||||||
"@tsconfig/recommended": "^1.0.13",
|
"@tsconfig/recommended": "^1.0.13",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/needle": "^3.3.0",
|
"@types/needle": "^3.3.0",
|
||||||
"@types/node": "^20.19.25",
|
"@types/node": "^20.19.41",
|
||||||
"@types/tunnel": "^0.0.7",
|
"@types/tunnel": "^0.0.7",
|
||||||
"@types/ws": "8.5.4",
|
"@types/ws": "8.5.4",
|
||||||
"@vue/language-plugin-pug": "^3.1.5",
|
"@vue/language-plugin-pug": "^3.3.5",
|
||||||
"babel-loader": "^10.0.0",
|
"browserslist": "^4.28.2",
|
||||||
"browserslist": "^4.28.0",
|
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"changelog-parser": "^3.0.1",
|
"changelog-parser": "^3.0.1",
|
||||||
"copy-webpack-plugin": "^13.0.1",
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
"core-js": "^3.47.0",
|
"core-js": "^3.49.0",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.4",
|
||||||
"css-minimizer-webpack-plugin": "^7.0.2",
|
"css-minimizer-webpack-plugin": "^8.0.0",
|
||||||
"del": "^6.1.1",
|
"del": "^6.1.1",
|
||||||
"electron": "37.6.1",
|
"electron": "42.4.0",
|
||||||
"electron-builder": "^26.3.2",
|
"electron-builder": "^26.15.3",
|
||||||
"electron-debug": "^3.2.0",
|
"electron-debug": "^3.2.0",
|
||||||
"electron-devtools-installer": "github:lyswhut/electron-devtools-installer#64596d615c1fc891eefd8aef1dfcb2c87aaadf03",
|
"electron-devtools-installer": "github:lyswhut/electron-devtools-installer#64596d615c1fc891eefd8aef1dfcb2c87aaadf03",
|
||||||
"electron-to-chromium": "^1.5.262",
|
"electron-to-chromium": "^1.5.372",
|
||||||
"electron-updater": "6.7.2",
|
"electron-updater": "6.8.9",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-standard": "^17.1.0",
|
"eslint-config-standard": "^17.1.0",
|
||||||
"eslint-config-standard-with-typescript": "^43.0.1",
|
"eslint-config-standard-with-typescript": "^43.0.1",
|
||||||
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
|
||||||
"eslint-plugin-html": "^8.1.3",
|
"eslint-plugin-html": "^8.1.4",
|
||||||
"eslint-plugin-vue": "^9.33.0",
|
"eslint-plugin-vue": "^9.33.0",
|
||||||
"eslint-plugin-vue-pug": "^0.6.2",
|
"eslint-plugin-vue-pug": "^0.6.2",
|
||||||
"eslint-webpack-plugin": "^4.2.0",
|
"eslint-webpack-plugin": "^4.2.0",
|
||||||
"html-webpack-plugin": "^5.6.5",
|
"html-webpack-plugin": "^5.6.7",
|
||||||
"less": "^4.4.2",
|
"less": "^4.6.6",
|
||||||
"less-loader": "^12.3.0",
|
"less-loader": "^13.0.0",
|
||||||
"mini-css-extract-plugin": "^2.9.4",
|
"mini-css-extract-plugin": "^2.10.2",
|
||||||
"node-loader": "^2.1.0",
|
"node-loader": "^2.1.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.15",
|
||||||
"postcss-loader": "^8.2.0",
|
"postcss-loader": "^8.2.1",
|
||||||
"postcss-pxtorem": "^6.1.0",
|
"postcss-pxtorem": "^6.1.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.4",
|
||||||
"pug-plain-loader": "^1.1.0",
|
"pug-plain-loader": "^1.1.0",
|
||||||
"rimraf": "^6.1.2",
|
"rimraf": "^6.1.3",
|
||||||
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
|
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
|
||||||
"svg-sprite-loader": "^6.0.11",
|
"svg-sprite-loader": "^6.0.11",
|
||||||
"svg-transform-loader": "^2.0.13",
|
"svg-transform-loader": "^2.0.13",
|
||||||
"svgo-loader": "^4.0.0",
|
"svgo-loader": "^5.0.0",
|
||||||
"terser": "^5.44.1",
|
"terser": "^5.48.0",
|
||||||
"terser-webpack-plugin": "^5.3.14",
|
"terser-webpack-plugin": "^5.6.1",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
"ts-loader": "^9.5.4",
|
"ts-loader": "^9.6.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"vue-eslint-parser": "^9.4.3",
|
"vue-eslint-parser": "^9.4.3",
|
||||||
"vue-loader": "^17.4.2",
|
"vue-loader": "^17.4.2",
|
||||||
"webpack": "^5.103.0",
|
"webpack": "^5.107.2",
|
||||||
"webpack-cli": "^6.0.1",
|
"webpack-cli": "^7.0.3",
|
||||||
"webpack-dev-server": "5.2.2",
|
"webpack-dev-server": "5.2.5",
|
||||||
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
|
||||||
"webpack-merge": "^6.0.1"
|
"webpack-merge": "^6.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@simonwep/pickr": "^1.9.1",
|
"@simonwep/pickr": "^1.9.1",
|
||||||
"better-sqlite3": "^12.5.0",
|
"better-sqlite3": "^12.10.1",
|
||||||
"bufferutil": "^4.0.9",
|
"bufferutil": "^4.1.0",
|
||||||
"comlink": "~4.3.1",
|
"comlink": "~4.3.1",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"electron-log": "^5.4.3",
|
"electron-log": "^5.4.4",
|
||||||
"font-list": "^2.0.1",
|
"font-list": "^2.1.0",
|
||||||
"iconv-lite": "^0.7.0",
|
"iconv-lite": "^0.7.2",
|
||||||
"image-size": "^1.1.0",
|
"image-size": "^1.1.0",
|
||||||
"jschardet": "^3.1.4",
|
"jschardet": "^3.1.4",
|
||||||
"long": "^5.3.2",
|
"long": "^5.3.2",
|
||||||
"message2call": "^0.1.3",
|
"message2call": "^0.1.3",
|
||||||
"music-metadata": "^11.10.3",
|
"music-metadata": "^11.13.0",
|
||||||
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
|
||||||
"node-id3": "^0.2.9",
|
"node-id3": "^0.2.9",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.7",
|
||||||
"tunnel": "^0.0.6",
|
"tunnel": "^0.0.6",
|
||||||
"utf-8-validate": "^6.0.5",
|
"undici": "^7.27.1",
|
||||||
|
"utf-8-validate": "^6.0.6",
|
||||||
"vue": "~3.3.13",
|
"vue": "~3.3.13",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "~4.5.1",
|
||||||
"ws": "^8.18.3"
|
"ws": "^8.21.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"got": "^11",
|
"got": "^11",
|
||||||
@@ -204,14 +195,19 @@
|
|||||||
"node-abi": "latest",
|
"node-abi": "latest",
|
||||||
"minimatch": "latest",
|
"minimatch": "latest",
|
||||||
"semver": "latest",
|
"semver": "latest",
|
||||||
|
"posthtml": "latest",
|
||||||
|
"node-gyp": "latest",
|
||||||
|
"sockjs": {
|
||||||
|
"uuid": "^12"
|
||||||
|
},
|
||||||
"svg-transform-loader": {
|
"svg-transform-loader": {
|
||||||
"postcss": "latest"
|
"postcss": "^8"
|
||||||
},
|
},
|
||||||
"svg-sprite-loader": {
|
"svg-sprite-loader": {
|
||||||
"postcss": "latest"
|
"postcss": "^8"
|
||||||
},
|
},
|
||||||
"svg-baker": {
|
"svg-baker": {
|
||||||
"postcss": "latest"
|
"postcss": "^8"
|
||||||
},
|
},
|
||||||
"braces": "latest",
|
"braces": "latest",
|
||||||
"node-gyp-build": "latest",
|
"node-gyp-build": "latest",
|
||||||
|
|||||||
+10
-3
@@ -1,6 +1,13 @@
|
|||||||
我们很高兴地宣布新项目 Any Listen 的桌面版已发布,目前已支持列表跟随本地文件自动更新、加载并播放WebDAV上的歌曲等功能,更多功能仍在积极开发中,桌面版与Web版将同步更新。
|
### 优化
|
||||||
对于有播放本地音乐或播放服务器上音乐需求的人可以试试,若遇到任何问题可以发 issue 反馈。
|
|
||||||
|
- 优化kw歌单列表数据显示
|
||||||
|
- 同步服务在连接时允许URL重定向
|
||||||
|
- Linux 点击托盘时将显示主界面(#2840)
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|
||||||
- 修复音量条在调整音量时实际音量与显示的数值不一致的问题(#2606)
|
- 修复 kg 搜索结果显示问题 (#2782)
|
||||||
|
- 修复某些情况下开放 API 获取到的音量为 0 的问题 (#2790)
|
||||||
|
- 修复 mg 图片、歌词获取
|
||||||
|
- 修复某些 tx 歌单打开失败的问题 (#1060, @ght-000)
|
||||||
|
- 修复在某些情况下添加歌曲时可能导致保存的歌曲顺序不对的问题(#2842)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -78,10 +78,6 @@ const modules = {
|
|||||||
|
|
||||||
restart_window: 'restart_window',
|
restart_window: 'restart_window',
|
||||||
|
|
||||||
// lang_s2t: 'lang_s2t',
|
|
||||||
|
|
||||||
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
|
|
||||||
handle_tx_decode_lyric: 'handle_tx_decode_lyric',
|
|
||||||
get_lyric_info: 'get_lyric_info',
|
get_lyric_info: 'get_lyric_info',
|
||||||
set_lyric_info: 'set_lyric_info',
|
set_lyric_info: 'set_lyric_info',
|
||||||
set_config: 'set_config',
|
set_config: 'set_config',
|
||||||
@@ -166,6 +162,7 @@ const modules = {
|
|||||||
key_down: 'key_down',
|
key_down: 'key_down',
|
||||||
request_main_window_channel: 'request_main_window_channel',
|
request_main_window_channel: 'request_main_window_channel',
|
||||||
provide_main_window_channel: 'provide_main_window_channel',
|
provide_main_window_channel: 'provide_main_window_channel',
|
||||||
|
mouse_enter_leave: 'mouse_enter_leave',
|
||||||
},
|
},
|
||||||
hotKey: {
|
hotKey: {
|
||||||
enable: 'enable',
|
enable: 'enable',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"./types"
|
"./types"
|
||||||
],
|
],
|
||||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
"@common/*": ["common/*"],
|
"@common/*": ["./*"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// 非业务工具方法
|
// 非业务工具方法
|
||||||
|
|
||||||
|
import { pathToFileURL } from 'url'
|
||||||
/**
|
/**
|
||||||
* 获取两个数之间的随机整数,大于等于min,小于max
|
* 获取两个数之间的随机整数,大于等于min,小于max
|
||||||
* @param {*} min
|
* @param {*} min
|
||||||
@@ -188,7 +189,7 @@ export const sortInsert = <T>(arr: Array<{ num: number, data: T }>, data: { num:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const encodePath = (path: string) => {
|
export const encodePath = (path: string) => {
|
||||||
return encodeURI(path.replaceAll('\\', '/'))
|
return pathToFileURL(path).href
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
import qs from 'node:querystring'
|
||||||
|
import {
|
||||||
|
FormData,
|
||||||
|
getGlobalDispatcher,
|
||||||
|
interceptors,
|
||||||
|
request as nodeRrequest,
|
||||||
|
ProxyAgent,
|
||||||
|
setGlobalDispatcher,
|
||||||
|
type Dispatcher,
|
||||||
|
} from 'undici'
|
||||||
|
|
||||||
|
const defaultOptions: Options = {
|
||||||
|
timeout: 15000,
|
||||||
|
headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
||||||
|
},
|
||||||
|
maxRedirect: 5,
|
||||||
|
} as const
|
||||||
|
const redirectDispatcher = interceptors.redirect({ maxRedirections: defaultOptions.maxRedirect })
|
||||||
|
const dispatchers = [
|
||||||
|
interceptors.retry({
|
||||||
|
maxRetries: 3,
|
||||||
|
minTimeout: 1000,
|
||||||
|
maxTimeout: 10000,
|
||||||
|
timeoutFactor: 2,
|
||||||
|
retryAfter: true,
|
||||||
|
}),
|
||||||
|
// interceptors.responseError(),
|
||||||
|
] as const
|
||||||
|
let proxyAgent: ProxyAgent | null = null
|
||||||
|
let globalDispatcher = getGlobalDispatcher()
|
||||||
|
const buildDispatcher = (redirectDispatcher: Dispatcher.DispatcherComposeInterceptor | null, retryNum = 3) => {
|
||||||
|
const otherInterceptors =
|
||||||
|
retryNum == 3
|
||||||
|
? dispatchers
|
||||||
|
: [
|
||||||
|
interceptors.retry({
|
||||||
|
maxRetries: retryNum,
|
||||||
|
minTimeout: 1000,
|
||||||
|
maxTimeout: 6000,
|
||||||
|
timeoutFactor: 2,
|
||||||
|
retryAfter: true,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
if (redirectDispatcher) {
|
||||||
|
return (proxyAgent ?? globalDispatcher).compose(redirectDispatcher, ...otherInterceptors)
|
||||||
|
}
|
||||||
|
return (proxyAgent ?? globalDispatcher).compose(...otherInterceptors)
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobalDispatcher(buildDispatcher(redirectDispatcher))
|
||||||
|
|
||||||
|
export const setProxy = (url?: string) => {
|
||||||
|
proxyAgent = url ? new ProxyAgent(url) : null
|
||||||
|
setGlobalDispatcher(buildDispatcher(redirectDispatcher))
|
||||||
|
}
|
||||||
|
export const setProxyByHost = (host?: string, port?: string) => {
|
||||||
|
setProxy(host ? `http://${host}:${port}` : undefined)
|
||||||
|
}
|
||||||
|
const CONTENT_TYPE = {
|
||||||
|
json: 'application/json',
|
||||||
|
form: 'application/x-www-form-urlencoded',
|
||||||
|
text: 'text/plain',
|
||||||
|
formdata: 'multipart/form-data',
|
||||||
|
xml: 'application/xml',
|
||||||
|
binary: 'application/octet-stream',
|
||||||
|
}
|
||||||
|
type ParamsData = Record<string, string | number | null | undefined | boolean>
|
||||||
|
export interface Options {
|
||||||
|
method?:
|
||||||
|
| 'GET'
|
||||||
|
| 'HEAD'
|
||||||
|
| 'POST'
|
||||||
|
| 'PUT'
|
||||||
|
| 'DELETE'
|
||||||
|
| 'OPTIONS'
|
||||||
|
| 'PATCH'
|
||||||
|
| 'PROPFIND'
|
||||||
|
| 'COPY'
|
||||||
|
| 'MOVE'
|
||||||
|
| 'MKCOL'
|
||||||
|
| 'PROPPATCH'
|
||||||
|
| 'QUOTA'
|
||||||
|
query?: ParamsData
|
||||||
|
headers?: Record<string, string | string[]>
|
||||||
|
timeout?: number
|
||||||
|
maxRedirect?: number
|
||||||
|
signal?: AbortController['signal']
|
||||||
|
json?: Record<string, unknown>
|
||||||
|
form?: ParamsData
|
||||||
|
binary?: Buffer | Uint8Array
|
||||||
|
text?: string
|
||||||
|
formdata?: Record<string, string | Buffer | Uint8Array>
|
||||||
|
xml?: string
|
||||||
|
needBody?: boolean
|
||||||
|
needRaw?: boolean
|
||||||
|
retryNum?: number
|
||||||
|
}
|
||||||
|
export interface Response<Res> {
|
||||||
|
headers: {
|
||||||
|
accept?: string | undefined
|
||||||
|
'accept-language'?: string | undefined
|
||||||
|
'accept-patch'?: string | undefined
|
||||||
|
'accept-ranges'?: string | undefined
|
||||||
|
'access-control-allow-credentials'?: string | undefined
|
||||||
|
'access-control-allow-headers'?: string | undefined
|
||||||
|
'access-control-allow-methods'?: string | undefined
|
||||||
|
'access-control-allow-origin'?: string | undefined
|
||||||
|
'access-control-expose-headers'?: string | undefined
|
||||||
|
'access-control-max-age'?: string | undefined
|
||||||
|
'access-control-request-headers'?: string | undefined
|
||||||
|
'access-control-request-method'?: string | undefined
|
||||||
|
age?: string | undefined
|
||||||
|
allow?: string | undefined
|
||||||
|
'alt-svc'?: string | undefined
|
||||||
|
authorization?: string | undefined
|
||||||
|
'cache-control'?: string | undefined
|
||||||
|
connection?: string | undefined
|
||||||
|
'content-disposition'?: string | undefined
|
||||||
|
'content-encoding'?: string | undefined
|
||||||
|
'content-language'?: string | undefined
|
||||||
|
'content-length'?: string | undefined
|
||||||
|
'content-location'?: string | undefined
|
||||||
|
'content-range'?: string | undefined
|
||||||
|
'content-type'?: string | undefined
|
||||||
|
cookie?: string | undefined
|
||||||
|
date?: string | undefined
|
||||||
|
etag?: string | undefined
|
||||||
|
expect?: string | undefined
|
||||||
|
expires?: string | undefined
|
||||||
|
forwarded?: string | undefined
|
||||||
|
from?: string | undefined
|
||||||
|
host?: string | undefined
|
||||||
|
'if-match'?: string | undefined
|
||||||
|
'if-modified-since'?: string | undefined
|
||||||
|
'if-none-match'?: string | undefined
|
||||||
|
'if-unmodified-since'?: string | undefined
|
||||||
|
'last-modified'?: string | undefined
|
||||||
|
location?: string | undefined
|
||||||
|
origin?: string | undefined
|
||||||
|
pragma?: string | undefined
|
||||||
|
'proxy-authenticate'?: string | undefined
|
||||||
|
'proxy-authorization'?: string | undefined
|
||||||
|
'public-key-pins'?: string | undefined
|
||||||
|
range?: string | undefined
|
||||||
|
referer?: string | undefined
|
||||||
|
'retry-after'?: string | undefined
|
||||||
|
'sec-websocket-accept'?: string | undefined
|
||||||
|
'sec-websocket-extensions'?: string | undefined
|
||||||
|
'sec-websocket-key'?: string | undefined
|
||||||
|
'sec-websocket-protocol'?: string | undefined
|
||||||
|
'sec-websocket-version'?: string | undefined
|
||||||
|
'set-cookie'?: string[] | undefined
|
||||||
|
'strict-transport-security'?: string | undefined
|
||||||
|
tk?: string | undefined
|
||||||
|
trailer?: string | undefined
|
||||||
|
'transfer-encoding'?: string | undefined
|
||||||
|
upgrade?: string | undefined
|
||||||
|
'user-agent'?: string | undefined
|
||||||
|
vary?: string | undefined
|
||||||
|
via?: string | undefined
|
||||||
|
warning?: string | undefined
|
||||||
|
'www-authenticate'?: string | undefined
|
||||||
|
}
|
||||||
|
body: Res
|
||||||
|
raw: Uint8Array
|
||||||
|
statusCode?: number
|
||||||
|
statusMessage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildRequestBody = (options: Options) => {
|
||||||
|
let contentType: string | undefined
|
||||||
|
let body: string | Buffer | Uint8Array | FormData | undefined
|
||||||
|
if (options.json != null) {
|
||||||
|
contentType = CONTENT_TYPE.json
|
||||||
|
body = JSON.stringify(options.json)
|
||||||
|
} else if (options.form != null) {
|
||||||
|
contentType = CONTENT_TYPE.form
|
||||||
|
body = qs.stringify(options.form)
|
||||||
|
} else if (options.binary != null) {
|
||||||
|
contentType = CONTENT_TYPE.binary
|
||||||
|
body = options.binary
|
||||||
|
} else if (options.formdata != null) {
|
||||||
|
const formdata = new FormData()
|
||||||
|
// eslint-disable-next-line guard-for-in
|
||||||
|
for (const key in options.formdata) {
|
||||||
|
formdata.append(key, options.formdata[key])
|
||||||
|
}
|
||||||
|
contentType = CONTENT_TYPE.formdata
|
||||||
|
body = formdata
|
||||||
|
} else if (options.xml != null) {
|
||||||
|
contentType = CONTENT_TYPE.xml
|
||||||
|
body = options.xml
|
||||||
|
} else if (options.text != null) {
|
||||||
|
contentType = CONTENT_TYPE.text
|
||||||
|
body = options.text
|
||||||
|
}
|
||||||
|
const headers = options.headers ?? {}
|
||||||
|
if (headers['Content-Type']) {
|
||||||
|
contentType = headers['Content-Type'] as string
|
||||||
|
}
|
||||||
|
const finalHeaders: NonNullable<Options['headers']> = { ...defaultOptions.headers, ...headers }
|
||||||
|
if (contentType) finalHeaders['Content-Type'] = contentType
|
||||||
|
return [finalHeaders, body] as const
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildRequestDispatcher = (options: Options) => {
|
||||||
|
let dispatcher: Dispatcher.ComposedDispatcher | undefined
|
||||||
|
|
||||||
|
if (options.maxRedirect != null) {
|
||||||
|
if (options.maxRedirect != defaultOptions.maxRedirect) {
|
||||||
|
if (options.maxRedirect) {
|
||||||
|
dispatcher = buildDispatcher(interceptors.redirect({ maxRedirections: options.maxRedirect }), options.retryNum)
|
||||||
|
} else {
|
||||||
|
dispatcher = buildDispatcher(null, options.retryNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
export const request = async <T = unknown>(url: string, options: Options = {}): Promise<Response<T>> => {
|
||||||
|
const method = options.method?.toUpperCase() ?? 'GET'
|
||||||
|
const timeout = options.timeout ?? defaultOptions.timeout
|
||||||
|
const [headers, body] = buildRequestBody(options)
|
||||||
|
// console.log(url, {
|
||||||
|
// method,
|
||||||
|
// bodyTimeout: timeout,
|
||||||
|
// headersTimeout: timeout,
|
||||||
|
// headers,
|
||||||
|
// query: options.query,
|
||||||
|
// body,
|
||||||
|
// signal: options.signal,
|
||||||
|
// dispatcher: buildRequestDispatcher(options),
|
||||||
|
// })
|
||||||
|
return nodeRrequest(url, {
|
||||||
|
method,
|
||||||
|
bodyTimeout: timeout,
|
||||||
|
headersTimeout: timeout,
|
||||||
|
headers,
|
||||||
|
query: options.query,
|
||||||
|
body,
|
||||||
|
signal: options.signal,
|
||||||
|
dispatcher: buildRequestDispatcher(options),
|
||||||
|
}).then(async(response) => {
|
||||||
|
if (options.needBody) {
|
||||||
|
return {
|
||||||
|
headers: response.headers,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
body: response.body as unknown as T,
|
||||||
|
} satisfies Omit<Response<T>, 'raw'> as Response<T>
|
||||||
|
}
|
||||||
|
if (options.needRaw) {
|
||||||
|
return {
|
||||||
|
headers: response.headers,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
raw: await response.body.bytes(),
|
||||||
|
} satisfies Omit<Response<T>, 'body'> as Response<T>
|
||||||
|
}
|
||||||
|
// console.log(response)
|
||||||
|
let body = (await response.body.text()) as T
|
||||||
|
if (!headers['Content-Type'] || headers['Content-Type'].includes(CONTENT_TYPE.json)) {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(body as string) as T
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
body,
|
||||||
|
headers: response.headers,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
} satisfies Omit<Response<T>, 'raw'> as Response<T>
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import qs from 'node:querystring'
|
||||||
|
import {
|
||||||
|
FormData,
|
||||||
|
getGlobalDispatcher,
|
||||||
|
request as nodeRrequest,
|
||||||
|
ProxyAgent,
|
||||||
|
setGlobalDispatcher,
|
||||||
|
type Dispatcher,
|
||||||
|
} from 'undici'
|
||||||
|
|
||||||
|
const defaultOptions: Options = {
|
||||||
|
timeout: 15000,
|
||||||
|
headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
||||||
|
},
|
||||||
|
maxRedirect: 5,
|
||||||
|
} as const
|
||||||
|
let proxyAgent: ProxyAgent | null = null
|
||||||
|
let globalDispatcher = getGlobalDispatcher()
|
||||||
|
const buildDispatcher = () => {
|
||||||
|
return proxyAgent ?? globalDispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobalDispatcher(buildDispatcher())
|
||||||
|
|
||||||
|
export const setProxy = (url?: string) => {
|
||||||
|
proxyAgent = url ? new ProxyAgent(url) : null
|
||||||
|
setGlobalDispatcher(buildDispatcher())
|
||||||
|
}
|
||||||
|
export const setProxyByHost = (host?: string, port?: string) => {
|
||||||
|
console.log(host)
|
||||||
|
setProxy(host ? `http://${host}:${port}` : undefined)
|
||||||
|
}
|
||||||
|
const CONTENT_TYPE = {
|
||||||
|
json: 'application/json',
|
||||||
|
form: 'application/x-www-form-urlencoded',
|
||||||
|
text: 'text/plain',
|
||||||
|
formdata: 'multipart/form-data',
|
||||||
|
xml: 'application/xml',
|
||||||
|
binary: 'application/octet-stream',
|
||||||
|
}
|
||||||
|
type ParamsData = Record<string, string | number | null | undefined | boolean>
|
||||||
|
export interface Options {
|
||||||
|
method?:
|
||||||
|
| 'GET'
|
||||||
|
| 'HEAD'
|
||||||
|
| 'POST'
|
||||||
|
| 'PUT'
|
||||||
|
| 'DELETE'
|
||||||
|
| 'OPTIONS'
|
||||||
|
| 'PATCH'
|
||||||
|
| 'PROPFIND'
|
||||||
|
| 'COPY'
|
||||||
|
| 'MOVE'
|
||||||
|
| 'MKCOL'
|
||||||
|
| 'PROPPATCH'
|
||||||
|
| 'QUOTA'
|
||||||
|
query?: ParamsData
|
||||||
|
headers?: Record<string, string | string[]>
|
||||||
|
timeout?: number
|
||||||
|
maxRedirect?: number
|
||||||
|
signal?: AbortController['signal']
|
||||||
|
json?: Record<string, unknown>
|
||||||
|
form?: ParamsData
|
||||||
|
binary?: Buffer | Uint8Array
|
||||||
|
text?: string
|
||||||
|
formdata?: Record<string, string | Buffer | Uint8Array>
|
||||||
|
xml?: string
|
||||||
|
needBody?: boolean
|
||||||
|
needRaw?: boolean
|
||||||
|
retryNum?: number
|
||||||
|
}
|
||||||
|
export interface Response<Res> {
|
||||||
|
headers: {
|
||||||
|
accept?: string | undefined
|
||||||
|
'accept-language'?: string | undefined
|
||||||
|
'accept-patch'?: string | undefined
|
||||||
|
'accept-ranges'?: string | undefined
|
||||||
|
'access-control-allow-credentials'?: string | undefined
|
||||||
|
'access-control-allow-headers'?: string | undefined
|
||||||
|
'access-control-allow-methods'?: string | undefined
|
||||||
|
'access-control-allow-origin'?: string | undefined
|
||||||
|
'access-control-expose-headers'?: string | undefined
|
||||||
|
'access-control-max-age'?: string | undefined
|
||||||
|
'access-control-request-headers'?: string | undefined
|
||||||
|
'access-control-request-method'?: string | undefined
|
||||||
|
age?: string | undefined
|
||||||
|
allow?: string | undefined
|
||||||
|
'alt-svc'?: string | undefined
|
||||||
|
authorization?: string | undefined
|
||||||
|
'cache-control'?: string | undefined
|
||||||
|
connection?: string | undefined
|
||||||
|
'content-disposition'?: string | undefined
|
||||||
|
'content-encoding'?: string | undefined
|
||||||
|
'content-language'?: string | undefined
|
||||||
|
'content-length'?: string | undefined
|
||||||
|
'content-location'?: string | undefined
|
||||||
|
'content-range'?: string | undefined
|
||||||
|
'content-type'?: string | undefined
|
||||||
|
cookie?: string | undefined
|
||||||
|
date?: string | undefined
|
||||||
|
etag?: string | undefined
|
||||||
|
expect?: string | undefined
|
||||||
|
expires?: string | undefined
|
||||||
|
forwarded?: string | undefined
|
||||||
|
from?: string | undefined
|
||||||
|
host?: string | undefined
|
||||||
|
'if-match'?: string | undefined
|
||||||
|
'if-modified-since'?: string | undefined
|
||||||
|
'if-none-match'?: string | undefined
|
||||||
|
'if-unmodified-since'?: string | undefined
|
||||||
|
'last-modified'?: string | undefined
|
||||||
|
location?: string | undefined
|
||||||
|
origin?: string | undefined
|
||||||
|
pragma?: string | undefined
|
||||||
|
'proxy-authenticate'?: string | undefined
|
||||||
|
'proxy-authorization'?: string | undefined
|
||||||
|
'public-key-pins'?: string | undefined
|
||||||
|
range?: string | undefined
|
||||||
|
referer?: string | undefined
|
||||||
|
'retry-after'?: string | undefined
|
||||||
|
'sec-websocket-accept'?: string | undefined
|
||||||
|
'sec-websocket-extensions'?: string | undefined
|
||||||
|
'sec-websocket-key'?: string | undefined
|
||||||
|
'sec-websocket-protocol'?: string | undefined
|
||||||
|
'sec-websocket-version'?: string | undefined
|
||||||
|
'set-cookie'?: string[] | undefined
|
||||||
|
'strict-transport-security'?: string | undefined
|
||||||
|
tk?: string | undefined
|
||||||
|
trailer?: string | undefined
|
||||||
|
'transfer-encoding'?: string | undefined
|
||||||
|
upgrade?: string | undefined
|
||||||
|
'user-agent'?: string | undefined
|
||||||
|
vary?: string | undefined
|
||||||
|
via?: string | undefined
|
||||||
|
warning?: string | undefined
|
||||||
|
'www-authenticate'?: string | undefined
|
||||||
|
}
|
||||||
|
body: Res
|
||||||
|
raw: Uint8Array
|
||||||
|
statusCode?: number
|
||||||
|
statusMessage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildRequestBody = (options: Options) => {
|
||||||
|
let contentType: string | undefined
|
||||||
|
let body: string | Buffer | Uint8Array | FormData | undefined
|
||||||
|
if (options.json != null) {
|
||||||
|
contentType = CONTENT_TYPE.json
|
||||||
|
body = JSON.stringify(options.json)
|
||||||
|
} else if (options.form != null) {
|
||||||
|
contentType = CONTENT_TYPE.form
|
||||||
|
body = qs.stringify(options.form)
|
||||||
|
} else if (options.binary != null) {
|
||||||
|
contentType = CONTENT_TYPE.binary
|
||||||
|
body = options.binary
|
||||||
|
} else if (options.formdata != null) {
|
||||||
|
const formdata = new FormData()
|
||||||
|
// eslint-disable-next-line guard-for-in
|
||||||
|
for (const key in options.formdata) {
|
||||||
|
formdata.append(key, options.formdata[key])
|
||||||
|
}
|
||||||
|
contentType = CONTENT_TYPE.formdata
|
||||||
|
body = formdata
|
||||||
|
} else if (options.xml != null) {
|
||||||
|
contentType = CONTENT_TYPE.xml
|
||||||
|
body = options.xml
|
||||||
|
} else if (options.text != null) {
|
||||||
|
contentType = CONTENT_TYPE.text
|
||||||
|
body = options.text
|
||||||
|
}
|
||||||
|
const headers = options.headers ?? {}
|
||||||
|
if (headers['Content-Type']) {
|
||||||
|
contentType = headers['Content-Type'] as string
|
||||||
|
}
|
||||||
|
const finalHeaders: NonNullable<Options['headers']> = { ...defaultOptions.headers, ...headers }
|
||||||
|
if (contentType) finalHeaders['Content-Type'] = contentType
|
||||||
|
return [finalHeaders, body] as const
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildRequestDispatcher = (options: Options) => {
|
||||||
|
return buildDispatcher()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const request = async <T = unknown>(url: string, options: Options = {}): Promise<Response<T>> => {
|
||||||
|
const method = (options.method?.toUpperCase() ?? 'GET') as Dispatcher.RequestOptions['method']
|
||||||
|
const timeout = options.timeout ?? defaultOptions.timeout
|
||||||
|
const [headers, body] = buildRequestBody(options)
|
||||||
|
// console.log(url, {
|
||||||
|
// method,
|
||||||
|
// bodyTimeout: timeout,
|
||||||
|
// headersTimeout: timeout,
|
||||||
|
// headers,
|
||||||
|
// query: options.query,
|
||||||
|
// body,
|
||||||
|
// signal: options.signal,
|
||||||
|
// dispatcher: buildRequestDispatcher(options),
|
||||||
|
// })
|
||||||
|
return nodeRrequest(url, {
|
||||||
|
method,
|
||||||
|
bodyTimeout: timeout,
|
||||||
|
headersTimeout: timeout,
|
||||||
|
headers,
|
||||||
|
query: options.query,
|
||||||
|
body,
|
||||||
|
signal: options.signal,
|
||||||
|
dispatcher: buildRequestDispatcher(options),
|
||||||
|
maxRedirections: options.maxRedirect ?? defaultOptions.maxRedirect,
|
||||||
|
}).then(async(response) => {
|
||||||
|
if (options.needBody) {
|
||||||
|
return {
|
||||||
|
headers: response.headers,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
body: response.body as unknown as T,
|
||||||
|
} satisfies Omit<Response<T>, 'raw'> as Response<T>
|
||||||
|
}
|
||||||
|
if (options.needRaw) {
|
||||||
|
return {
|
||||||
|
headers: response.headers,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
raw: new Uint8Array(await response.body.arrayBuffer()),
|
||||||
|
} satisfies Omit<Response<T>, 'body'> as unknown as Response<T>
|
||||||
|
}
|
||||||
|
// console.log(response)
|
||||||
|
let body = (await response.body.text()) as T
|
||||||
|
if (!headers['Content-Type'] || headers['Content-Type'].includes(CONTENT_TYPE.json)) {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(body as string) as T
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
body,
|
||||||
|
headers: response.headers,
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
} satisfies Omit<Response<T>, 'raw'> as Response<T>
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -148,3 +148,7 @@ export const clipFileNameLength = (name: string) => {
|
|||||||
return name.length > MAX_FILE_NAME_LENGTH ? name.substring(0, MAX_FILE_NAME_LENGTH) : name
|
return name.length > MAX_FILE_NAME_LENGTH ? name.substring(0, MAX_FILE_NAME_LENGTH) : name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatMusicName = (format: string, name: string, singer: string) => {
|
||||||
|
// return format.replace(/歌名|歌手/g, match => match === '歌名' ? name : singer)
|
||||||
|
return format.replace('歌手', singer).replace('歌名', name)
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -613,7 +613,7 @@
|
|||||||
"songlist__import_input_tip_1": "Cross-service playlists are not supported. Please confirm whether the playlist to be opened corresponds to the current chosen service",
|
"songlist__import_input_tip_1": "Cross-service playlists are not supported. Please confirm whether the playlist to be opened corresponds to the current chosen service",
|
||||||
"songlist__import_input_tip_2": "If you encounter a playlist link that cannot be opened. Please send us your feedback",
|
"songlist__import_input_tip_2": "If you encounter a playlist link that cannot be opened. Please send us your feedback",
|
||||||
"songlist__import_input_tip_3": "Unable to open Kugou playlist with playlist ID or link from lite edition, but support to open it with Kugou code or link from main edition",
|
"songlist__import_input_tip_3": "Unable to open Kugou playlist with playlist ID or link from lite edition, but support to open it with Kugou code or link from main edition",
|
||||||
"songlist__import_input_tip_4": "NetEase Cloud Music's \"I Like\" playlist requires a token to open. For details, see ",
|
"songlist__import_input_tip_4": "NetEase’s “Private Radar” playlists require a token to access. Tencent’s personal playlists require your profile be public to access. For details, see ",
|
||||||
"songlist__import_input_title": "Open shared playlist",
|
"songlist__import_input_title": "Open shared playlist",
|
||||||
"songlist__open_list": "Open \"{name}\" playlist",
|
"songlist__open_list": "Open \"{name}\" playlist",
|
||||||
"songlist__tag_info_hot_tag": "Top Tags",
|
"songlist__tag_info_hot_tag": "Top Tags",
|
||||||
|
|||||||
+1
-1
@@ -613,7 +613,7 @@
|
|||||||
"songlist__import_input_tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前选择的歌单来源是否对应",
|
"songlist__import_input_tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前选择的歌单来源是否对应",
|
||||||
"songlist__import_input_tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
|
"songlist__import_input_tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
|
||||||
"songlist__import_input_tip_3": "酷狗源歌单不支持用歌单 ID 或概念版链接打开,但支持用普通版链接或酷狗码打开",
|
"songlist__import_input_tip_3": "酷狗源歌单不支持用歌单 ID 或概念版链接打开,但支持用普通版链接或酷狗码打开",
|
||||||
"songlist__import_input_tip_4": "网易源的「我喜欢」歌单需要 Token 才能打开,详情看 ",
|
"songlist__import_input_tip_4": "网易源「私人雷达」歌单需要 Token 才能打开,企鹅源个人歌单需要将个人主页公开才能打开,请参阅 ",
|
||||||
"songlist__import_input_title": "打开分享的歌单",
|
"songlist__import_input_title": "打开分享的歌单",
|
||||||
"songlist__open_list": "打开「{name}」歌单",
|
"songlist__open_list": "打开「{name}」歌单",
|
||||||
"songlist__tag_info_hot_tag": "热门标签",
|
"songlist__tag_info_hot_tag": "热门标签",
|
||||||
|
|||||||
+1
-1
@@ -613,7 +613,7 @@
|
|||||||
"songlist__import_input_tip_1": "不支援跨平台開啟歌單,請確認要開啟的歌單與目前歌單的來源平台是否對應",
|
"songlist__import_input_tip_1": "不支援跨平台開啟歌單,請確認要開啟的歌單與目前歌單的來源平台是否對應",
|
||||||
"songlist__import_input_tip_2": "若遇到無法開啟的歌單連結,歡迎回報",
|
"songlist__import_input_tip_2": "若遇到無法開啟的歌單連結,歡迎回報",
|
||||||
"songlist__import_input_tip_3": "酷狗音樂歌單不支援用歌單 ID 與概念版連結開啟,但支援用普通版連結與酷狗碼開啟",
|
"songlist__import_input_tip_3": "酷狗音樂歌單不支援用歌單 ID 與概念版連結開啟,但支援用普通版連結與酷狗碼開啟",
|
||||||
"songlist__import_input_tip_4": "網易雲音樂的「我喜歡」歌單需要 Token 才能開啟,詳情看 ",
|
"songlist__import_input_tip_4": "網易雲音樂「私人雷達」歌單需要 Token 才能開啟,企鵝音樂個人歌單需要將個人資料頁公開才能開啟,詳情看 ",
|
||||||
"songlist__import_input_title": "開啟分享的歌單",
|
"songlist__import_input_title": "開啟分享的歌單",
|
||||||
"songlist__open_list": "開啟「{name}」歌單",
|
"songlist__open_list": "開啟「{name}」歌單",
|
||||||
"songlist__tag_info_hot_tag": "熱門標籤",
|
"songlist__tag_info_hot_tag": "熱門標籤",
|
||||||
|
|||||||
+20
-4
@@ -2,7 +2,7 @@ import path from 'node:path'
|
|||||||
import { existsSync, mkdirSync, renameSync } from 'fs'
|
import { existsSync, mkdirSync, renameSync } from 'fs'
|
||||||
import { app, shell, screen, nativeTheme, dialog } from 'electron'
|
import { app, shell, screen, nativeTheme, dialog } from 'electron'
|
||||||
import { URL_SCHEME_RXP } from '@common/constants'
|
import { URL_SCHEME_RXP } from '@common/constants'
|
||||||
import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
|
import { getProxy, getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
|
||||||
import { navigationUrlWhiteList } from '@common/config'
|
import { navigationUrlWhiteList } from '@common/config'
|
||||||
import defaultSetting from '@common/defaultSetting'
|
import defaultSetting from '@common/defaultSetting'
|
||||||
import { isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
|
import { isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
|
||||||
@@ -11,6 +11,7 @@ import { isMac, log } from '@common/utils'
|
|||||||
import createWorkers from './worker'
|
import createWorkers from './worker'
|
||||||
import { migrateDBData } from './utils/migrate'
|
import { migrateDBData } from './utils/migrate'
|
||||||
import { openDirInExplorer } from '@common/utils/electron'
|
import { openDirInExplorer } from '@common/utils/electron'
|
||||||
|
import { setProxyByHost } from '@common/utils/request'
|
||||||
|
|
||||||
export const initGlobalData = () => {
|
export const initGlobalData = () => {
|
||||||
const envParams = parseEnvParams()
|
const envParams = parseEnvParams()
|
||||||
@@ -236,14 +237,29 @@ export const listenerAppEvent = (startApp: () => void) => {
|
|||||||
global.lx?.event_app.system_theme_change(shouldUseDarkColors)
|
global.lx?.event_app.system_theme_change(shouldUseDarkColors)
|
||||||
})
|
})
|
||||||
|
|
||||||
global.lx.event_app.on('updated_config', (config, setting) => {
|
const setProxy = () => {
|
||||||
if (config.includes('player.volume')) {
|
const proxy = getProxy()
|
||||||
|
if (proxy) {
|
||||||
|
setProxyByHost(proxy.host, proxy.port ? String(proxy.port) : undefined)
|
||||||
|
} else setProxyByHost()
|
||||||
|
}
|
||||||
|
global.lx.event_app.on('updated_config', (keys, setting) => {
|
||||||
|
if (keys.includes('network.proxy.enable') || (global.lx.appSetting['network.proxy.enable'] && keys.some(k => k.includes('network.proxy.')))) {
|
||||||
|
setProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.includes('player.volume')) {
|
||||||
global.lx.event_app.player_status({ volume: Math.trunc(setting['player.volume']! * 100) })
|
global.lx.event_app.player_status({ volume: Math.trunc(setting['player.volume']! * 100) })
|
||||||
}
|
}
|
||||||
if (config.includes('player.isMute')) {
|
if (keys.includes('player.isMute')) {
|
||||||
global.lx.event_app.player_status({ mute: setting['player.isMute'] })
|
global.lx.event_app.player_status({ mute: setting['player.isMute'] })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
global.lx.event_app.on('app_inited', () => {
|
||||||
|
setProxy()
|
||||||
|
global.lx.event_app.player_status({ volume: Math.trunc(global.lx.appSetting['player.volume'] * 100) })
|
||||||
|
global.lx.event_app.player_status({ mute: global.lx.appSetting['player.isMute'] })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const initTheme = () => {
|
const initTheme = () => {
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ let client: LX.Sync.Client.Socket | null
|
|||||||
// let listSyncPromise: Promise<void>
|
// let listSyncPromise: Promise<void>
|
||||||
export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.ClientKeyInfo) => {
|
export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.ClientKeyInfo) => {
|
||||||
client = new WebSocket(`${urlInfo.wsProtocol}//${urlInfo.hostPath}/socket?i=${encodeURIComponent(keyInfo.clientId)}&t=${encodeURIComponent(aesEncrypt(SYNC_CODE.msgConnect, keyInfo.key))}`, {
|
client = new WebSocket(`${urlInfo.wsProtocol}//${urlInfo.hostPath}/socket?i=${encodeURIComponent(keyInfo.clientId)}&t=${encodeURIComponent(aesEncrypt(SYNC_CODE.msgConnect, keyInfo.key))}`, {
|
||||||
|
followRedirects: true,
|
||||||
}) as LX.Sync.Client.Socket
|
}) as LX.Sync.Client.Socket
|
||||||
client.data = {
|
client.data = {
|
||||||
keyInfo,
|
keyInfo,
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import { httpFetch, type RequestOptions } from '@main/utils/request'
|
|||||||
import { decodeData, encodeData } from '../utils'
|
import { decodeData, encodeData } from '../utils'
|
||||||
|
|
||||||
export const request = async(url: string, options: RequestOptions = { }) => {
|
export const request = async(url: string, options: RequestOptions = { }) => {
|
||||||
return httpFetch(url, {
|
return httpFetch<string>(url, {
|
||||||
...options,
|
...options,
|
||||||
timeout: options.timeout ?? 10000,
|
timeout: options.timeout ?? 10000,
|
||||||
follow_max: 5,
|
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
return {
|
return {
|
||||||
text: response.body,
|
text: response.body,
|
||||||
|
|||||||
@@ -150,9 +150,11 @@ export const createTray = () => {
|
|||||||
// tray.setToolTip('LX Music')
|
// tray.setToolTip('LX Music')
|
||||||
// createMenu()
|
// createMenu()
|
||||||
tray.setIgnoreDoubleClickEvents(true)
|
tray.setIgnoreDoubleClickEvents(true)
|
||||||
|
if (!isMac) {
|
||||||
tray.on('click', () => {
|
tray.on('click', () => {
|
||||||
showMainWindow()
|
showMainWindow()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const destroyTray = () => {
|
export const destroyTray = () => {
|
||||||
|
|||||||
@@ -109,14 +109,20 @@ const inflateScript = async(script: string) => new Promise<string>((resolve, rej
|
|||||||
})
|
})
|
||||||
export const importApi = async(scriptRaw: string): Promise<LX.UserApi.UserApiInfo> => {
|
export const importApi = async(scriptRaw: string): Promise<LX.UserApi.UserApiInfo> => {
|
||||||
let scriptInfo = parseScriptInfo(scriptRaw)
|
let scriptInfo = parseScriptInfo(scriptRaw)
|
||||||
|
const script = await deflateScript(scriptRaw)
|
||||||
|
userApis ??= []
|
||||||
|
for (const api of userApis) {
|
||||||
|
const existingScript = scripts.get(api.id)
|
||||||
|
if (existingScript === script) {
|
||||||
|
throw new Error(`导入失败,脚本内容与已有的源「${api.name}」相同`)
|
||||||
|
}
|
||||||
|
}
|
||||||
const apiInfo = {
|
const apiInfo = {
|
||||||
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
|
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
|
||||||
...scriptInfo,
|
...scriptInfo,
|
||||||
allowShowUpdateAlert: true,
|
allowShowUpdateAlert: true,
|
||||||
}
|
}
|
||||||
userApis ??= []
|
|
||||||
userApis.push(apiInfo)
|
userApis.push(apiInfo)
|
||||||
const script = await deflateScript(scriptRaw)
|
|
||||||
scripts.set(apiInfo.id, script)
|
scripts.set(apiInfo.id, script)
|
||||||
saveData()
|
saveData()
|
||||||
return apiInfo
|
return apiInfo
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { isLinux } from '@common/utils'
|
import { isLinux } from '@common/utils'
|
||||||
import { closeWindow, createWindow, getBounds, isExistWindow, alwaysOnTopTools, setBounds, setIgnoreMouseEvents, setSkipTaskbar } from './main'
|
import { closeWindow, createWindow, getBounds, isExistWindow, alwaysOnTopTools, setBounds, setIgnoreMouseEvents, setSkipTaskbar } from './main'
|
||||||
import { sendConfigChange } from './rendererEvent'
|
import { sendConfigChange, sendMouseLeave } from './rendererEvent'
|
||||||
import { buildLyricConfig, getLyricWindowBounds, initWindowSize, watchConfigKeys } from './utils'
|
import { buildLyricConfig, getLyricWindowBounds, initWindowSize, watchConfigKeys } from './utils'
|
||||||
|
import { mouseCheckTools } from './mouseCheckTools'
|
||||||
|
|
||||||
let isLock: boolean
|
let isLock: boolean
|
||||||
let isEnable: boolean
|
let isEnable: boolean
|
||||||
@@ -21,14 +22,21 @@ export const setLrcConfig = (keys: Array<keyof LX.AppSetting>, setting: Partial<
|
|||||||
isLock = global.lx.appSetting['desktopLyric.isLock']
|
isLock = global.lx.appSetting['desktopLyric.isLock']
|
||||||
if (global.lx.appSetting['desktopLyric.isLock']) {
|
if (global.lx.appSetting['desktopLyric.isLock']) {
|
||||||
setIgnoreMouseEvents(true, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] })
|
setIgnoreMouseEvents(true, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] })
|
||||||
|
mouseCheckTools.runCheck(sendMouseLeave)
|
||||||
} else {
|
} else {
|
||||||
setIgnoreMouseEvents(false, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] })
|
setIgnoreMouseEvents(false, { forward: !isLinux && global.lx.appSetting['desktopLyric.isHoverHide'] })
|
||||||
|
mouseCheckTools.cacnelCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keys.includes('desktopLyric.isHoverHide') && isHoverHide != global.lx.appSetting['desktopLyric.isHoverHide']) {
|
if (keys.includes('desktopLyric.isHoverHide') && isHoverHide != global.lx.appSetting['desktopLyric.isHoverHide']) {
|
||||||
isHoverHide = global.lx.appSetting['desktopLyric.isHoverHide']
|
isHoverHide = global.lx.appSetting['desktopLyric.isHoverHide']
|
||||||
if (!isLinux) {
|
if (!isLinux) {
|
||||||
setIgnoreMouseEvents(global.lx.appSetting['desktopLyric.isLock'], { forward: global.lx.appSetting['desktopLyric.isHoverHide'] })
|
setIgnoreMouseEvents(global.lx.appSetting['desktopLyric.isLock'], { forward: isHoverHide })
|
||||||
|
if (isHoverHide) {
|
||||||
|
mouseCheckTools.runCheck(sendMouseLeave)
|
||||||
|
} else {
|
||||||
|
mouseCheckTools.cacnelCheck()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keys.includes('desktopLyric.isAlwaysOnTop') && isAlwaysOnTop != global.lx.appSetting['desktopLyric.isAlwaysOnTop']) {
|
if (keys.includes('desktopLyric.isAlwaysOnTop') && isAlwaysOnTop != global.lx.appSetting['desktopLyric.isAlwaysOnTop']) {
|
||||||
@@ -54,7 +62,7 @@ export const setLrcConfig = (keys: Array<keyof LX.AppSetting>, setting: Partial<
|
|||||||
if (keys.includes('desktopLyric.isLockScreen') && isLockScreen != global.lx.appSetting['desktopLyric.isLockScreen']) {
|
if (keys.includes('desktopLyric.isLockScreen') && isLockScreen != global.lx.appSetting['desktopLyric.isLockScreen']) {
|
||||||
isLockScreen = global.lx.appSetting['desktopLyric.isLockScreen']
|
isLockScreen = global.lx.appSetting['desktopLyric.isLockScreen']
|
||||||
if (global.lx.appSetting['desktopLyric.isLockScreen']) {
|
if (global.lx.appSetting['desktopLyric.isLockScreen']) {
|
||||||
setBounds(getLyricWindowBounds(getBounds(), {
|
setBounds(getLyricWindowBounds(getBounds()!, {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
w: global.lx.appSetting['desktopLyric.width'],
|
w: global.lx.appSetting['desktopLyric.width'],
|
||||||
@@ -77,6 +85,7 @@ export const setLrcConfig = (keys: Array<keyof LX.AppSetting>, setting: Partial<
|
|||||||
createWindow()
|
createWindow()
|
||||||
} else {
|
} else {
|
||||||
alwaysOnTopTools.clearLoop()
|
alwaysOnTopTools.clearLoop()
|
||||||
|
mouseCheckTools.cacnelCheck()
|
||||||
closeWindow()
|
closeWindow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,8 +172,8 @@ export const sendEvent = <T = any>(name: string, params?: T) => {
|
|||||||
mainSend(browserWindow, name, params)
|
mainSend(browserWindow, name, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBounds = (): Electron.Rectangle => {
|
export const getBounds = (): Electron.Rectangle | null => {
|
||||||
if (!browserWindow) throw new Error('window is not available')
|
if (!browserWindow) return null
|
||||||
return browserWindow.getBounds()
|
return browserWindow.getBounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { screen } from 'electron'
|
||||||
|
import { getBounds } from './main'
|
||||||
|
import { isLinux } from '@common/utils'
|
||||||
|
|
||||||
|
|
||||||
|
export const mouseCheckTools = {
|
||||||
|
timer: null as NodeJS.Timeout | null,
|
||||||
|
isMouseInWindow: false,
|
||||||
|
sendMouseLeave: null as (() => void) | null,
|
||||||
|
|
||||||
|
onInBoundsChange(isInBounds: boolean) {
|
||||||
|
if (isInBounds) return
|
||||||
|
this.stop()
|
||||||
|
this.sendMouseLeave?.()
|
||||||
|
},
|
||||||
|
|
||||||
|
checkMousePosition() {
|
||||||
|
const bounds = getBounds()
|
||||||
|
if (!bounds) return
|
||||||
|
const point = screen.getCursorScreenPoint()
|
||||||
|
const isInBounds = point.x >= bounds.x && point.x <= bounds.x + bounds.width &&
|
||||||
|
point.y >= bounds.y && point.y <= bounds.y + bounds.height
|
||||||
|
if (isInBounds !== this.isMouseInWindow) {
|
||||||
|
this.isMouseInWindow = isInBounds
|
||||||
|
this.onInBoundsChange(isInBounds)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.timer = null
|
||||||
|
this.checkMousePosition()
|
||||||
|
this.start()
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (!this.timer) return
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null
|
||||||
|
// console.log('stop check')
|
||||||
|
},
|
||||||
|
|
||||||
|
setMouseInWindow(isInWindow: boolean) {
|
||||||
|
this.isMouseInWindow = isInWindow
|
||||||
|
},
|
||||||
|
runCheck(sendMouseLeave: () => void) {
|
||||||
|
if (isLinux || !global.lx.appSetting['desktopLyric.isLock'] || !global.lx.appSetting['desktopLyric.isHoverHide'] || !this.isMouseInWindow) return
|
||||||
|
// console.log('run check')
|
||||||
|
this.sendMouseLeave = sendMouseLeave
|
||||||
|
this.stop()
|
||||||
|
this.start()
|
||||||
|
},
|
||||||
|
cacnelCheck() {
|
||||||
|
this.stop()
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { buildLyricConfig, getLyricWindowBounds } from './utils'
|
|||||||
import { sendNewDesktopLyricClient } from '@main/modules/winMain'
|
import { sendNewDesktopLyricClient } from '@main/modules/winMain'
|
||||||
import { getBounds, getMainFrame, sendEvent, setBounds, setResizeable } from './main'
|
import { getBounds, getMainFrame, sendEvent, setBounds, setResizeable } from './main'
|
||||||
import { MessageChannelMain } from 'electron'
|
import { MessageChannelMain } from 'electron'
|
||||||
|
import { mouseCheckTools } from './mouseCheckTools'
|
||||||
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
@@ -26,7 +27,7 @@ export default () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mainOn<LX.DesktopLyric.NewBounds>(WIN_LYRIC_RENDERER_EVENT_NAME.set_win_bounds, ({ params: options }) => {
|
mainOn<LX.DesktopLyric.NewBounds>(WIN_LYRIC_RENDERER_EVENT_NAME.set_win_bounds, ({ params: options }) => {
|
||||||
setBounds(getLyricWindowBounds(getBounds(), options))
|
setBounds(getLyricWindowBounds(getBounds()!, options))
|
||||||
})
|
})
|
||||||
|
|
||||||
mainOn<boolean>(WIN_LYRIC_RENDERER_EVENT_NAME.set_win_resizeable, ({ params: resizable }) => {
|
mainOn<boolean>(WIN_LYRIC_RENDERER_EVENT_NAME.set_win_resizeable, ({ params: resizable }) => {
|
||||||
@@ -45,6 +46,16 @@ export default () => {
|
|||||||
// without going through the main process!
|
// without going through the main process!
|
||||||
console.log('request_main_window_channel')
|
console.log('request_main_window_channel')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mainOn<boolean>(WIN_LYRIC_RENDERER_EVENT_NAME.mouse_enter_leave, ({ params: isEnter }) => {
|
||||||
|
if (isEnter) {
|
||||||
|
mouseCheckTools.setMouseInWindow(true)
|
||||||
|
mouseCheckTools.runCheck(sendMouseLeave)
|
||||||
|
} else {
|
||||||
|
mouseCheckTools.setMouseInWindow(false)
|
||||||
|
mouseCheckTools.cacnelCheck()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendConfigChange = (setting: Partial<LX.DesktopLyric.Config>) => {
|
export const sendConfigChange = (setting: Partial<LX.DesktopLyric.Config>) => {
|
||||||
@@ -55,3 +66,6 @@ export const sendMainWindowInitedEvent = () => {
|
|||||||
sendEvent(WIN_LYRIC_RENDERER_EVENT_NAME.main_window_inited)
|
sendEvent(WIN_LYRIC_RENDERER_EVENT_NAME.main_window_inited)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sendMouseLeave = () => {
|
||||||
|
sendEvent(WIN_LYRIC_RENDERER_EVENT_NAME.mouse_enter_leave, false)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { registerRendererEvents as list } from '@main/modules/commonRenderers/li
|
|||||||
import { registerRendererEvents as dislike } from '@main/modules/commonRenderers/dislike'
|
import { registerRendererEvents as dislike } from '@main/modules/commonRenderers/dislike'
|
||||||
import app, { sendConfigChange } from './app'
|
import app, { sendConfigChange } from './app'
|
||||||
import hotKey from './hotKey'
|
import hotKey from './hotKey'
|
||||||
import kw_decodeLyric from './kw_decodeLyric'
|
|
||||||
import tx_decodeLyric from './tx_decodeLyric'
|
|
||||||
import userApi from './userApi'
|
import userApi from './userApi'
|
||||||
import sync from './sync'
|
import sync from './sync'
|
||||||
import data from './data'
|
import data from './data'
|
||||||
@@ -30,8 +28,6 @@ export default () => {
|
|||||||
dislike(sendEvent)
|
dislike(sendEvent)
|
||||||
app()
|
app()
|
||||||
hotKey()
|
hotKey()
|
||||||
kw_decodeLyric()
|
|
||||||
tx_decodeLyric()
|
|
||||||
userApi()
|
userApi()
|
||||||
sync()
|
sync()
|
||||||
data()
|
data()
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import { inflate } from 'zlib'
|
|
||||||
import iconv from 'iconv-lite'
|
|
||||||
import { mainHandle } from '@common/mainIpc'
|
|
||||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
|
||||||
|
|
||||||
const handleInflate = async(data: Buffer) => {
|
|
||||||
return new Promise((resolve: (result: Buffer) => void, reject) => {
|
|
||||||
inflate(data, (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resolve(result)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const buf_key = Buffer.from('yeelion')
|
|
||||||
const buf_key_len = buf_key.length
|
|
||||||
|
|
||||||
const decodeLyric = async(buf: Buffer, isGetLyricx: boolean) => {
|
|
||||||
// const info = buf.slice(0, index).toString()
|
|
||||||
// if (!info.startsWith('tp=content')) return null
|
|
||||||
// const isLyric = info.includes('\r\nlrcx=0\r\n')
|
|
||||||
if (buf.toString('utf8', 0, 10) != 'tp=content') return ''
|
|
||||||
// const index = buf.indexOf('\r\n\r\n') + 4
|
|
||||||
const lrcData = await handleInflate(buf.subarray(buf.indexOf('\r\n\r\n') + 4))
|
|
||||||
|
|
||||||
if (!isGetLyricx) return iconv.decode(lrcData, 'gb18030')
|
|
||||||
|
|
||||||
const buf_str = Buffer.from(lrcData.toString(), 'base64')
|
|
||||||
const buf_str_len = buf_str.length
|
|
||||||
const output = new Uint8Array(buf_str_len)
|
|
||||||
let i = 0
|
|
||||||
while (i < buf_str_len) {
|
|
||||||
let j = 0
|
|
||||||
while (j < buf_key_len && i < buf_str_len) {
|
|
||||||
output[i] = buf_str[i] ^ buf_key[j]
|
|
||||||
i++
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return iconv.decode(Buffer.from(output), 'gb18030')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
mainHandle<{ lrcBase64: string, isGetLyricx: boolean }, string>(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, async({ params: { lrcBase64, isGetLyricx } }) => {
|
|
||||||
const lrc = await decodeLyric(Buffer.from(lrcBase64, 'base64'), isGetLyricx)
|
|
||||||
return Buffer.from(lrc).toString('base64')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { createInflate, constants as zlibConstants } from 'node:zlib'
|
|
||||||
// import path from 'path'
|
|
||||||
import { mainHandle } from '@common/mainIpc'
|
|
||||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/dot-notation, @typescript-eslint/quotes
|
|
||||||
// const require = module[`require`].bind(module)
|
|
||||||
|
|
||||||
let qrc_decode: (buf: Buffer, len: number) => Buffer
|
|
||||||
|
|
||||||
const inflate = async(lrcBuf: Buffer) => new Promise<string>((resolve, reject) => {
|
|
||||||
const buffer_builder: Buffer[] = []
|
|
||||||
const decompress_stream = createInflate()
|
|
||||||
.on('data', (chunk) => {
|
|
||||||
buffer_builder.push(chunk)
|
|
||||||
})
|
|
||||||
.on('close', () => {
|
|
||||||
resolve(Buffer.concat(buffer_builder).toString())
|
|
||||||
})
|
|
||||||
.on('error', (err: any) => {
|
|
||||||
// console.log(err)
|
|
||||||
if (err.errno !== zlibConstants.Z_BUF_ERROR) { // EOF: expected
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// decompress_stream.write(lrcBuf)
|
|
||||||
decompress_stream.end(lrcBuf)
|
|
||||||
})
|
|
||||||
|
|
||||||
const decode = async(str: string): Promise<string> => {
|
|
||||||
if (!str) return ''
|
|
||||||
const buf = Buffer.from(str, 'hex')
|
|
||||||
return inflate(qrc_decode(buf, buf.length))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 感谢某位不愿透露姓名的大佬提供的C++算法源码,但由于作者不希望公开,所以将会以预构建二进制文件的形式加入代码仓库中
|
|
||||||
const handleDecode = async(lrc: string, tlrc: string, rlrc: string) => {
|
|
||||||
if (!qrc_decode) {
|
|
||||||
// const nativeBindingPath = path.join(__dirname, '../build/Release/qrc_decode.node')
|
|
||||||
// const nativeBindingPath = process.env.NODE_ENV !== 'production' ? path.join(__dirname, '../build/Release/qrc_decode.node')
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const addon = require('qrc_decode.node')
|
|
||||||
// console.log(addon)
|
|
||||||
qrc_decode = addon.qrc_decode
|
|
||||||
}
|
|
||||||
|
|
||||||
const [lyric, tlyric, rlyric] = await Promise.all([decode(lrc), decode(tlrc), decode(rlrc)])
|
|
||||||
return {
|
|
||||||
lyric,
|
|
||||||
tlyric,
|
|
||||||
rlyric,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
mainHandle<{ lrc: string, tlrc: string, rlrc: string }, { lyric: string, tlyric: string, rlyric: string }>(WIN_MAIN_RENDERER_EVENT_NAME.handle_tx_decode_lyric, async({ params: { lrc, tlrc, rlrc } }) => {
|
|
||||||
return handleDecode(lrc, tlrc, rlrc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -5,10 +5,10 @@
|
|||||||
"./types"
|
"./types"
|
||||||
],
|
],
|
||||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
"@common/*": ["common/*"],
|
"@common/*": ["../common/*"],
|
||||||
"@main/*": ["main/*"],
|
"@main/*": ["../main/*"],
|
||||||
"@static/*": ["static/*"],
|
"@static/*": ["../static/*"],
|
||||||
"@/*": ["./*"],
|
"@/*": ["../*"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { encodePath, isUrl, throttle } from '@common/utils'
|
import { encodePath, isUrl, throttle, isMac } from '@common/utils'
|
||||||
import migrateSetting from '@common/utils/migrateSetting'
|
import migrateSetting from '@common/utils/migrateSetting'
|
||||||
import getStore from '@main/utils/store'
|
import getStore from '@main/utils/store'
|
||||||
import { STORE_NAMES, URL_SCHEME_RXP } from '@common/constants'
|
import { STORE_NAMES, URL_SCHEME_RXP } from '@common/constants'
|
||||||
@@ -293,7 +293,7 @@ export const setPowerSaveBlocker = (enabled: boolean) => {
|
|||||||
let isEnabled = powerSaveBlockerId != null && powerSaveBlocker.isStarted(powerSaveBlockerId)
|
let isEnabled = powerSaveBlockerId != null && powerSaveBlocker.isStarted(powerSaveBlockerId)
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (isEnabled) return
|
if (isEnabled) return
|
||||||
powerSaveBlockerId = powerSaveBlocker.start('prevent-app-suspension')
|
powerSaveBlockerId = powerSaveBlocker.start(isMac ? 'prevent-display-sleep' : 'prevent-app-suspension')
|
||||||
} else {
|
} else {
|
||||||
if (!isEnabled) return
|
if (!isEnabled) return
|
||||||
powerSaveBlocker.stop(powerSaveBlockerId!)
|
powerSaveBlocker.stop(powerSaveBlockerId!)
|
||||||
|
|||||||
+4
-131
@@ -1,8 +1,5 @@
|
|||||||
import needle, { type NeedleHttpVerbs, type NeedleOptions, type BodyData, type NeedleCallback, type NeedleResponse } from 'needle'
|
|
||||||
// import progress from 'request-progress'
|
// import progress from 'request-progress'
|
||||||
import { httpOverHttp, httpsOverHttp } from 'tunnel'
|
import { request, type Options } from '@common/utils/request'
|
||||||
import { type ClientRequest } from 'node:http'
|
|
||||||
import { getProxy } from './index'
|
|
||||||
// import fs from 'fs'
|
// import fs from 'fs'
|
||||||
|
|
||||||
export const requestMsg = {
|
export const requestMsg = {
|
||||||
@@ -14,53 +11,6 @@ export const requestMsg = {
|
|||||||
cancelRequest: '取消http请求',
|
cancelRequest: '取消http请求',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
|
||||||
const httpsRxp = /^https:/
|
|
||||||
const getRequestAgent = (url: string) => {
|
|
||||||
const proxy = getProxy()
|
|
||||||
return proxy ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)({ proxy }) : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestOptions extends NeedleOptions {
|
|
||||||
method?: NeedleHttpVerbs
|
|
||||||
body?: BodyData
|
|
||||||
form?: BodyData
|
|
||||||
formData?: BodyData
|
|
||||||
}
|
|
||||||
export type RequestCallback = NeedleCallback
|
|
||||||
type RequestResponse = NeedleResponse
|
|
||||||
const request = (url: string, options: RequestOptions, callback: RequestCallback): ClientRequest => {
|
|
||||||
let data: BodyData = null
|
|
||||||
if (options.body) {
|
|
||||||
data = options.body
|
|
||||||
} else if (options.form) {
|
|
||||||
data = options.form
|
|
||||||
// data.content_type = 'application/x-www-form-urlencoded'
|
|
||||||
options.json = false
|
|
||||||
} else if (options.formData) {
|
|
||||||
data = options.formData
|
|
||||||
// data.content_type = 'multipart/form-data'
|
|
||||||
options.json = false
|
|
||||||
}
|
|
||||||
options.response_timeout = options.timeout
|
|
||||||
|
|
||||||
return needle.request(options.method ?? 'get', url, data, options, (err, resp, body) => {
|
|
||||||
if (!err) {
|
|
||||||
body = resp.body = resp.raw.toString()
|
|
||||||
try {
|
|
||||||
resp.body = JSON.parse(resp.body)
|
|
||||||
} catch (_) {}
|
|
||||||
body = resp.body
|
|
||||||
}
|
|
||||||
callback(err, resp, body)
|
|
||||||
// @ts-expect-error
|
|
||||||
}).request
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const defaultHeaders = {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
|
|
||||||
}
|
|
||||||
// var proxyUrl = "http://" + user + ":" + password + "@" + host + ":" + port;
|
// var proxyUrl = "http://" + user + ":" + password + "@" + host + ":" + port;
|
||||||
// var proxiedRequest = request.defaults({'proxy': proxyUrl});
|
// var proxiedRequest = request.defaults({'proxy': proxyUrl});
|
||||||
|
|
||||||
@@ -68,70 +18,13 @@ const defaultHeaders = {
|
|||||||
// abort: () => void
|
// abort: () => void
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
|
||||||
* promise 形式的请求方法
|
|
||||||
* @param {*} url
|
|
||||||
* @param {*} options
|
|
||||||
*/
|
|
||||||
const buildHttpPromose = async(url: string, options: RequestOptions): Promise<RequestResponse> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
void fetchData(url, options.method, options, (err, resp, body) => {
|
|
||||||
// options.isShowProgress && window.api.hideProgress()
|
|
||||||
// debugRequest && console.log(`\n---response------${url}------------`)
|
|
||||||
// debugRequest && console.log(body)
|
|
||||||
// obj.requestObj = null
|
|
||||||
// obj.cancelFn = null
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resolve(resp)
|
|
||||||
})
|
|
||||||
// .then(request => {
|
|
||||||
// // obj.requestObj = ro
|
|
||||||
// // if (obj.isCancelled) obj.cancelHttp()
|
|
||||||
// promise.abort = () => {
|
|
||||||
// request.destroy(new Error('cancelled'))
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
// let obj = {
|
|
||||||
// isCancelled: false,
|
|
||||||
// cancelHttp: () => {
|
|
||||||
// if (!obj.requestObj) return obj.isCancelled = true
|
|
||||||
// cancelHttp(obj.requestObj)
|
|
||||||
// obj.requestObj = null
|
|
||||||
// obj.promise = obj.cancelHttp = null
|
|
||||||
// obj.cancelFn(new Error(requestMsg.cancelRequest))
|
|
||||||
// obj.cancelFn = null
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// obj.promise = new Promise((resolve, reject) => {
|
|
||||||
// obj.cancelFn = reject
|
|
||||||
// debugRequest && console.log(`\n---send request------${url}------------`)
|
|
||||||
// fetchData(url, options.method, options, (err, resp, body) => {
|
|
||||||
// // options.isShowProgress && window.api.hideProgress()
|
|
||||||
// debugRequest && console.log(`\n---response------${url}------------`)
|
|
||||||
// debugRequest && console.log(body)
|
|
||||||
// obj.requestObj = null
|
|
||||||
// obj.cancelFn = null
|
|
||||||
// if (err) { reject(err); return }
|
|
||||||
// resolve(resp)
|
|
||||||
// }).then(ro => {
|
|
||||||
// obj.requestObj = ro
|
|
||||||
// if (obj.isCancelled) obj.cancelHttp()
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求超时自动重试
|
* 请求超时自动重试
|
||||||
* @param {*} url
|
* @param {*} url
|
||||||
* @param {*} options
|
* @param {*} options
|
||||||
*/
|
*/
|
||||||
export const httpFetch = async(url: string, options: RequestOptions = { method: 'get' }) => {
|
export const httpFetch = async<T = unknown> (url: string, options: Options) => {
|
||||||
return buildHttpPromose(url, options).catch(async(err: any) => {
|
return request<T>(url, options).catch(async(err: any) => {
|
||||||
// console.log('出错', err)
|
// console.log('出错', err)
|
||||||
if (err.message === 'socket hang up') {
|
if (err.message === 'socket hang up') {
|
||||||
// window.globalObj.apiSource = 'temp'
|
// window.globalObj.apiSource = 'temp'
|
||||||
@@ -166,24 +59,4 @@ export const httpFetch = async(url: string, options: RequestOptions = { method:
|
|||||||
// return requestPromise
|
// return requestPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = async(url: string, method: RequestOptions['method'], {
|
export type RequestOptions = Options
|
||||||
headers = {},
|
|
||||||
format = 'json',
|
|
||||||
timeout = 15000,
|
|
||||||
...options
|
|
||||||
}, callback: RequestCallback) => {
|
|
||||||
// console.log(url, options)
|
|
||||||
console.log('---start---', url)
|
|
||||||
headers = Object.assign({}, headers)
|
|
||||||
|
|
||||||
return request(url, {
|
|
||||||
...options,
|
|
||||||
method,
|
|
||||||
headers: Object.assign({}, defaultHeaders, headers),
|
|
||||||
timeout,
|
|
||||||
agent: getRequestAgent(url),
|
|
||||||
json: format === 'json',
|
|
||||||
}, (err, resp, body) => {
|
|
||||||
callback(err, resp, body)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
createMusicInfoOrderClearStatement,
|
createMusicInfoOrderClearStatement,
|
||||||
createMusicInfoByListAndMusicInfoIdQueryStatement,
|
createMusicInfoByListAndMusicInfoIdQueryStatement,
|
||||||
createMusicInfoByMusicInfoIdQueryStatement,
|
createMusicInfoByMusicInfoIdQueryStatement,
|
||||||
|
createMusicInfoOrderStatement,
|
||||||
} from './statements'
|
} from './statements'
|
||||||
|
|
||||||
const idFixRxp = /\.0$/
|
const idFixRxp = /\.0$/
|
||||||
@@ -368,3 +369,14 @@ export const overwriteListData = (lists: LX.DBService.UserListInfo[], musicInfos
|
|||||||
})(lists, musicInfos)
|
})(lists, musicInfos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取列表内音乐的排序
|
||||||
|
* @param listId 列表id
|
||||||
|
* @param musicInfoId 音乐id
|
||||||
|
* @returns 音乐排序信息
|
||||||
|
*/
|
||||||
|
export const getMusicInfoOrder = (listId: string, musicInfoId: string) => {
|
||||||
|
const musicInfoOrderStatement = createMusicInfoOrderStatement()
|
||||||
|
return musicInfoOrderStatement.get({ listId, musicInfoId })
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ import {
|
|||||||
updateMusicInfoOrder,
|
updateMusicInfoOrder,
|
||||||
updateMusicInfos,
|
updateMusicInfos,
|
||||||
updateUserLists as updateUserListsFromDB,
|
updateUserLists as updateUserListsFromDB,
|
||||||
|
getMusicInfoOrder,
|
||||||
} from './dbHelper'
|
} from './dbHelper'
|
||||||
|
|
||||||
let userLists: LX.DBService.UserListInfo[]
|
let userLists: LX.DBService.UserListInfo[]
|
||||||
let musicLists = new Map<string, LX.Music.MusicInfo[]>()
|
let musicLists = new Map<string, LX.Music.MusicInfo[]>()
|
||||||
|
let rawPoss = new Map<string, number>()
|
||||||
|
|
||||||
const toDBMusicInfo = (musicInfos: LX.Music.MusicInfo[], listId: string, offset: number = 0): LX.DBService.MusicInfo[] => {
|
const toDBMusicInfo = (musicInfos: LX.Music.MusicInfo[], listId: string, offset: number = 0): LX.DBService.MusicInfo[] => {
|
||||||
return musicInfos.map((info, index) => {
|
return musicInfos.map((info, index) => {
|
||||||
@@ -41,8 +43,10 @@ const toDBMusicInfo = (musicInfos: LX.Music.MusicInfo[], listId: string, offset:
|
|||||||
export const getAllUserList = (): LX.List.UserListInfo[] => {
|
export const getAllUserList = (): LX.List.UserListInfo[] => {
|
||||||
userLists ??= queryAllUserList()
|
userLists ??= queryAllUserList()
|
||||||
|
|
||||||
|
rawPoss.clear()
|
||||||
return userLists.map(list => {
|
return userLists.map(list => {
|
||||||
const { position, ...newList } = list
|
const { position, ...newList } = list
|
||||||
|
rawPoss.set(list.id, position)
|
||||||
return newList
|
return newList
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -55,10 +59,15 @@ export const getAllUserList = (): LX.List.UserListInfo[] => {
|
|||||||
export const createUserLists = (position: number, lists: LX.List.UserListInfo[]) => {
|
export const createUserLists = (position: number, lists: LX.List.UserListInfo[]) => {
|
||||||
userLists ??= queryAllUserList()
|
userLists ??= queryAllUserList()
|
||||||
if (position < 0 || position >= userLists.length) {
|
if (position < 0 || position >= userLists.length) {
|
||||||
|
// 如果是最末尾,那么取最后一个列表的原始位置 + 1 作为新列表的原始位置,否则新列表的原始位置为 position
|
||||||
|
// 因为原始 pos 可能比 userLists.length 大,所以不能直接用 userLists.length 作为新列表的原始位置
|
||||||
|
const order = userLists.length ? (rawPoss.get(userLists.at(-1)!.id) ?? userLists.length) + 1 : 0
|
||||||
const newLists: LX.DBService.UserListInfo[] = lists.map((list, index) => {
|
const newLists: LX.DBService.UserListInfo[] = lists.map((list, index) => {
|
||||||
|
const pos = order + index
|
||||||
|
rawPoss.set(list.id, pos)
|
||||||
return {
|
return {
|
||||||
...list,
|
...list,
|
||||||
position: position + index,
|
position: pos,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
insertUserLists(newLists)
|
insertUserLists(newLists)
|
||||||
@@ -72,6 +81,8 @@ export const createUserLists = (position: number, lists: LX.List.UserListInfo[])
|
|||||||
})
|
})
|
||||||
insertUserLists(newUserLists, true)
|
insertUserLists(newUserLists, true)
|
||||||
userLists = newUserLists
|
userLists = newUserLists
|
||||||
|
rawPoss.clear()
|
||||||
|
for (const list of userLists) rawPoss.set(list.id, list.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +108,7 @@ export const createUserLists = (position: number, lists: LX.List.UserListInfo[])
|
|||||||
export const removeUserLists = (ids: string[]) => {
|
export const removeUserLists = (ids: string[]) => {
|
||||||
deleteUserLists(ids)
|
deleteUserLists(ids)
|
||||||
userLists &&= queryAllUserList()
|
userLists &&= queryAllUserList()
|
||||||
|
for (const id of ids) rawPoss.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,6 +159,8 @@ export const updateUserListsPosition = (position: number, ids: string[]) => {
|
|||||||
})
|
})
|
||||||
insertUserLists(newUserLists, true)
|
insertUserLists(newUserLists, true)
|
||||||
userLists = newUserLists
|
userLists = newUserLists
|
||||||
|
rawPoss.clear()
|
||||||
|
for (const list of userLists) rawPoss.set(list.id, list.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,11 +224,15 @@ export const musicsAdd = (listId: string, musicInfos: LX.Music.MusicInfo[], addM
|
|||||||
arrUnshift(targetList, musicInfos)
|
arrUnshift(targetList, musicInfos)
|
||||||
break
|
break
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
default:
|
default: {
|
||||||
insertMusicInfoList(toDBMusicInfo(musicInfos, listId, targetList.length))
|
// 如果是添加到最后,那么取最后一个歌曲的原始位置 + 1 作为新歌曲的原始位置,否则新歌曲的原始位置为 position
|
||||||
|
// 因为原始 pos 可能比 targetList.length 大,所以不能直接用 targetList.length 作为新歌曲的原始位置
|
||||||
|
const order = targetList.length ? (getMusicInfoOrder(listId, targetList.at(-1)!.id)?.order ?? targetList.length) + 1 : 0
|
||||||
|
insertMusicInfoList(toDBMusicInfo(musicInfos, listId, order))
|
||||||
arrPush(targetList, musicInfos)
|
arrPush(targetList, musicInfos)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,6 +381,8 @@ export const listDataOverwrite = (myListData: MakeOptional<LX.List.ListDataFull,
|
|||||||
if (userLists) userLists.splice(0, userLists.length, ...dbLists)
|
if (userLists) userLists.splice(0, userLists.length, ...dbLists)
|
||||||
else userLists = dbLists
|
else userLists = dbLists
|
||||||
|
|
||||||
|
rawPoss.clear()
|
||||||
|
for (const list of userLists) rawPoss.set(list.id, list.position)
|
||||||
musicLists.clear()
|
musicLists.clear()
|
||||||
musicLists.set(LIST_IDS.DEFAULT, listData.defaultList)
|
musicLists.set(LIST_IDS.DEFAULT, listData.defaultList)
|
||||||
musicLists.set(LIST_IDS.LOVE, listData.loveList)
|
musicLists.set(LIST_IDS.LOVE, listData.loveList)
|
||||||
|
|||||||
@@ -145,6 +145,17 @@ export const createMusicInfoByMusicInfoIdQueryStatement = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建根据列表Id与音乐id查询音乐排序语句
|
||||||
|
* @returns 查询语句
|
||||||
|
*/
|
||||||
|
export const createMusicInfoOrderStatement = () => {
|
||||||
|
const db = getDB()
|
||||||
|
return db.prepare<[LX.DBService.ListMusicInfoQuery], LX.DBService.MusicInfoOrder>(`SELECT "listId", "musicInfoId", "order"
|
||||||
|
FROM "main"."my_list_music_info_order"
|
||||||
|
WHERE "listId"=@listId AND "musicInfoId"=@musicInfoId`)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建音乐信息排序插入语句
|
* 创建音乐信息排序插入语句
|
||||||
* @returns 插入语句
|
* @returns 插入语句
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="container" :class="[{ lock: setting['desktopLyric.isLock'] }, { hide: isHide || (isHoverHide && isMouseEnter) }]">
|
<div id="container" :class="[{ lock: setting['desktopLyric.isLock'] }, { hide: isHide || isHoverHide }]">
|
||||||
<div id="main" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave" @mousemove="handleMouseMoveMain">
|
<div id="main">
|
||||||
<transition enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut">
|
<transition enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut">
|
||||||
<div v-show="!setting['desktopLyric.isLock']" class="control-bar">
|
<div v-show="!setting['desktopLyric.isLock']" class="control-bar">
|
||||||
<layout-control-bar />
|
<layout-control-bar />
|
||||||
@@ -41,7 +41,7 @@ import usePauseHide from '@lyric/useApp/usePauseHide'
|
|||||||
const isShowResize = window.os != 'windows'
|
const isShowResize = window.os != 'windows'
|
||||||
useCommon()
|
useCommon()
|
||||||
const { handleMouseDown, handleTouchDown } = useWindowSize()
|
const { handleMouseDown, handleTouchDown } = useWindowSize()
|
||||||
const { handleMouseMoveMain, isHoverHide, isMouseEnter } = useHoverHide()
|
const isHoverHide = useHoverHide()
|
||||||
useLyric()
|
useLyric()
|
||||||
useTheme()
|
useTheme()
|
||||||
const isHide = usePauseHide()
|
const isHide = usePauseHide()
|
||||||
@@ -87,7 +87,8 @@ body {
|
|||||||
}
|
}
|
||||||
&.hide {
|
&.hide {
|
||||||
opacity: .05;
|
opacity: .05;
|
||||||
&:hover {
|
|
||||||
|
&:not(.lock):hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
"@common/*": ["common/*"],
|
"@common/*": ["../common/*"],
|
||||||
// "@renderer/*": ["renderer/*"],
|
// "@renderer/*": ["renderer/*"],
|
||||||
"@lyric/*": ["renderer-lyric/*"],
|
"@lyric/*": ["../renderer-lyric/*"],
|
||||||
"@static/*": ["static/*"],
|
"@static/*": ["../static/*"],
|
||||||
"@root/*": ["./*"],
|
"@root/*": ["../*"],
|
||||||
},
|
},
|
||||||
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
"./types"
|
"./types"
|
||||||
|
|||||||
@@ -1,98 +1,41 @@
|
|||||||
import { computed, ref } from '@common/utils/vueTools'
|
import { computed, ref, onBeforeUnmount } from '@common/utils/vueTools'
|
||||||
import { setting } from '@lyric/store/state'
|
import { setting } from '@lyric/store/state'
|
||||||
|
import { sendMouseEnterLeave, onMouseEnterLeave } from '@lyric/utils/ipc'
|
||||||
let mouseCheckTools: {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
preX: number
|
|
||||||
preY: number
|
|
||||||
timeout: NodeJS.Timeout | null
|
|
||||||
handleCheck: (setShow: () => void) => void
|
|
||||||
handleMove: (x: number, y: number, setShow: () => void) => void
|
|
||||||
startTimeout: (setShow: () => void) => void
|
|
||||||
stopTimeout: () => void
|
|
||||||
} = {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
preX: 0,
|
|
||||||
preY: 0,
|
|
||||||
timeout: null,
|
|
||||||
handleCheck(setShow: () => void) {
|
|
||||||
let xDiff = Math.abs(this.x - this.preX)
|
|
||||||
let yDiff = Math.abs(this.y - this.preY)
|
|
||||||
if (xDiff > 8) {
|
|
||||||
if (this.x > this.preX) {
|
|
||||||
if (this.x + xDiff * 1.25 > window.innerWidth - 16) {
|
|
||||||
setShow()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.x - xDiff * 1.25 < 8) {
|
|
||||||
setShow()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (yDiff > 8) {
|
|
||||||
if (this.y > this.preY) {
|
|
||||||
if (this.y + yDiff * 1.25 > window.innerHeight - 16) {
|
|
||||||
setShow()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.y - yDiff * 1.25 < 8) {
|
|
||||||
setShow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setShow(false)
|
|
||||||
},
|
|
||||||
handleMove(x: number, y: number, setShow: () => void) {
|
|
||||||
// console.log(x, y, this.x, this.y)
|
|
||||||
this.preX = this.x
|
|
||||||
this.preY = this.y
|
|
||||||
this.x = x
|
|
||||||
this.y = y
|
|
||||||
this.startTimeout(setShow)
|
|
||||||
},
|
|
||||||
startTimeout(setShow: () => void) {
|
|
||||||
this.stopTimeout()
|
|
||||||
this.timeout = setTimeout(this.handleCheck.bind(this), 200, setShow)
|
|
||||||
},
|
|
||||||
stopTimeout() {
|
|
||||||
if (!this.timeout) return
|
|
||||||
clearTimeout(this.timeout)
|
|
||||||
this.timeout = null
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const isMouseEnter = ref(false)
|
const isMouseEnter = ref(false)
|
||||||
|
|
||||||
const isHoverHide = computed(() => {
|
const isHoverHide = computed(() => {
|
||||||
return setting['desktopLyric.isLock'] && setting['desktopLyric.isHoverHide']
|
return setting['desktopLyric.isHoverHide'] && isMouseEnter.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleMouseMoveMain = (event: MouseEvent) => {
|
const handleMouseMove = () => {
|
||||||
if (!isHoverHide.value) return
|
|
||||||
handleMouseEnter()
|
handleMouseEnter()
|
||||||
mouseCheckTools.handleMove(event.clientX, event.clientY, () => {
|
|
||||||
handleMouseLeave()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (!isHoverHide.value || isMouseEnter.value) return
|
if (isMouseEnter.value) return
|
||||||
isMouseEnter.value = true
|
isMouseEnter.value = true
|
||||||
|
sendMouseEnterLeave(true)
|
||||||
}
|
}
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
if (!isHoverHide.value) return
|
if (!isMouseEnter.value) return
|
||||||
isMouseEnter.value = false
|
isMouseEnter.value = false
|
||||||
mouseCheckTools.stopTimeout()
|
sendMouseEnterLeave(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const removeMouseEnterLeaveListener = onMouseEnterLeave(({ params: isEnter }) => {
|
||||||
isMouseEnter,
|
isMouseEnter.value = isEnter
|
||||||
isHoverHide,
|
})
|
||||||
handleMouseMoveMain,
|
document.body.addEventListener('mousemove', handleMouseMove)
|
||||||
}
|
document.body.addEventListener('mouseenter', handleMouseEnter)
|
||||||
|
document.body.addEventListener('mouseleave', handleMouseLeave)
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
removeMouseEnterLeaveListener()
|
||||||
|
document.body.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.body.removeEventListener('mouseenter', handleMouseEnter)
|
||||||
|
document.body.removeEventListener('mouseleave', handleMouseLeave)
|
||||||
|
})
|
||||||
|
|
||||||
|
return isHoverHide
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,16 @@ export const onMainWindowInited = (listener: LX.IpcRendererEventListener): Remov
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sendMouseEnterLeave = (isEnter: boolean) => {
|
||||||
|
rendererSend(WIN_LYRIC_RENDERER_EVENT_NAME.mouse_enter_leave, isEnter)
|
||||||
|
}
|
||||||
|
export const onMouseEnterLeave = (listener: LX.IpcRendererEventListenerParams<boolean>): RemoveListener => {
|
||||||
|
rendererOn<boolean>(WIN_LYRIC_RENDERER_EVENT_NAME.mouse_enter_leave, listener)
|
||||||
|
return () => {
|
||||||
|
rendererOff(WIN_LYRIC_RENDERER_EVENT_NAME.mouse_enter_leave, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On Theme Change
|
* On Theme Change
|
||||||
* @param listener LX.IpcRendererEventListenerParams<shouldUseDarkColors: boolean>
|
* @param listener LX.IpcRendererEventListenerParams<shouldUseDarkColors: boolean>
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import {
|
|||||||
import { appSetting } from '@renderer/store/setting'
|
import { appSetting } from '@renderer/store/setting'
|
||||||
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
||||||
import { LIST_IDS } from '@common/constants'
|
import { LIST_IDS } from '@common/constants'
|
||||||
|
import { formatMusicName } from '@renderer/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CorePlayBar',
|
name: 'CorePlayBar',
|
||||||
@@ -112,7 +113,7 @@ export default {
|
|||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
return musicInfo.name
|
return musicInfo.name
|
||||||
? appSetting['download.fileName'].replace('歌名', musicInfo.name).replace('歌手', musicInfo.singer)
|
? formatMusicName(appSetting['download.fileName'], musicInfo.name, musicInfo.singer)
|
||||||
: ''
|
: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import {
|
|||||||
import { appSetting } from '@renderer/store/setting'
|
import { appSetting } from '@renderer/store/setting'
|
||||||
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
||||||
import { LIST_IDS } from '@common/constants'
|
import { LIST_IDS } from '@common/constants'
|
||||||
|
import { formatMusicName } from '@renderer/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CorePlayBar',
|
name: 'CorePlayBar',
|
||||||
@@ -112,7 +113,7 @@ export default {
|
|||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
return musicInfo.name
|
return musicInfo.name
|
||||||
? appSetting['download.fileName'].replace('歌名', musicInfo.name).replace('歌手', musicInfo.singer)
|
? formatMusicName(appSetting['download.fileName'], musicInfo.name, musicInfo.singer)
|
||||||
: ''
|
: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import {
|
|||||||
import { appSetting } from '@renderer/store/setting'
|
import { appSetting } from '@renderer/store/setting'
|
||||||
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
import { togglePlay, playNext, playPrev } from '@renderer/core/player'
|
||||||
import { LIST_IDS } from '@common/constants'
|
import { LIST_IDS } from '@common/constants'
|
||||||
|
import { formatMusicName } from '@renderer/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CorePlayBar',
|
name: 'CorePlayBar',
|
||||||
@@ -114,7 +115,7 @@ export default {
|
|||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
return musicInfo.name
|
return musicInfo.name
|
||||||
? appSetting['download.fileName'].replace('歌名', musicInfo.name).replace('歌手', musicInfo.singer)
|
? formatMusicName(appSetting['download.fileName'], musicInfo.name, musicInfo.singer)
|
||||||
: ''
|
: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ export default {
|
|||||||
// border-bottom: 2px solid var(--color-primary);
|
// border-bottom: 2px solid var(--color-primary);
|
||||||
// border-color: var(--color-primary);
|
// border-color: var(--color-primary);
|
||||||
border: none;
|
border: none;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
outline: none;
|
outline: none;
|
||||||
// height: @height-toolbar * .7;
|
// height: @height-toolbar * .7;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ let convolverDynamicsCompressor: DynamicsCompressorNode
|
|||||||
let gainNode: GainNode
|
let gainNode: GainNode
|
||||||
let panner: PannerNode
|
let panner: PannerNode
|
||||||
let pitchShifterNode: AudioWorkletNode
|
let pitchShifterNode: AudioWorkletNode
|
||||||
let pitchShifterNodePitchFactor: AudioParam | null
|
let pitchShifterNodePitchFactor: AudioParam
|
||||||
let pitchShifterNodeLoadStatus: 'none' | 'loading' | 'unconnect' | 'connected' = 'none'
|
let pitchShifterNodeLoadStatus: 'none' | 'loading' | 'unconnect' | 'connected' = 'none'
|
||||||
let pitchShifterNodeTempValue = 1
|
let pitchShifterNodeTempValue = 1
|
||||||
let defaultChannelCount = 2
|
let defaultChannelCount = 2
|
||||||
@@ -65,6 +65,16 @@ export const createAudio = () => {
|
|||||||
audio.autoplay = true
|
audio.autoplay = true
|
||||||
audio.preload = 'auto'
|
audio.preload = 'auto'
|
||||||
audio.crossOrigin = 'anonymous'
|
audio.crossOrigin = 'anonymous'
|
||||||
|
|
||||||
|
// https://developer.chrome.com/blog/autoplay
|
||||||
|
audio.addEventListener('playing', () => {
|
||||||
|
if (audioContext?.state == 'suspended') {
|
||||||
|
void audioContext.resume().catch((err) => {
|
||||||
|
console.error('Resume audio context failed:', err)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const initAnalyser = () => {
|
const initAnalyser = () => {
|
||||||
@@ -322,7 +332,7 @@ const connectPitchShifterNode = () => {
|
|||||||
// convolverDynamicsCompressor.connect(pitchShifterNode)
|
// convolverDynamicsCompressor.connect(pitchShifterNode)
|
||||||
// pitchShifterNode.connect(panner)
|
// pitchShifterNode.connect(panner)
|
||||||
pitchShifterNodeLoadStatus = 'connected'
|
pitchShifterNodeLoadStatus = 'connected'
|
||||||
pitchShifterNodePitchFactor!.value = pitchShifterNodeTempValue
|
pitchShifterNodePitchFactor.value = pitchShifterNodeTempValue
|
||||||
}
|
}
|
||||||
const disconnectPitchShifterNode = () => {
|
const disconnectPitchShifterNode = () => {
|
||||||
console.log('disconnect Pitch Shifter Node')
|
console.log('disconnect Pitch Shifter Node')
|
||||||
@@ -331,7 +341,6 @@ const disconnectPitchShifterNode = () => {
|
|||||||
lastBiquadFilter.connect(convolver)
|
lastBiquadFilter.connect(convolver)
|
||||||
lastBiquadFilter.connect(convolverSourceGainNode)
|
lastBiquadFilter.connect(convolverSourceGainNode)
|
||||||
pitchShifterNodeLoadStatus = 'unconnect'
|
pitchShifterNodeLoadStatus = 'unconnect'
|
||||||
pitchShifterNodePitchFactor = null
|
|
||||||
|
|
||||||
audio!.removeEventListener('playing', connectNode)
|
audio!.removeEventListener('playing', connectNode)
|
||||||
audio!.removeEventListener('pause', disconnectNode)
|
audio!.removeEventListener('pause', disconnectNode)
|
||||||
@@ -373,7 +382,7 @@ export const setPitchShifter = (val: number) => {
|
|||||||
case 'connected':
|
case 'connected':
|
||||||
// a: 1 = 半音
|
// a: 1 = 半音
|
||||||
// value = 2 ** (a / 12)
|
// value = 2 ** (a / 12)
|
||||||
pitchShifterNodePitchFactor!.value = val
|
pitchShifterNodePitchFactor.value = val
|
||||||
break
|
break
|
||||||
case 'unconnect':
|
case 'unconnect':
|
||||||
connectPitchShifterNode()
|
connectPitchShifterNode()
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
"@common/*": ["common/*"],
|
"@common/*": ["../common/*"],
|
||||||
"@renderer/*": ["renderer/*"],
|
"@renderer/*": ["../renderer/*"],
|
||||||
// "@lyric/*": ["renderer-lyric/*"],
|
// "@lyric/*": ["../renderer-lyric/*"],
|
||||||
"@static/*": ["static/*"],
|
"@static/*": ["../static/*"],
|
||||||
"@root/*": ["./*"],
|
"@root/*": ["../*"],
|
||||||
},
|
},
|
||||||
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
"./types"
|
"./types"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { checkPath, joinPath, extname, basename, readFile, getFileStats } from '@common/utils/nodejs'
|
import { checkPath, joinPath, extname, basename, readFile, getFileStats } from '@common/utils/nodejs'
|
||||||
import { formatPlayTime } from '@common/utils/common'
|
import { formatPlayTime } from '@common/utils/common'
|
||||||
import type { IComment } from 'music-metadata/lib/type'
|
|
||||||
import { decodeKrc } from '@common/utils/lyricUtils/kg'
|
import { decodeKrc } from '@common/utils/lyricUtils/kg'
|
||||||
|
import { type IAudioMetadata } from 'music-metadata'
|
||||||
|
|
||||||
export const checkDownloadFileAvailable = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<boolean> => {
|
export const checkDownloadFileAvailable = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<boolean> => {
|
||||||
return musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName) &&
|
return musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName) &&
|
||||||
@@ -164,6 +164,7 @@ export const getLocalMusicFilePic = async(path: string) => {
|
|||||||
// return lyricInfo
|
// return lyricInfo
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
type IComment = NonNullable<IAudioMetadata['common']['comment']> extends Array<infer U> ? U : never
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取歌曲文件歌词
|
* 获取歌曲文件歌词
|
||||||
@@ -181,7 +182,7 @@ export const getLocalMusicFileLyric = async(path: string): Promise<LX.Music.Lyri
|
|||||||
const { confidence, encoding } = detect(lrcBuf)
|
const { confidence, encoding } = detect(lrcBuf)
|
||||||
console.log('lrc file encoding', confidence, encoding)
|
console.log('lrc file encoding', confidence, encoding)
|
||||||
if (confidence > 0.8) {
|
if (confidence > 0.8) {
|
||||||
const iconv = await import('iconv-lite')
|
const iconv = (await import('iconv-lite')).default
|
||||||
if (iconv.encodingExists(encoding)) {
|
if (iconv.encodingExists(encoding)) {
|
||||||
const lrc = iconv.decode(lrcBuf, encoding)
|
const lrc = iconv.decode(lrcBuf, encoding)
|
||||||
if (lrc) {
|
if (lrc) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
singer: decodeName(formatSingerName(rawData.Singers, 'name')),
|
singer: decodeName(formatSingerName(rawData.Singers, 'name')),
|
||||||
name: decodeName(rawData.SongName),
|
name: decodeName(`${rawData.OriSongName}${rawData.Suffix ? ` ${rawData.Suffix}` : ''}`),
|
||||||
albumName: decodeName(rawData.AlbumName),
|
albumName: decodeName(rawData.AlbumName),
|
||||||
albumId: rawData.AlbumID,
|
albumId: rawData.AlbumID,
|
||||||
songmid: rawData.Audioid,
|
songmid: rawData.Audioid,
|
||||||
|
|||||||
@@ -67,31 +67,31 @@ export default {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const buf_key = Buffer.from('yeelion')
|
// const buf_key = Buffer.from('yeelion')
|
||||||
const buf_key_len = buf_key.length
|
// const buf_key_len = buf_key.length
|
||||||
const buildParams = (id, isGetLyricx) => {
|
// const buildParams = (id, isGetLyricx) => {
|
||||||
let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}`
|
// let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}`
|
||||||
if (isGetLyricx) params += '&lrcx=1'
|
// if (isGetLyricx) params += '&lrcx=1'
|
||||||
const buf_str = Buffer.from(params)
|
// const buf_str = Buffer.from(params)
|
||||||
const buf_str_len = buf_str.length
|
// const buf_str_len = buf_str.length
|
||||||
const output = new Uint16Array(buf_str_len)
|
// const output = new Uint16Array(buf_str_len)
|
||||||
let i = 0
|
// let i = 0
|
||||||
while (i < buf_str_len) {
|
// while (i < buf_str_len) {
|
||||||
let j = 0
|
// let j = 0
|
||||||
while (j < buf_key_len && i < buf_str_len) {
|
// while (j < buf_key_len && i < buf_str_len) {
|
||||||
output[i] = buf_key[j] ^ buf_str[i]
|
// output[i] = buf_key[j] ^ buf_str[i]
|
||||||
i++
|
// i++
|
||||||
j++
|
// j++
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return Buffer.from(output).toString('base64')
|
// return Buffer.from(output).toString('base64')
|
||||||
}
|
// }
|
||||||
|
|
||||||
// console.log(buildParams('207527604', false))
|
// console.log(buildParams('207527604', false))
|
||||||
// console.log(buildParams('207527604', true))
|
// console.log(buildParams('207527604', true))
|
||||||
|
|
||||||
const timeExp = /^\[([\d:.]*)\]{1}/g
|
const timeExp = /^\[([\d:.]*)\]{1}/g
|
||||||
const existTimeExp = /\[\d{1,2}:.*\d{1,4}\]/
|
const existTimeExp = /\[\d{1,2}:.*\d{1,4}]/
|
||||||
const lyricxTag = /^<-?\d+,-?\d+>/
|
const lyricxTag = /^<-?\d+,-?\d+>/
|
||||||
export default {
|
export default {
|
||||||
/* sortLrcArr(arr) {
|
/* sortLrcArr(arr) {
|
||||||
@@ -168,14 +168,6 @@ export default {
|
|||||||
|
|
||||||
if (!isLyricx && lrcT.length > lrc.length * 0.3 && lrc.length - lrcT.length > 6) {
|
if (!isLyricx && lrcT.length > lrc.length * 0.3 && lrc.length - lrcT.length > 6) {
|
||||||
throw new Error('failed')
|
throw new Error('failed')
|
||||||
// if (lrc.length * 0.4 < lrcT.length) { // 翻译数量需大于歌词数量的0.4倍,否则认为没有翻译
|
|
||||||
// const tItem = lrc.pop()
|
|
||||||
// tItem.time = lrc[lrc.length - 1].time
|
|
||||||
// lrcT.push(tItem)
|
|
||||||
// } else {
|
|
||||||
// lrc = arr
|
|
||||||
// lrcT = []
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -235,10 +227,11 @@ export default {
|
|||||||
// },
|
// },
|
||||||
getLyric(musicInfo, isGetLyricx = true) {
|
getLyric(musicInfo, isGetLyricx = true) {
|
||||||
// this.getLyric2(musicInfo)
|
// this.getLyric2(musicInfo)
|
||||||
const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
|
const requestObj = httpFetch(`http://mlyric.kuwo.cn/mobi.s?f=web&type=lyric&lrcx=${isGetLyricx ? 1 : 0}&rid=${musicInfo.songmid}&encode=utf8`)
|
||||||
requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
|
requestObj.promise = requestObj.promise.then(async({ statusCode, body, raw }) => {
|
||||||
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
|
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
|
||||||
return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => {
|
const lrcText = await decodeLyric(raw, isGetLyricx)
|
||||||
|
// console.log(lrcText)
|
||||||
// let lrcInfo
|
// let lrcInfo
|
||||||
// try {
|
// try {
|
||||||
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
||||||
@@ -248,7 +241,7 @@ export default {
|
|||||||
let lrcInfo
|
let lrcInfo
|
||||||
// console.log(Buffer.from(base64Data, 'base64').toString())
|
// console.log(Buffer.from(base64Data, 'base64').toString())
|
||||||
try {
|
try {
|
||||||
lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
|
lrcInfo = this.parseLrc(lrcText)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return Promise.reject(new Error('Get lyric failed'))
|
return Promise.reject(new Error('Get lyric failed'))
|
||||||
}
|
}
|
||||||
@@ -264,7 +257,6 @@ export default {
|
|||||||
// console.log(lrcInfo)
|
// console.log(lrcInfo)
|
||||||
return lrcInfo
|
return lrcInfo
|
||||||
})
|
})
|
||||||
})
|
|
||||||
return requestObj
|
return requestObj
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,11 +145,13 @@ export default {
|
|||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
filterList2(rawData) {
|
filterList2(rawData) {
|
||||||
// console.log(rawData)
|
|
||||||
const list = []
|
const list = []
|
||||||
|
const allowedTypes = ['songlist', 'list', 'album']
|
||||||
|
// digest: 5 = list, 8 = songlist, 13 = album, 4 = artist
|
||||||
rawData.forEach(item => {
|
rawData.forEach(item => {
|
||||||
if (!item.label) return
|
item.list.forEach(item => {
|
||||||
list.push(...item.list.map(item => ({
|
if (!allowedTypes.includes(item.type)) return
|
||||||
|
list.push({
|
||||||
play_count: item.play_count && this.formatPlayCount(item.listencnt),
|
play_count: item.play_count && this.formatPlayCount(item.listencnt),
|
||||||
id: `digest-${item.digest}__${item.id}`,
|
id: `digest-${item.digest}__${item.id}`,
|
||||||
author: item.uname,
|
author: item.uname,
|
||||||
@@ -160,7 +162,8 @@ export default {
|
|||||||
grade: item.favorcnt && item.favorcnt / 10,
|
grade: item.favorcnt && item.favorcnt / 10,
|
||||||
desc: item.desc,
|
desc: item.desc,
|
||||||
source: 'kw',
|
source: 'kw',
|
||||||
})))
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,34 +1,43 @@
|
|||||||
// import { httpGet, httpFetch } from '../../request'
|
|
||||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
|
||||||
import { rendererInvoke } from '@common/rendererIpc'
|
|
||||||
import { createCipheriv, createDecipheriv } from 'crypto'
|
import { createCipheriv, createDecipheriv } from 'crypto'
|
||||||
import { toMD5 } from '../utils'
|
import { toMD5 } from '../utils'
|
||||||
|
import { inflate } from 'zlib'
|
||||||
|
|
||||||
// const kw_token = {
|
|
||||||
// token: null,
|
|
||||||
// isGetingToken: false,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const translationMap = {
|
const handleInflate = async(data) => {
|
||||||
// "{'": '{"',
|
return new Promise((resolve, reject) => {
|
||||||
// "'}\n": '"}',
|
inflate(data, (err, result) => {
|
||||||
// "'}": '"}',
|
if (err) {
|
||||||
// "':'": '":"',
|
reject(err)
|
||||||
// "','": '","',
|
return
|
||||||
// "':{'": '":{"',
|
}
|
||||||
// "':['": '":["',
|
resolve(result)
|
||||||
// "'}],'": '"}],"',
|
})
|
||||||
// "':[{'": '":[{"',
|
})
|
||||||
// "'},'": '"},"',
|
}
|
||||||
// "'},{'": '"},{"',
|
|
||||||
// "':[],'": '":[],"',
|
|
||||||
// "':{},'": '":{},"',
|
|
||||||
// "'}]}": '"}]}',
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export const objStr2JSON = str => {
|
const buf_key = Buffer.from('yeelion')
|
||||||
// return JSON.parse(str.replace(/(^{'|'}\n$|'}$|':'|','|':\[{'|'}\],'|':{'|'},'|'},{'|':\['|':\[\],'|':{},'|'}]})/g, s => translationMap[s]))
|
const buf_key_len = buf_key.length
|
||||||
// }
|
|
||||||
|
export const decodeLyric = async(rawData, isGetLyricx) => {
|
||||||
|
const buf = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData)
|
||||||
|
if (buf.toString('utf8', 0, 10).toLowerCase() !== 'tp=content') return ''
|
||||||
|
const lrcData = await handleInflate(buf.subarray(buf.indexOf('\r\n\r\n') + 4))
|
||||||
|
if (!isGetLyricx) return lrcData.toString('utf8')
|
||||||
|
const buf_str = Buffer.from(lrcData.toString(), 'base64')
|
||||||
|
const buf_str_len = buf_str.length
|
||||||
|
const output = new Uint8Array(buf_str_len)
|
||||||
|
let i = 0
|
||||||
|
while (i < buf_str_len) {
|
||||||
|
let j = 0
|
||||||
|
while (j < buf_key_len && i < buf_str_len) {
|
||||||
|
output[i] = buf_str[i] ^ buf_key[j]
|
||||||
|
i++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(output).toString('utf8')
|
||||||
|
}
|
||||||
|
|
||||||
export const objStr2JSON = str => {
|
export const objStr2JSON = str => {
|
||||||
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
|
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
|
||||||
@@ -37,13 +46,13 @@ export const objStr2JSON = str => {
|
|||||||
|
|
||||||
export const formatSinger = rawData => rawData.replace(/&/g, '、')
|
export const formatSinger = rawData => rawData.replace(/&/g, '、')
|
||||||
|
|
||||||
export const matchToken = headers => {
|
// export const matchToken = headers => {
|
||||||
try {
|
// try {
|
||||||
return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1]
|
// return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1]
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
return null
|
// return null
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
// const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
|
||||||
|
|
||||||
@@ -63,8 +72,6 @@ export const matchToken = headers => {
|
|||||||
// })
|
// })
|
||||||
// })
|
// })
|
||||||
|
|
||||||
export const decodeLyric = base64Data => rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, base64Data)
|
|
||||||
|
|
||||||
// export const tokenRequest = async(url, options = {}) => {
|
// export const tokenRequest = async(url, options = {}) => {
|
||||||
// let token = kw_token.token
|
// let token = kw_token.token
|
||||||
// if (!token) token = await getToken()
|
// if (!token) token = await getToken()
|
||||||
@@ -88,8 +95,8 @@ export const decodeLyric = base64Data => rendererInvoke(WIN_MAIN_RENDERER_EVENT_
|
|||||||
|
|
||||||
export const lrcTools = {
|
export const lrcTools = {
|
||||||
rxps: {
|
rxps: {
|
||||||
wordLine: /^(\[\d{1,2}:.*\d{1,4}\])\s*(\S+(?:\s+\S+)*)?\s*/,
|
wordLine: /^(\[\d{1,2}:.*\d{1,4}])\s*(\S+(?:\s+\S+)*)?\s*/,
|
||||||
tagLine: /\[(ver|ti|ar|al|offset|by|kuwo):\s*(\S+(?:\s+\S+)*)\s*\]/,
|
tagLine: /\[(ver|ti|ar|al|offset|by|kuwo):\s*(\S+(?:\s+\S+)*)\s*]/,
|
||||||
wordTimeAll: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/g,
|
wordTimeAll: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/g,
|
||||||
wordTime: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/,
|
wordTime: /<(-?\d+),(-?\d+)(?:,-?\d+)?>/,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const mrcTools = {
|
|||||||
},
|
},
|
||||||
async getMusicInfo(songInfo) {
|
async getMusicInfo(songInfo) {
|
||||||
return songInfo.mrcUrl == null
|
return songInfo.mrcUrl == null
|
||||||
? getMusicInfo(songInfo.copyrightId)
|
? getMusicInfo(songInfo.songmid)
|
||||||
: songInfo
|
: songInfo
|
||||||
},
|
},
|
||||||
getLyric(songInfo) {
|
getLyric(songInfo) {
|
||||||
|
|||||||
@@ -1,28 +1,8 @@
|
|||||||
import { httpFetch } from '../../request'
|
import { getMusicInfo } from './musicInfo'
|
||||||
import getSongId from './songId'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async getPicUrl(songId, tryNum = 0) {
|
|
||||||
let requestObj = httpFetch(`http://music.migu.cn/v3/api/music/audioPlayer/getSongPic?songId=${songId}`, {
|
|
||||||
headers: {
|
|
||||||
Referer: 'http://music.migu.cn/v3/music/player/audio?from=migu',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
requestObj.promise.then(({ body }) => {
|
|
||||||
if (body.returnCode !== '000000') {
|
|
||||||
if (tryNum > 5) return Promise.reject(new Error('图片获取失败'))
|
|
||||||
let tryRequestObj = this.getPic(songId, ++tryNum)
|
|
||||||
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
|
|
||||||
return tryRequestObj.promise
|
|
||||||
}
|
|
||||||
let url = body.largePic || body.mediumPic || body.smallPic
|
|
||||||
if (!/https?:/.test(url)) url = 'http:' + url
|
|
||||||
return url
|
|
||||||
})
|
|
||||||
return requestObj
|
|
||||||
},
|
|
||||||
async getPic(songInfo) {
|
async getPic(songInfo) {
|
||||||
const songId = await getSongId(songInfo)
|
const info = await getMusicInfo(songInfo.songmid)
|
||||||
return this.getPicUrl(songId)
|
return info.img
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getMusicInfo } from './musicInfo'
|
|||||||
|
|
||||||
const getSongId = async(mInfo) => {
|
const getSongId = async(mInfo) => {
|
||||||
if (mInfo.songmid != mInfo.copyrightId) return mInfo.songmid
|
if (mInfo.songmid != mInfo.copyrightId) return mInfo.songmid
|
||||||
const musicInfo = await getMusicInfo(mInfo.copyrightId)
|
const musicInfo = await getMusicInfo(mInfo.songmid)
|
||||||
return musicInfo.songmid
|
return musicInfo.songmid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { httpFetch } from '../../request'
|
import { httpFetch } from '../../request'
|
||||||
import getMusicInfo from './musicInfo'
|
import getMusicInfo from './musicInfo'
|
||||||
import { rendererInvoke } from '@common/rendererIpc'
|
import { decodeQrc } from './qrcDecode'
|
||||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
|
||||||
|
|
||||||
const songIdMap = new Map()
|
const songIdMap = new Map()
|
||||||
const promises = new Map()
|
const promises = new Map()
|
||||||
export const decodeLyric = (lrc, tlrc, rlrc) => rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_tx_decode_lyric, { lrc, tlrc, rlrc })
|
export const decodeLyric = async(lrc, tlrc, rlrc) => ({
|
||||||
|
lyric: await decodeQrc(lrc),
|
||||||
|
tlyric: await decodeQrc(tlrc),
|
||||||
|
rlyric: await decodeQrc(rlrc),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
const parseTools = {
|
const parseTools = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { httpFetch } from '../../request'
|
|
||||||
import { formatPlayTime, sizeFormate } from '../../index'
|
import { formatPlayTime, sizeFormate } from '../../index'
|
||||||
import { formatSingerName } from '../utils'
|
import { formatSingerName } from '../utils'
|
||||||
|
import { signRequest } from './utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
limit: 50,
|
limit: 50,
|
||||||
@@ -10,71 +10,58 @@ export default {
|
|||||||
successCode: 0,
|
successCode: 0,
|
||||||
musicSearch(str, page, limit, retryNum = 0) {
|
musicSearch(str, page, limit, retryNum = 0) {
|
||||||
if (retryNum > 5) return Promise.reject(new Error('搜索失败'))
|
if (retryNum > 5) return Promise.reject(new Error('搜索失败'))
|
||||||
// searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=sizer.yqq.song_next&searchid=49252838123499591&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0`)
|
const searchRequest = signRequest({
|
||||||
// const searchRequest = httpFetch(`https://shc.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&remoteplace=txt.yqq.top&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&cv=4747474&ct=24&format=json&inCharset=utf-8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&uin=0&hostUin=0&loginUin=0`)
|
|
||||||
const searchRequest = httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
|
||||||
method: 'post',
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'QQMusic 14090508(android 12)',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
comm: {
|
comm: {
|
||||||
ct: '11',
|
_channelid: '0',
|
||||||
cv: '14090508',
|
_os_version: '6.2.9200-2',
|
||||||
v: '14090508',
|
ct: '19',
|
||||||
|
cv: '2151',
|
||||||
|
guid: '1F70E520B2EAA7D25E11760783C53CA9',
|
||||||
|
patch: '118',
|
||||||
|
psrf_access_token_expiresAt: 0,
|
||||||
|
psrf_qqaccess_token: '',
|
||||||
|
psrf_qqopenid: '',
|
||||||
|
psrf_qqunionid: '',
|
||||||
tmeAppID: 'qqmusic',
|
tmeAppID: 'qqmusic',
|
||||||
phonetype: 'EBG-AN10',
|
tmeLoginType: 0,
|
||||||
deviceScore: '553.47',
|
uin: '0',
|
||||||
devicelevel: '50',
|
wid: '7223299733393904640',
|
||||||
newdevicelevel: '20',
|
|
||||||
rom: 'HuaWei/EMOTION/EmotionUI_14.2.0',
|
|
||||||
os_ver: '12',
|
|
||||||
OpenUDID: '0',
|
|
||||||
OpenUDID2: '0',
|
|
||||||
QIMEI36: '0',
|
|
||||||
udid: '0',
|
|
||||||
chid: '0',
|
|
||||||
aid: '0',
|
|
||||||
oaid: '0',
|
|
||||||
taid: '0',
|
|
||||||
tid: '0',
|
|
||||||
wid: '0',
|
|
||||||
uid: '0',
|
|
||||||
sid: '0',
|
|
||||||
modeSwitch: '6',
|
|
||||||
teenMode: '0',
|
|
||||||
ui_mode: '2',
|
|
||||||
nettype: '1020',
|
|
||||||
v4ip: '',
|
|
||||||
},
|
},
|
||||||
req: {
|
'music.search.SearchCgiService': {
|
||||||
module: 'music.search.SearchCgiService',
|
module: 'music.search.SearchCgiService',
|
||||||
method: 'DoSearchForQQMusicMobile',
|
method: 'DoSearchForQQMusicDesktop',
|
||||||
param: {
|
param: {
|
||||||
search_type: 0,
|
|
||||||
query: str,
|
|
||||||
page_num: page,
|
|
||||||
num_per_page: limit,
|
|
||||||
highlight: 0,
|
|
||||||
nqc_flag: 0,
|
|
||||||
multi_zhida: 0,
|
|
||||||
cat: 2,
|
|
||||||
grp: 1,
|
grp: 1,
|
||||||
sin: 0,
|
num_per_page: limit,
|
||||||
sem: 0,
|
page_num: page,
|
||||||
},
|
query: str,
|
||||||
|
remoteplace: 'txt.newclient.top',
|
||||||
|
search_type: 0,
|
||||||
|
searchid: this.getSearchId(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${this.limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
|
return searchRequest.then(({ body }) => {
|
||||||
return searchRequest.promise.then(({ body }) => {
|
|
||||||
// console.log(body)
|
// console.log(body)
|
||||||
if (body.code != this.successCode || body.req.code != this.successCode) return this.musicSearch(str, page, limit, ++retryNum)
|
const req = body?.['music.search.SearchCgiService'] ?? body?.req
|
||||||
return body.req.data
|
if (!req || body.code != this.successCode || req.code != this.successCode) {
|
||||||
|
return this.musicSearch(str, page, limit, ++retryNum)
|
||||||
|
}
|
||||||
|
return req.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* PC 客户端版 searchid:32 位大写十六进制 GUID + 5 位补零随机数 = 37 字符。
|
||||||
|
* 对应 QQ 音乐 PC 端 searchid 形状(服务端只需要唯一的会话 ID,形状一致即可)。
|
||||||
|
*/
|
||||||
|
getSearchId() {
|
||||||
|
let guid = ''
|
||||||
|
for (let i = 0; i < 32; i++) guid += Math.floor(Math.random() * 16).toString(16)
|
||||||
|
return guid.toUpperCase() + String(Math.floor(Math.random() * 100000)).padStart(5, '0')
|
||||||
|
},
|
||||||
handleResult(rawList) {
|
handleResult(rawList) {
|
||||||
// console.log(rawList)
|
// console.log(rawList)
|
||||||
|
if (!rawList || !Array.isArray(rawList)) return []
|
||||||
const list = []
|
const list = []
|
||||||
rawList.forEach(item => {
|
rawList.forEach(item => {
|
||||||
if (!item.file?.media_mid) return
|
if (!item.file?.media_mid) return
|
||||||
@@ -119,11 +106,12 @@ export default {
|
|||||||
}
|
}
|
||||||
list.push({
|
list.push({
|
||||||
singer: formatSingerName(item.singer, 'name'),
|
singer: formatSingerName(item.singer, 'name'),
|
||||||
name: item.name + (item.title_extra ?? ''),
|
// name: item.name + (item.title_extra ?? ''),
|
||||||
|
name: item.title,
|
||||||
albumName,
|
albumName,
|
||||||
albumId,
|
albumId,
|
||||||
source: 'tx',
|
source: 'tx',
|
||||||
interval: formatPlayTime(item.interval),
|
interval: item.interval ? formatPlayTime(item.interval) : null,
|
||||||
songId: item.id,
|
songId: item.id,
|
||||||
albumMid: item.album?.mid ?? '',
|
albumMid: item.album?.mid ?? '',
|
||||||
strMediaMid: item.file.media_mid,
|
strMediaMid: item.file.media_mid,
|
||||||
@@ -141,11 +129,10 @@ export default {
|
|||||||
},
|
},
|
||||||
search(str, page = 1, limit) {
|
search(str, page = 1, limit) {
|
||||||
if (limit == null) limit = this.limit
|
if (limit == null) limit = this.limit
|
||||||
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
|
|
||||||
return this.musicSearch(str, page, limit).then(({ body, meta }) => {
|
return this.musicSearch(str, page, limit).then(({ body, meta }) => {
|
||||||
let list = this.handleResult(body.item_song)
|
let list = this.handleResult(body.song.list)
|
||||||
|
|
||||||
this.total = meta.estimate_sum
|
this.total = meta.sum
|
||||||
this.page = page
|
this.page = page
|
||||||
this.allPage = Math.ceil(this.total / limit)
|
this.allPage = Math.ceil(this.total / limit)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import { inflate, constants } from 'zlib'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QRC 歌词解密(3DES-ECB + zlib inflate)
|
||||||
|
*
|
||||||
|
* 由原 C++ native 插件(qrc_decode.node)1:1 移植为纯 JS 实现。
|
||||||
|
* 加密算法为标准 3DES(DES-EDE3,ECB 模式、无填充),密钥固定。
|
||||||
|
* 数据流程:hex 字符串 -> 字节 -> 3DES 解密 -> zlib inflate -> UTF-8 文本。
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DES_ENCRYPT = 1
|
||||||
|
const DES_DECRYPT = 0
|
||||||
|
|
||||||
|
// DES S-box。注意:QRC 使用的是非标准 DES 变体,
|
||||||
|
// 其中 S2[23]=15、S4[53]=10 两处与标准 DES 不同(标准为 14、1)。
|
||||||
|
// 这两个值与原 native 插件(qrc_decode.node)二进制完全一致,不可改为标准值,
|
||||||
|
// 否则解密结果错误。下表逐字节对应原 C++ 实现。
|
||||||
|
const sbox = [
|
||||||
|
[
|
||||||
|
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||||
|
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 15, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||||
|
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||||
|
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||||
|
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 10, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||||
|
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||||
|
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||||
|
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||||
|
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
const key_rnd_shift = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
||||||
|
const key_perm_c = [
|
||||||
|
56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
|
||||||
|
]
|
||||||
|
const key_perm_d = [
|
||||||
|
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3,
|
||||||
|
]
|
||||||
|
const key_compression = [
|
||||||
|
13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46,
|
||||||
|
54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31,
|
||||||
|
]
|
||||||
|
|
||||||
|
// 全部以无符号 32 位处理,>>> 0 保证溢出行为与 C++ uint32_t 一致
|
||||||
|
const bitnum = (a, b, c) => (((a[((b / 32) | 0) * 4 + 3 - (((b % 32) / 8) | 0)] >>> (7 - (b % 8))) & 1) << c) >>> 0
|
||||||
|
|
||||||
|
const bitnum_intr = (a, b, c) => (((a >>> (31 - b)) & 1) << c) >>> 0
|
||||||
|
|
||||||
|
const bitnum_intl = (a, b, c) => (((((a << b) >>> 0) & 0x80000000) >>> 0) >>> c) >>> 0
|
||||||
|
|
||||||
|
const sbox_bit = a => (a & 32) | ((a & 31) >>> 1) | ((a & 1) << 4)
|
||||||
|
|
||||||
|
// 初始置换:返回 [s0, s1]
|
||||||
|
const initial_permutation = (input) => {
|
||||||
|
const s0 = (
|
||||||
|
bitnum(input, 57, 31) | bitnum(input, 49, 30) | bitnum(input, 41, 29) | bitnum(input, 33, 28) |
|
||||||
|
bitnum(input, 25, 27) | bitnum(input, 17, 26) | bitnum(input, 9, 25) | bitnum(input, 1, 24) |
|
||||||
|
bitnum(input, 59, 23) | bitnum(input, 51, 22) | bitnum(input, 43, 21) | bitnum(input, 35, 20) |
|
||||||
|
bitnum(input, 27, 19) | bitnum(input, 19, 18) | bitnum(input, 11, 17) | bitnum(input, 3, 16) |
|
||||||
|
bitnum(input, 61, 15) | bitnum(input, 53, 14) | bitnum(input, 45, 13) | bitnum(input, 37, 12) |
|
||||||
|
bitnum(input, 29, 11) | bitnum(input, 21, 10) | bitnum(input, 13, 9) | bitnum(input, 5, 8) |
|
||||||
|
bitnum(input, 63, 7) | bitnum(input, 55, 6) | bitnum(input, 47, 5) | bitnum(input, 39, 4) |
|
||||||
|
bitnum(input, 31, 3) | bitnum(input, 23, 2) | bitnum(input, 15, 1) | bitnum(input, 7, 0)
|
||||||
|
) >>> 0
|
||||||
|
const s1 = (
|
||||||
|
bitnum(input, 56, 31) | bitnum(input, 48, 30) | bitnum(input, 40, 29) | bitnum(input, 32, 28) |
|
||||||
|
bitnum(input, 24, 27) | bitnum(input, 16, 26) | bitnum(input, 8, 25) | bitnum(input, 0, 24) |
|
||||||
|
bitnum(input, 58, 23) | bitnum(input, 50, 22) | bitnum(input, 42, 21) | bitnum(input, 34, 20) |
|
||||||
|
bitnum(input, 26, 19) | bitnum(input, 18, 18) | bitnum(input, 10, 17) | bitnum(input, 2, 16) |
|
||||||
|
bitnum(input, 60, 15) | bitnum(input, 52, 14) | bitnum(input, 44, 13) | bitnum(input, 36, 12) |
|
||||||
|
bitnum(input, 28, 11) | bitnum(input, 20, 10) | bitnum(input, 12, 9) | bitnum(input, 4, 8) |
|
||||||
|
bitnum(input, 62, 7) | bitnum(input, 54, 6) | bitnum(input, 46, 5) | bitnum(input, 38, 4) |
|
||||||
|
bitnum(input, 30, 3) | bitnum(input, 22, 2) | bitnum(input, 14, 1) | bitnum(input, 6, 0)
|
||||||
|
) >>> 0
|
||||||
|
return [s0, s1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逆置换:将 s0/s1 写回 8 字节 out
|
||||||
|
const inverse_permutation = (s0, s1, out) => {
|
||||||
|
out[3] = (bitnum_intr(s1, 7, 7) | bitnum_intr(s0, 7, 6) | bitnum_intr(s1, 15, 5) | bitnum_intr(s0, 15, 4) | bitnum_intr(s1, 23, 3) | bitnum_intr(s0, 23, 2) | bitnum_intr(s1, 31, 1) | bitnum_intr(s0, 31, 0)) & 0xff
|
||||||
|
out[2] = (bitnum_intr(s1, 6, 7) | bitnum_intr(s0, 6, 6) | bitnum_intr(s1, 14, 5) | bitnum_intr(s0, 14, 4) | bitnum_intr(s1, 22, 3) | bitnum_intr(s0, 22, 2) | bitnum_intr(s1, 30, 1) | bitnum_intr(s0, 30, 0)) & 0xff
|
||||||
|
out[1] = (bitnum_intr(s1, 5, 7) | bitnum_intr(s0, 5, 6) | bitnum_intr(s1, 13, 5) | bitnum_intr(s0, 13, 4) | bitnum_intr(s1, 21, 3) | bitnum_intr(s0, 21, 2) | bitnum_intr(s1, 29, 1) | bitnum_intr(s0, 29, 0)) & 0xff
|
||||||
|
out[0] = (bitnum_intr(s1, 4, 7) | bitnum_intr(s0, 4, 6) | bitnum_intr(s1, 12, 5) | bitnum_intr(s0, 12, 4) | bitnum_intr(s1, 20, 3) | bitnum_intr(s0, 20, 2) | bitnum_intr(s1, 28, 1) | bitnum_intr(s0, 28, 0)) & 0xff
|
||||||
|
out[7] = (bitnum_intr(s1, 3, 7) | bitnum_intr(s0, 3, 6) | bitnum_intr(s1, 11, 5) | bitnum_intr(s0, 11, 4) | bitnum_intr(s1, 19, 3) | bitnum_intr(s0, 19, 2) | bitnum_intr(s1, 27, 1) | bitnum_intr(s0, 27, 0)) & 0xff
|
||||||
|
out[6] = (bitnum_intr(s1, 2, 7) | bitnum_intr(s0, 2, 6) | bitnum_intr(s1, 10, 5) | bitnum_intr(s0, 10, 4) | bitnum_intr(s1, 18, 3) | bitnum_intr(s0, 18, 2) | bitnum_intr(s1, 26, 1) | bitnum_intr(s0, 26, 0)) & 0xff
|
||||||
|
out[5] = (bitnum_intr(s1, 1, 7) | bitnum_intr(s0, 1, 6) | bitnum_intr(s1, 9, 5) | bitnum_intr(s0, 9, 4) | bitnum_intr(s1, 17, 3) | bitnum_intr(s0, 17, 2) | bitnum_intr(s1, 25, 1) | bitnum_intr(s0, 25, 0)) & 0xff
|
||||||
|
out[4] = (bitnum_intr(s1, 0, 7) | bitnum_intr(s0, 0, 6) | bitnum_intr(s1, 8, 5) | bitnum_intr(s0, 8, 4) | bitnum_intr(s1, 16, 3) | bitnum_intr(s0, 16, 2) | bitnum_intr(s1, 24, 1) | bitnum_intr(s0, 24, 0)) & 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feistel 轮函数 f(state, key)
|
||||||
|
const des_f = (state, key) => {
|
||||||
|
const t1 = (
|
||||||
|
bitnum_intl(state, 31, 0) | (((state & 0xF0000000) >>> 0) >>> 1) | bitnum_intl(state, 4, 5) |
|
||||||
|
bitnum_intl(state, 3, 6) | (((state & 0x0F000000) >>> 0) >>> 3) | bitnum_intl(state, 8, 11) |
|
||||||
|
bitnum_intl(state, 7, 12) | (((state & 0x00F00000) >>> 0) >>> 5) | bitnum_intl(state, 12, 17) |
|
||||||
|
bitnum_intl(state, 11, 18) | (((state & 0x000F0000) >>> 0) >>> 7) | bitnum_intl(state, 16, 23)
|
||||||
|
) >>> 0
|
||||||
|
const t2 = (
|
||||||
|
bitnum_intl(state, 15, 0) | (((state & 0x0000F000) << 15) >>> 0) | bitnum_intl(state, 20, 5) |
|
||||||
|
bitnum_intl(state, 19, 6) | (((state & 0x00000F00) << 13) >>> 0) | bitnum_intl(state, 24, 11) |
|
||||||
|
bitnum_intl(state, 23, 12) | (((state & 0x000000F0) << 11) >>> 0) | bitnum_intl(state, 28, 17) |
|
||||||
|
bitnum_intl(state, 27, 18) | (((state & 0x0000000F) << 9) >>> 0) | bitnum_intl(state, 0, 23)
|
||||||
|
) >>> 0
|
||||||
|
|
||||||
|
const lrgstate = [
|
||||||
|
(t1 >>> 24) & 0xFF, (t1 >>> 16) & 0xFF, (t1 >>> 8) & 0xFF,
|
||||||
|
(t2 >>> 24) & 0xFF, (t2 >>> 16) & 0xFF, (t2 >>> 8) & 0xFF,
|
||||||
|
]
|
||||||
|
for (let i = 0; i < 6; i++) lrgstate[i] ^= key[i]
|
||||||
|
|
||||||
|
let s = (
|
||||||
|
(sbox[0][sbox_bit(lrgstate[0] >>> 2)] << 28) |
|
||||||
|
(sbox[1][sbox_bit(((lrgstate[0] & 0x03) << 4) | (lrgstate[1] >>> 4))] << 24) |
|
||||||
|
(sbox[2][sbox_bit(((lrgstate[1] & 0x0F) << 2) | (lrgstate[2] >>> 6))] << 20) |
|
||||||
|
(sbox[3][sbox_bit(lrgstate[2] & 0x3F)] << 16) |
|
||||||
|
(sbox[4][sbox_bit(lrgstate[3] >>> 2)] << 12) |
|
||||||
|
(sbox[5][sbox_bit(((lrgstate[3] & 0x03) << 4) | (lrgstate[4] >>> 4))] << 8) |
|
||||||
|
(sbox[6][sbox_bit(((lrgstate[4] & 0x0F) << 2) | (lrgstate[5] >>> 6))] << 4) |
|
||||||
|
sbox[7][sbox_bit(lrgstate[5] & 0x3F)]
|
||||||
|
) >>> 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
bitnum_intl(s, 15, 0) | bitnum_intl(s, 6, 1) | bitnum_intl(s, 19, 2) | bitnum_intl(s, 20, 3) |
|
||||||
|
bitnum_intl(s, 28, 4) | bitnum_intl(s, 11, 5) | bitnum_intl(s, 27, 6) | bitnum_intl(s, 16, 7) |
|
||||||
|
bitnum_intl(s, 0, 8) | bitnum_intl(s, 14, 9) | bitnum_intl(s, 22, 10) | bitnum_intl(s, 25, 11) |
|
||||||
|
bitnum_intl(s, 4, 12) | bitnum_intl(s, 17, 13) | bitnum_intl(s, 30, 14) | bitnum_intl(s, 9, 15) |
|
||||||
|
bitnum_intl(s, 1, 16) | bitnum_intl(s, 7, 17) | bitnum_intl(s, 23, 18) | bitnum_intl(s, 13, 19) |
|
||||||
|
bitnum_intl(s, 31, 20) | bitnum_intl(s, 26, 21) | bitnum_intl(s, 2, 22) | bitnum_intl(s, 8, 23) |
|
||||||
|
bitnum_intl(s, 18, 24) | bitnum_intl(s, 12, 25) | bitnum_intl(s, 29, 26) | bitnum_intl(s, 5, 27) |
|
||||||
|
bitnum_intl(s, 21, 28) | bitnum_intl(s, 10, 29) | bitnum_intl(s, 3, 30) | bitnum_intl(s, 24, 31)
|
||||||
|
) >>> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单次 DES 加/解密一个 8 字节分组(轮密钥 schedule 为 16x6)
|
||||||
|
const des_crypt = (input, schedule, output) => {
|
||||||
|
let [s0, s1] = initial_permutation(input)
|
||||||
|
for (let i = 0; i < 15; i++) {
|
||||||
|
const prev = s1
|
||||||
|
s1 = (des_f(s1, schedule[i]) ^ s0) >>> 0
|
||||||
|
s0 = prev
|
||||||
|
}
|
||||||
|
s0 = (des_f(s1, schedule[15]) ^ s0) >>> 0
|
||||||
|
inverse_permutation(s0, s1, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 16 轮子密钥
|
||||||
|
const key_schedule = (key, mode) => {
|
||||||
|
const schedule = Array.from({ length: 16 }, () => new Uint8Array(6))
|
||||||
|
let c = 0
|
||||||
|
let d = 0
|
||||||
|
for (let i = 0; i < 28; i++) {
|
||||||
|
c = (c | bitnum(key, key_perm_c[i], 31 - i)) >>> 0
|
||||||
|
d = (d | bitnum(key, key_perm_d[i], 31 - i)) >>> 0
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
c = ((((c << key_rnd_shift[i]) >>> 0) | (c >>> (28 - key_rnd_shift[i]))) & 0xFFFFFFF0) >>> 0
|
||||||
|
d = ((((d << key_rnd_shift[i]) >>> 0) | (d >>> (28 - key_rnd_shift[i]))) & 0xFFFFFFF0) >>> 0
|
||||||
|
const togen = mode === DES_DECRYPT ? 15 - i : i
|
||||||
|
for (let j = 0; j < 24; j++) {
|
||||||
|
schedule[togen][(j / 8) | 0] |= bitnum_intr(c, key_compression[j], 7 - (j % 8))
|
||||||
|
}
|
||||||
|
for (let j = 24; j < 48; j++) {
|
||||||
|
schedule[togen][(j / 8) | 0] |= bitnum_intr(d, key_compression[j] - 27, 7 - (j % 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schedule
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3DES(EDE3)密钥编排,返回 3 组 schedule
|
||||||
|
const tripledes_key_setup = (key, mode) => {
|
||||||
|
if (mode === DES_ENCRYPT) {
|
||||||
|
return [
|
||||||
|
key_schedule(key.subarray(0, 8), DES_ENCRYPT),
|
||||||
|
key_schedule(key.subarray(8, 16), DES_DECRYPT),
|
||||||
|
key_schedule(key.subarray(16, 24), DES_ENCRYPT),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
key_schedule(key.subarray(16, 24), DES_DECRYPT),
|
||||||
|
key_schedule(key.subarray(8, 16), DES_ENCRYPT),
|
||||||
|
key_schedule(key.subarray(0, 8), DES_DECRYPT),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3DES 加/解密一个 8 字节分组
|
||||||
|
const tripledes_crypt = (input, schedule, output) => {
|
||||||
|
const buf = new Uint8Array(8)
|
||||||
|
des_crypt(input, schedule[0], buf)
|
||||||
|
des_crypt(buf, schedule[1], output)
|
||||||
|
des_crypt(output, schedule[2], buf)
|
||||||
|
output.set(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QRC 固定密钥:!@#)(*$%123ZXC!@!@#)(NHL
|
||||||
|
const QRC_KEY = Buffer.from([
|
||||||
|
0x21, 0x40, 0x23, 0x29, 0x28, 0x2a, 0x24, 0x25, 0x31, 0x32, 0x33, 0x5a,
|
||||||
|
0x58, 0x43, 0x21, 0x40, 0x21, 0x40, 0x23, 0x29, 0x28, 0x4e, 0x48, 0x4c,
|
||||||
|
])
|
||||||
|
|
||||||
|
const handleInflate = (data) => new Promise((resolve, reject) => {
|
||||||
|
// Z_SYNC_FLUSH:容忍尾部不完整的 zlib 流(对应原实现忽略 Z_BUF_ERROR 的行为)
|
||||||
|
inflate(data, { finishFlush: constants.Z_SYNC_FLUSH }, (err, result) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 解密腾讯 QRC 歌词。
|
||||||
|
* @param {string} hexData 服务端返回的十六进制字符串
|
||||||
|
* @returns {Promise<string>} 解密并解压后的歌词文本(解析失败时返回空字符串)
|
||||||
|
*/
|
||||||
|
export const decodeQrc = async(hexData) => {
|
||||||
|
if (!hexData || hexData.length % 2 !== 0) return ''
|
||||||
|
const encrypted = Buffer.from(hexData, 'hex')
|
||||||
|
if (encrypted.length === 0) return ''
|
||||||
|
|
||||||
|
const schedule = tripledes_key_setup(QRC_KEY, DES_DECRYPT)
|
||||||
|
|
||||||
|
const block = new Uint8Array(8)
|
||||||
|
for (let i = 0; i + 8 <= encrypted.length; i += 8) {
|
||||||
|
tripledes_crypt(encrypted.subarray(i, i + 8), schedule, block)
|
||||||
|
encrypted.set(block, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await handleInflate(encrypted)
|
||||||
|
return result.toString('utf8')
|
||||||
|
} catch {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -193,6 +193,65 @@ export default {
|
|||||||
return id
|
return id
|
||||||
},
|
},
|
||||||
// 获取歌曲列表内的音乐
|
// 获取歌曲列表内的音乐
|
||||||
|
async getListDetail2(id, tryNum = 0) {
|
||||||
|
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
|
|
||||||
|
const requestObj_listDetail = httpFetch('https://u.y.qq.com/cgi-bin/musicu.fcg', {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
Origin: 'https://y.qq.com',
|
||||||
|
Referer: `https://y.qq.com/n/yqq/playsquare/${id}.html`,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
comm: {
|
||||||
|
cv: 4747474,
|
||||||
|
ct: 24,
|
||||||
|
format: 'json',
|
||||||
|
inCharset: 'utf-8',
|
||||||
|
outCharset: 'utf-8',
|
||||||
|
platform: 'yqq.json',
|
||||||
|
needNewCode: 1,
|
||||||
|
uin: 0,
|
||||||
|
},
|
||||||
|
req_1: {
|
||||||
|
module: 'music.srfDissInfo.aiDissInfo',
|
||||||
|
method: 'uniform_get_Dissinfo',
|
||||||
|
param: {
|
||||||
|
disstid: parseInt(id),
|
||||||
|
userinfo: 1,
|
||||||
|
tag: 1,
|
||||||
|
orderlist: 1,
|
||||||
|
song_begin: 0,
|
||||||
|
song_num: this.limit_song,
|
||||||
|
onlysonglist: 0,
|
||||||
|
enc_host_uin: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const { body } = await requestObj_listDetail.promise
|
||||||
|
// console.log(body)
|
||||||
|
if (body.code !== this.successCode) return this.getListDetail2(id, ++tryNum)
|
||||||
|
if (body.req_1.code !== this.successCode) throw new Error('failed')
|
||||||
|
|
||||||
|
const result = body.req_1.data
|
||||||
|
const dirinfo = result.dirinfo
|
||||||
|
return {
|
||||||
|
list: this.filterListDetail(result.songlist),
|
||||||
|
page: 1,
|
||||||
|
limit: this.limit_song,
|
||||||
|
total: result.songnum,
|
||||||
|
source: 'tx',
|
||||||
|
info: {
|
||||||
|
name: dirinfo.title,
|
||||||
|
img: dirinfo.picurl,
|
||||||
|
desc: decodeName(dirinfo.desc ?? '').replace(/<br>/g, '\n'),
|
||||||
|
author: dirinfo.host_nick,
|
||||||
|
play_count: formatPlayCount(dirinfo.listennum),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取歌曲列表内的音乐
|
||||||
async getListDetail(id, tryNum = 0) {
|
async getListDetail(id, tryNum = 0) {
|
||||||
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
if (tryNum > 2) return Promise.reject(new Error('try max num'))
|
||||||
|
|
||||||
@@ -206,7 +265,9 @@ export default {
|
|||||||
})
|
})
|
||||||
const { body } = await requestObj_listDetail.promise
|
const { body } = await requestObj_listDetail.promise
|
||||||
|
|
||||||
|
// console.log(body)
|
||||||
if (body.code !== this.successCode) return this.getListDetail(id, ++tryNum)
|
if (body.code !== this.successCode) return this.getListDetail(id, ++tryNum)
|
||||||
|
if (body.subcode !== this.successCode || !body.cdlist) return this.getListDetail2(id)
|
||||||
const cdlist = body.cdlist[0]
|
const cdlist = body.cdlist[0]
|
||||||
return {
|
return {
|
||||||
list: this.filterListDetail(cdlist.songlist),
|
list: this.filterListDetail(cdlist.songlist),
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import crypto from 'node:crypto'
|
||||||
|
|
||||||
|
const PART_1_INDEXES = [23, 14, 6, 36, 16, 40, 7, 19]
|
||||||
|
const PART_2_INDEXES = [16, 1, 32, 12, 19, 27, 8, 5]
|
||||||
|
const SCRAMBLE_VALUES = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179]
|
||||||
|
|
||||||
|
async function hashSHA1(data) {
|
||||||
|
return crypto.createHash('sha1').update(data).digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickHashByIdx(hash, indexes) {
|
||||||
|
return indexes.map((idx) => hash[idx]).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64Encode(data) {
|
||||||
|
return Buffer.from(data)
|
||||||
|
.toString('base64')
|
||||||
|
.replace(/[\\/+=]/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function zzcSign(text) {
|
||||||
|
const hash = await hashSHA1(text)
|
||||||
|
const part1 = pickHashByIdx(hash, PART_1_INDEXES)
|
||||||
|
const part2 = pickHashByIdx(hash, PART_2_INDEXES)
|
||||||
|
const part3 = SCRAMBLE_VALUES.map((value, i) => value ^ parseInt(hash.slice(i * 2, i * 2 + 2), 16))
|
||||||
|
const b64Part = base64Encode(part3).replace(/[\\/+=]/g, '')
|
||||||
|
return `zzc${part1}${b64Part}${part2}`.toLowerCase()
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import { httpFetch } from '../../../request'
|
||||||
|
import { zzcSign } from './crypto'
|
||||||
|
|
||||||
|
export const signRequest = async(data) => {
|
||||||
|
// console.log(data)
|
||||||
|
const sign = await zzcSign(JSON.stringify(data))
|
||||||
|
// console.log('sign', sign)
|
||||||
|
return httpFetch(`https://u.y.qq.com/cgi-bin/musics.fcg?sign=${sign}`, {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'QQMusic 14090508(android 12)',
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
}).promise
|
||||||
|
}
|
||||||
@@ -10,12 +10,21 @@ export default {
|
|||||||
page: 0,
|
page: 0,
|
||||||
allPage: 1,
|
allPage: 1,
|
||||||
musicSearch(str, page, limit) {
|
musicSearch(str, page, limit) {
|
||||||
const searchRequest = eapiRequest('/api/cloudsearch/pc', {
|
// const searchRequest = eapiRequest('/api/cloudsearch/pc', {
|
||||||
s: str,
|
// s: str,
|
||||||
type: 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
|
// type: 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
|
||||||
limit,
|
// limit,
|
||||||
total: page == 1,
|
// total: page == 1,
|
||||||
|
// offset: limit * (page - 1),
|
||||||
|
// })
|
||||||
|
const searchRequest = eapiRequest('/api/search/song/list/page', {
|
||||||
|
keyword: str,
|
||||||
|
needCorrect: '1',
|
||||||
|
channel: 'typing',
|
||||||
offset: limit * (page - 1),
|
offset: limit * (page - 1),
|
||||||
|
scene: 'normal',
|
||||||
|
total: page == 1,
|
||||||
|
limit,
|
||||||
})
|
})
|
||||||
return searchRequest.promise.then(({ body }) => body)
|
return searchRequest.promise.then(({ body }) => body)
|
||||||
},
|
},
|
||||||
@@ -30,6 +39,7 @@ export default {
|
|||||||
// console.log(rawList)
|
// console.log(rawList)
|
||||||
if (!rawList) return []
|
if (!rawList) return []
|
||||||
return rawList.map(item => {
|
return rawList.map(item => {
|
||||||
|
item = item.baseInfo.simpleSongData
|
||||||
const types = []
|
const types = []
|
||||||
const _types = {}
|
const _types = {}
|
||||||
let size
|
let size
|
||||||
@@ -87,12 +97,12 @@ export default {
|
|||||||
return this.musicSearch(str, page, limit).then(result => {
|
return this.musicSearch(str, page, limit).then(result => {
|
||||||
// console.log(result)
|
// console.log(result)
|
||||||
if (!result || result.code !== 200) return this.search(str, page, limit, retryNum)
|
if (!result || result.code !== 200) return this.search(str, page, limit, retryNum)
|
||||||
let list = this.handleResult(result.result.songs || [])
|
let list = this.handleResult(result.data.resources || [])
|
||||||
// console.log(list)
|
// console.log(list)
|
||||||
|
|
||||||
if (list == null) return this.search(str, page, limit, retryNum)
|
if (list == null) return this.search(str, page, limit, retryNum)
|
||||||
|
|
||||||
this.total = result.result.songCount || 0
|
this.total = result.data.totalCount || 0
|
||||||
this.page = page
|
this.page = page
|
||||||
this.allPage = Math.ceil(this.total / this.limit)
|
this.allPage = Math.ceil(this.total / this.limit)
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ import useTaskActions from './useTaskActions'
|
|||||||
import useMusicAdd from './useMusicAdd'
|
import useMusicAdd from './useMusicAdd'
|
||||||
import { downloadStatus } from '@renderer/store/download/state'
|
import { downloadStatus } from '@renderer/store/download/state'
|
||||||
import { appSetting } from '@renderer/store/setting'
|
import { appSetting } from '@renderer/store/setting'
|
||||||
|
import { formatMusicName } from '@renderer/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
@@ -203,7 +204,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getName = (downloadInfo) => {
|
const getName = (downloadInfo) => {
|
||||||
return appSetting['download.fileName'].replace('歌名', downloadInfo.metadata.musicInfo.name).replace('歌手', downloadInfo.metadata.musicInfo.singer)
|
return formatMusicName(appSetting['download.fileName'], downloadInfo.metadata.musicInfo.name, downloadInfo.metadata.musicInfo.singer)
|
||||||
}
|
}
|
||||||
const getTypeName = (quality) => {
|
const getTypeName = (quality) => {
|
||||||
return quality == 'flac24bit' ? 'FLAC Hires' : quality?.toUpperCase()
|
return quality == 'flac24bit' ? 'FLAC Hires' : quality?.toUpperCase()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { dialog } from '@renderer/plugins/Dialog'
|
|||||||
import { useI18n } from '@renderer/plugins/i18n'
|
import { useI18n } from '@renderer/plugins/i18n'
|
||||||
import { removeListMusics } from '@renderer/store/list/action'
|
import { removeListMusics } from '@renderer/store/list/action'
|
||||||
import { appSetting } from '@renderer/store/setting'
|
import { appSetting } from '@renderer/store/setting'
|
||||||
import { toOldMusicInfo } from '@renderer/utils/index'
|
import { formatMusicName, toOldMusicInfo } from '@renderer/utils/index'
|
||||||
import { addDislikeInfo, hasDislike } from '@renderer/core/dislikeList'
|
import { addDislikeInfo, hasDislike } from '@renderer/core/dislikeList'
|
||||||
import { playNext } from '@renderer/core/player'
|
import { playNext } from '@renderer/core/player'
|
||||||
import { playMusicInfo } from '@renderer/store/player/state'
|
import { playMusicInfo } from '@renderer/store/player/state'
|
||||||
@@ -34,7 +34,7 @@ export default ({ props, list, selectedList, removeAllSelect }) => {
|
|||||||
|
|
||||||
const handleCopyName = index => {
|
const handleCopyName = index => {
|
||||||
const minfo = list.value[index]
|
const minfo = list.value[index]
|
||||||
clipboardWriteText(appSetting['download.fileName'].replace('歌名', minfo.name).replace('歌手', minfo.singer))
|
clipboardWriteText(formatMusicName(appSetting['download.fileName'], minfo.name, minfo.singer))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDislikeMusic = async(index) => {
|
const handleDislikeMusic = async(index) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DOWNLOAD_STATUS, QUALITYS } from '@common/constants'
|
|||||||
import { filterFileName } from '@common/utils/common'
|
import { filterFileName } from '@common/utils/common'
|
||||||
import { buildLyrics } from './lrcTool'
|
import { buildLyrics } from './lrcTool'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { clipFileNameLength, clipNameLength } from '@common/utils/tools'
|
import { clipFileNameLength, clipNameLength, formatMusicName } from '@common/utils/tools'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存歌词文件
|
* 保存歌词文件
|
||||||
@@ -14,7 +14,7 @@ export const saveLrc = async(lrcData: LX.Music.LyricInfo, info: {
|
|||||||
downloadTlrc: boolean
|
downloadTlrc: boolean
|
||||||
downloadRlrc: boolean
|
downloadRlrc: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const iconv = await import('iconv-lite')
|
const iconv = (await import('iconv-lite')).default
|
||||||
const lrc = buildLyrics(lrcData, info.downloadLxlrc, info.downloadTlrc, info.downloadRlrc)
|
const lrc = buildLyrics(lrcData, info.downloadLxlrc, info.downloadTlrc, info.downloadRlrc)
|
||||||
switch (info.format) {
|
switch (info.format) {
|
||||||
case 'gbk':
|
case 'gbk':
|
||||||
@@ -91,9 +91,7 @@ export const createDownloadInfo = (musicInfo: LX.Music.MusicInfoOnline, type: LX
|
|||||||
ext,
|
ext,
|
||||||
filePath: '',
|
filePath: '',
|
||||||
listId,
|
listId,
|
||||||
fileName: filterFileName(`${clipFileNameLength(fileName
|
fileName: filterFileName(`${clipFileNameLength(formatMusicName(fileName, musicInfo.name, clipNameLength(musicInfo.singer)))}.${ext}`),
|
||||||
.replace('歌名', musicInfo.name)
|
|
||||||
.replace('歌手', clipNameLength(musicInfo.singer)))}.${ext}`),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// downloadInfo.metadata.filePath = joinPath(savePath, downloadInfo.metadata.fileName)
|
// downloadInfo.metadata.filePath = joinPath(savePath, downloadInfo.metadata.fileName)
|
||||||
|
|||||||
@@ -243,9 +243,24 @@ export const filterDuplicateMusic = async(list: LX.Music.MusicInfo[], isFilterVa
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const searchListMusic = (list: LX.Music.MusicInfo[], text: string) => {
|
export const searchListMusic = (list: LX.Music.MusicInfo[], text: string) => {
|
||||||
|
const fullMathNameResults = new Set<LX.Music.MusicInfo>()
|
||||||
|
const fullMathSingerResults = new Set<LX.Music.MusicInfo>()
|
||||||
|
const fullMathAlbumResults = new Set<LX.Music.MusicInfo>()
|
||||||
|
const textLower = text.toLowerCase()
|
||||||
|
for (const mInfo of list) {
|
||||||
|
if (mInfo.name?.toLowerCase().includes(textLower)) {
|
||||||
|
fullMathNameResults.add(mInfo)
|
||||||
|
} else if (mInfo.singer?.toLowerCase().includes(textLower)) {
|
||||||
|
fullMathSingerResults.add(mInfo)
|
||||||
|
} else if (mInfo.meta.albumName?.toLowerCase().includes(textLower)) {
|
||||||
|
fullMathAlbumResults.add(mInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
let result: LX.Music.MusicInfo[] = []
|
let result: LX.Music.MusicInfo[] = []
|
||||||
let rxp = new RegExp(text.split('').map(s => s.replace(/[.*+?^${}()|[\]\\]/, '\\$&')).join('.*') + '.*', 'i')
|
let rxp = new RegExp(text.split('').map(s => s.replace(/[.*+?^${}()|[\]\\]/, '\\$&')).join('.*') + '.*', 'i')
|
||||||
for (const mInfo of list) {
|
for (const mInfo of list) {
|
||||||
|
if (fullMathNameResults.has(mInfo) || fullMathSingerResults.has(mInfo) || fullMathAlbumResults.has(mInfo)) continue
|
||||||
|
|
||||||
const str = `${mInfo.name}${mInfo.singer}${mInfo.meta.albumName ? mInfo.meta.albumName : ''}`
|
const str = `${mInfo.name}${mInfo.singer}${mInfo.meta.albumName ? mInfo.meta.albumName : ''}`
|
||||||
if (rxp.test(str)) result.push(mInfo)
|
if (rxp.test(str)) result.push(mInfo)
|
||||||
}
|
}
|
||||||
@@ -258,7 +273,12 @@ export const searchListMusic = (list: LX.Music.MusicInfo[], text: string) => {
|
|||||||
data: mInfo,
|
data: mInfo,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return sortedList.map(item => item.data).reverse()
|
return [
|
||||||
|
...fullMathNameResults.values(),
|
||||||
|
...fullMathSingerResults.values(),
|
||||||
|
...fullMathAlbumResults.values(),
|
||||||
|
...sortedList.map(item => item.data).reverse(),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +324,7 @@ export const createLocalMusicInfos = async(filePaths: string[]): Promise<LX.Musi
|
|||||||
* @param isMerge 是否合并
|
* @param isMerge 是否合并
|
||||||
*/
|
*/
|
||||||
export const exportPlayListToText = async(savePath: string, lists: Array<LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull>, isMerge: boolean) => {
|
export const exportPlayListToText = async(savePath: string, lists: Array<LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull>, isMerge: boolean) => {
|
||||||
const iconv = await import('iconv-lite')
|
const iconv = (await import('iconv-lite')).default
|
||||||
|
|
||||||
if (isMerge) {
|
if (isMerge) {
|
||||||
await saveStrToFile(savePath,
|
await saveStrToFile(savePath,
|
||||||
@@ -328,7 +348,7 @@ export const exportPlayListToCSV = async(savePath: string,
|
|||||||
lists: Array<LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull>,
|
lists: Array<LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull>,
|
||||||
isMerge: boolean,
|
isMerge: boolean,
|
||||||
header: string) => {
|
header: string) => {
|
||||||
const iconv = await import('iconv-lite')
|
const iconv = (await import('iconv-lite')).default
|
||||||
|
|
||||||
const filterStr = (str: string) => {
|
const filterStr = (str: string) => {
|
||||||
if (!str) return ''
|
if (!str) return ''
|
||||||
|
|||||||
+2
-2
@@ -5,10 +5,10 @@
|
|||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"rootDir": "./",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */
|
|
||||||
// "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "@common/*": ["common/*"],
|
// "@common/*": ["common/*"],
|
||||||
// "@renderer/*": ["renderer/*"],
|
// "@renderer/*": ["renderer/*"],
|
||||||
|
|||||||
Reference in New Issue
Block a user