Compare commits

..

1 Commits

Author SHA1 Message Date
lyswhut
3fe34545b9 添加自定义源二进制数据传输支持 2023-05-13 11:53:56 +08:00
465 changed files with 11042 additions and 17320 deletions

119
.eslintrc Normal file
View File

@@ -0,0 +1,119 @@
{
"root": true,
"extends": [
"standard"
],
"plugins": [
"html"
],
"parser": "@babel/eslint-parser",
"parserOptions": {
// "requireConfigFile": false
},
"rules": {
"no-new": "off",
"camelcase": "off",
"no-return-assign": "off",
"space-before-function-paren": ["error", "never"],
"no-var": "error",
"no-fallthrough": "off",
"prefer-promise-reject-errors": "off",
"eqeqeq": "off",
"no-multiple-empty-lines": [1, {"max": 2}],
"comma-dangle": [2, "always-multiline"],
"standard/no-callback-literal": "off",
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off"
},
"ignorePatterns": ["vendors", "*.min.js", "dist"],
"overrides": [
{
"files": [ "*.vue" ],
"rules": {
"no-new": "off",
"camelcase": "off",
"no-return-assign": "off",
"space-before-function-paren": ["error", "never"],
"no-var": "error",
"no-fallthrough": "off",
"prefer-promise-reject-errors": "off",
"eqeqeq": "off",
"no-multiple-empty-lines": [1, {"max": 2}],
"comma-dangle": [2, "always-multiline"],
"standard/no-callback-literal": "off",
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off",
"vue/multi-word-component-names": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/ban-ts-comment": "off",
"vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/use-v-on-exact": "off",
"@typescript-eslint/restrict-template-expressions": "off",
// "no-undef": "off"
},
"parser": "vue-eslint-parser",
"extends": [
"plugin:vue/base",
// "plugin:vue/strongly-recommended"
"plugin:vue/vue3-recommended",
"standard-with-typescript",
],
"parserOptions": {
"sourceType": "module",
"parser": {
// Script parser for `<script>`
"js": "@typescript-eslint/parser",
// Script parser for `<script lang="ts">`
"ts": "@typescript-eslint/parser"
},
"extraFileExtensions": [".vue"]
}
},
{
"files": [ "*.ts" ],
"rules": {
"no-new": "off",
"camelcase": "off",
"no-return-assign": "off",
"space-before-function-paren": ["error", "never"],
"no-var": "error",
"no-fallthrough": "off",
"prefer-promise-reject-errors": "off",
"eqeqeq": "off",
"no-multiple-empty-lines": [1, {"max": 2}],
"comma-dangle": [2, "always-multiline"],
"standard/no-callback-literal": "off",
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/restrict-template-expressions": [1, {
"allowBoolean": true
}],
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/return-await": "off",
"@typescript-eslint/ban-ts-comment": "off",
"multiline-ternary": "off",
"@typescript-eslint/comma-dangle": "off",
},
"parser": "@typescript-eslint/parser",
"extends": [
"standard-with-typescript"
],
"parserOptions": {
"project": "./src/**/tsconfig.json"
}
}
]
}

View File

@@ -1,98 +0,0 @@
const baseRule = {
'no-new': 'off',
camelcase: 'off',
'no-return-assign': 'off',
'space-before-function-paren': ['error', 'never'],
'no-var': 'error',
'no-fallthrough': 'off',
eqeqeq: 'off',
'require-atomic-updates': ['error', { allowProperties: true }],
'no-multiple-empty-lines': [1, { max: 2 }],
'comma-dangle': [2, 'always-multiline'],
'standard/no-callback-literal': 'off',
'prefer-const': 'off',
'no-labels': 'off',
'node/no-callback-literal': 'off',
'multiline-ternary': 'off',
}
const typescriptRule = {
...baseRule,
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/restrict-template-expressions': [1, {
allowBoolean: true,
allowAny: true,
}],
'@typescript-eslint/restrict-plus-operands': [1, {
allowBoolean: true,
allowAny: true,
}],
'@typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: {
arguments: false,
attributes: false,
},
},
],
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/comma-dangle': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
}
const vueRule = {
...typescriptRule,
'vue/multi-word-component-names': 'off',
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/use-v-on-exact': 'off',
}
exports.base = {
extends: ['standard'],
rules: baseRule,
parser: '@babel/eslint-parser',
}
exports.html = {
files: ['*.html'],
plugins: ['html'],
}
exports.typescript = {
files: ['*.ts'],
rules: typescriptRule,
parser: '@typescript-eslint/parser',
extends: [
'standard-with-typescript',
],
}
exports.vue = {
files: ['*.vue'],
rules: vueRule,
parser: 'vue-eslint-parser',
extends: [
// 'plugin:vue/vue3-essential',
'plugin:vue/base',
'plugin:vue/vue3-recommended',
'plugin:vue-pug/vue3-recommended',
// "plugin:vue/strongly-recommended"
'standard-with-typescript',
],
parserOptions: {
sourceType: 'module',
parser: {
// Script parser for `<script>`
js: '@typescript-eslint/parser',
// Script parser for `<script lang="ts">`
ts: '@typescript-eslint/parser',
},
extraFileExtensions: ['.vue'],
},
}

View File

@@ -1,20 +0,0 @@
const { base, typescript } = require('./.eslintrc.base.cjs')
module.exports = {
root: true,
...base,
overrides: [
{
...typescript,
parserOptions: {
project: './tsconfig.json',
},
},
],
ignorePatterns: [
'node_modules',
'*.min.js',
'dist',
'build',
],
}

View File

@@ -1,26 +0,0 @@
name: Setup
description: Setup Node Env
runs:
using: composite
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '16'
- name: Cache node modules
id: cache-npm
uses: actions/cache@v4
with:
path: |
${{ env.NPM_CACHE }}
${{ env.ELECTRON_CACHE }}
${{ env.ELECTRON_BUILDERCACHE }}
key: ${{ runner.os }}-node-modules-cache-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-modules-cache-
- name: Install dependencies
run: npm ci
shell: bash

View File

@@ -6,117 +6,92 @@ on:
- beta
jobs:
# CheckCode:
# name: Lint Code
# runs-on: ubuntu-latest
# steps:
# - name: Check out git repository
# uses: actions/checkout@v4
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '16'
# - name: Cache file
# uses: actions/cache@v4
# with:
# path: |
# node_modules
# $HOME/.cache/electron
# $HOME/.cache/electron-builder
# $HOME/.npm/_prebuilds
# key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-build-
# - name: Install Dependencies
# run: |
# npm ci
# - name: Lint src code
# run: npm run lint
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
%APPDATA%\npm-cache
%LOCALAPPDATA%\electron\Cache
%LOCALAPPDATA%\electron-builder\Cache
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm ci
- name: Build src code
run: npm run build
run: npm run build:src
- name: Build Package Setup x64
run: npm run pack:win:setup:x64
- name: Upload Artifact Setup x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-x64-Setup
path: build/*-x64-Setup.exe
path: build/* x64 Setup.exe
- name: Build Package 7z x64
run: npm run pack:win:7z:x64
- name: Upload Artifact 7z x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-win_x64-green
path: build/*win_x64-green.7z
path: build/*win_x64 green.7z
- name: Build Package Setup x86
run: npm run pack:win:setup:x86
- name: Upload Artifact Setup x86
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-x86-Setup
path: build/* x86 Setup.exe
- name: Build Package 7z x86
run: npm run pack:win:7z:x86
- name: Upload Artifact 7z x86
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-win_x86-green
path: build/*win_x86 green.7z
- name: Build Package Setup arm64
run: npm run pack:win:setup:arm64
- name: Upload Artifact Setup arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-arm64-Setup
path: build/*-arm64-Setup.exe
path: build/* arm64 Setup.exe
- name: Build Package 7z arm64
run: npm run pack:win:7z:arm64
- name: Upload Artifact 7z arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-win_arm64-green
path: build/*win_arm64-green.7z
path: build/*win_arm64 green.7z
- name: Install old electron
run: npm install electron@22
- name: Install python setuptools
run: pip.exe install setuptools
- name: Build Package win7 Setup x64
run: npm run pack:win7:setup:x64
- name: Upload Artifact win7 Setup x64
uses: actions/upload-artifact@v4
- name: Build Package Setup x86_64
run: npm run pack:win:setup:x86_64
- name: Upload Artifact Setup x86_64
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-win7_x64-Setup
path: build/*win7_x64-Setup.exe
- name: Build Package win7 7z x64
run: npm run pack:win7:7z:x64
- name: Upload Artifact win7 7z x64
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-win7_x64-green
path: build/*win7_x64-green.7z
- name: Build Package win7 7z x86
run: npm run pack:win7:7z:x86
- name: Upload Artifact win7 7z x86
uses: actions/upload-artifact@v4
with:
name: lx-music-desktop-win7_x86-green
path: build/*win7_x86-green.7z
name: lx-music-desktop-x86_64-Setup
path: build/*x86_64 Setup.exe
- name: Generate file MD5
run: |
@@ -126,38 +101,51 @@ jobs:
Mac:
name: Mac
runs-on: macos-latest
# needs: CheckCode
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install python setuptools
run: python3 -m pip install setuptools
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm ci
- name: Build src code
run: npm run build
run: npm run build:src
- name: Build Package dmg
run: |
npm run pack:mac:dmg
npm run pack:mac:dmg:arm64
env:
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
- name: Upload Artifact dmg
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-mac-dmg
path: |
build/*.dmg
!build/*-arm64.dmg
- name: Upload Artifact dmg
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-mac-dmg-arm64
path: build/*-arm64.dmg
@@ -170,71 +158,84 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Install package
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm ci
- name: Build src code
run: npm run build
run: npm run build:src
- name: Build Package deb amd64
run: npm run pack:linux:deb:amd64
- name: Upload Artifact deb amd64
uses: actions/upload-artifact@v4
- name: Build Package deb x64
run: npm run pack:linux:deb:x64
- name: Upload Artifact deb x64
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-deb-amd64
path: build/*_amd64.deb
name: lx-music-desktop-deb-x64
path: build/* x64.deb
- name: Build Package deb arm64
run: npm run pack:linux:deb:arm64
- name: Upload Artifact deb arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-deb-arm64
path: build/*_arm64.deb
path: build/* arm64.deb
- name: Build Package deb armv7l
run: npm run pack:linux:deb:armv7l
- name: Upload Artifact deb armv7l
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-deb-armv7l
path: build/*_armv7l.deb
path: build/* armv7l.deb
- name: Build Package x64 appImage
run: npm run pack:linux:appImage
- name: Upload Artifact x64 appImage
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-x64-appImage
path: build/*_x64.AppImage
path: build/* x64.AppImage
- name: Build Package x64 rpm
run: npm run pack:linux:rpm
- name: Upload Artifact x64 rpm
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-x64-rpm
path: build/*.x64.rpm
path: build/* x64.rpm
- name: Build Package x64 pacman
run: npm run pack:linux:pacman
- name: Upload Artifact x64 pacman
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: lx-music-desktop-x64-pacman
path: build/*_x64.pacman
path: build/* x64.pacman
- name: Generate file MD5
run: |

View File

@@ -1,28 +0,0 @@
name: Run build test
on:
pull_request:
branches:
- dev
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '16'
- name: Install Dependencies
run: npm ci
- name: Eslint check
run: npm run lint
- name: Test Build
run: npm run build

View File

@@ -1,16 +0,0 @@
name: Publish NPM Version Info
on:
release:
types: [published]
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.PAT }}
repository: lyswhut/lx-music-desktop-version-info
event-type: npm-release

View File

@@ -6,99 +6,46 @@ on:
- master
jobs:
# CheckCode:
# name: Lint Code
# runs-on: ubuntu-latest
# steps:
# - name: Check out git repository
# uses: actions/checkout@v4
# - name: Install Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '16'
# - name: Cache file
# uses: actions/cache@v4
# with:
# path: |
# node_modules
# $HOME/.cache/electron
# $HOME/.cache/electron-builder
# $HOME/.npm/_prebuilds
# key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-build-
# - name: Install Dependencies
# run: |
# npm ci
# - name: Lint src code
# run: npm run lint
Windows:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
%APPDATA%\npm-cache
%LOCALAPPDATA%\electron\Cache
%LOCALAPPDATA%\electron-builder\Cache
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm ci
- name: Build src code
run: npm run build
run: npm run build:src
- name: Release package
run: |
npm run publish:win:setup:x64:always
npm run publish:win:7z:x64
npm run publish:win:7z:arm64
npm run publish:win:setup:x86
npm run publish:win:7z:x86
npm run publish:win:setup:arm64
npm run publish:win:setup:x64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
- name: Generate file MD5
run: |
cd build
Get-FileHash *.exe,*.7z -Algorithm MD5 | Format-List
Windows_7:
name: Windows
runs-on: windows-latest
env:
NPM_CACHE: '%APPDATA%\npm-cache'
ELECTRON_CACHE: '%LOCALAPPDATA%\electron\Cache'
ELECTRON_BUILDERCACHE: '%LOCALAPPDATA%\electron-builder\Cache'
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Build src code
run: npm run build
- name: Prepare win7 electron env
run: |
npm install electron@22
pip.exe install setuptools
- name: Release win7 package
run: |
npm run publish:win7:setup:x64
npm run publish:win7:7z:x64
npm run publish:win7:7z:x86
npm run publish:win:7z:arm64
npm run publish:win:setup:x86_64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
@@ -111,29 +58,41 @@ jobs:
Mac:
name: Mac
runs-on: macos-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Install python3 setuptools
run: python3 -m pip install setuptools
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm ci
- name: Build src code
run: npm run build
run: npm run build:src
- name: Release package
run: |
npm run publish:mac:dmg
npm run publish:mac:dmg:always
npm run publish:mac:dmg:arm64
env:
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BT_TOKEN: ${{ secrets.BT_TOKEN }}
@@ -145,27 +104,40 @@ jobs:
Linux:
name: Linux
runs-on: ubuntu-latest
env:
NPM_CACHE: $HOME/.npm
ELECTRON_CACHE: $HOME/.cache/electron
ELECTRON_BUILDERCACHE: $HOME/.cache/electron-builder
# needs: CheckCode
steps:
- name: Install package
run: sudo apt-get update && sudo apt-get install -y rpm libarchive-tools
- name: Check out git repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup Node Env
uses: ./.github/actions/setup
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Cache file
uses: actions/cache@v3
with:
path: |
node_modules
$HOME/.cache/electron
$HOME/.cache/electron-builder
$HOME/.npm/_prebuilds
key: ${{ runner.os }}-build-caches-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-
- name: Install Dependencies
run: |
npm ci
- name: Build src code
run: npm run build
run: npm run build:src
- name: Release package
run: |
npm run publish:linux:deb:amd64
npm run publish:linux:deb:x64:always
npm run publish:linux:deb:arm64
npm run publish:linux:deb:armv7l
npm run publish:linux:appImage

View File

@@ -5,25 +5,13 @@ module.exports = {
'chalk',
'del',
'comlink',
'vue',
'image-size',
'message2call',
'@types/ws',
// 'eslint-config-standard-with-typescript',
// 'typescript', // https://github.com/microsoft/TypeScript/pull/54567
],
// target: 'newest',
// filter: [
// 'electron-builder',
// 'electron-updater',
// ],
// target: 'patch',
// filter: [
// 'vue',
// ],
// target: 'minor',
// filter: [
// 'electron',

View File

@@ -6,181 +6,6 @@ Project versioning adheres to [Semantic Versioning](http://semver.org/).
Commit convention is based on [Conventional Commits](http://conventionalcommits.org).
Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
## [2.6.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.5.0...v2.6.0) - 2024-02-01
提交祝大家新年快乐!
更新前需要注意:
由于自定义源的调用方式变更可能会导致某些第三方源停止工作如果出现这种情况你需要将LX回退到 v2.5.0
### 新增
- 若自定义源初始化失败,将会出现弹窗提示初始化失败的详情
- 添加win7_x64架构的安装版安装包构建
- 新增播放歌曲时阻止电脑休眠,默认启用,可到设置-播放设置关闭(#1563
### 优化
- 更新zh-tw翻译
- 自定义源列显示源版本号、作者名字
- 优化列表全选机制,修复列表未获得焦点时仍然可以全选的问题
- 优化搜索框交互逻辑,防止鼠标操作时意外搜索候选列表的内容
- 添加对wy源某些歌曲有问题的歌词进行修复
- 改进本地音乐在线信息的匹配机制
- 优化任务下载状态显示,现在下载时若数据传输完成但数据写入未完成时会显示相应的状态
- 添加对下载歌曲时封面图片大小的控制处理(#1609
- 添加创建同名列表时的二次确认(#1621
### 修复
- 修复备份文件无法导入json格式的问题
- Windows、MacOS平台下的字体列表取消使用原生方式获取以修复某些字体应用后无效的问题#1596
- 修复亮暗主题自动切换功能无效的问题(#1697
- 修复 MacOS 平台在 Finder 打开文件或目录时应用卡死的问题(#1684
- 修复下载模块在数据写入速度较慢的情况下出现任务及文件异常的问题
- 修复临时列表变更会意外触发同步的问题
- 修复最小化后再隐藏窗口时,托盘菜单的显示主界面功能异常的问题
### 变更
- 播放歌曲时默认会阻止系统进入休眠状态,若你不行软件阻止系统休眠,可以到设置-播放设置取消勾选“播放歌曲时阻止电脑休眠”设置
### 其他
- 移除所有内置源由于收到腾讯投诉要求停止提供软件内置的连接到他们平台的在线播放及下载服务所以从即日2023年10月18日起LX本身不再提供上述服务
- 更新 electron 到 v25.9.8
- 更新许可协议的排版,使其看起来更加清晰明了,更新数据来源原理说明
### 自定义源的不兼容变更与新增内容(源开发者需要看)
自定义源的调用方式已改变:
- 为了与移动端的调用方式统一,不再推荐使用 `window.lx` 对象(移动端无`window`对象),改用 `globalThis.lx`
- `inited` 事件不再需要传递 `status` 属性,脚本运行过程中,在成功调用 `inited` 事件之前的任何首次未捕获的错误都将视为初始化失败,所以现在若想人为让脚本初始化失败,直接抛出一个错误即可
- 新增 `globalThis.lx.env` 属性,桌面端环境固定为 `desktop`,移动端环境固定为 `mobile`
- 新增 `globalThis.lx.currentScriptInfo` 对象,可以从这里获取解析后的脚本头部注释信息及脚本原始内容,具体可用属性看文档说明
- `globalThis.lx.version` 属性更新到 `2.0.0`
- 自定义源不再使用`script`标签的形式执行,若要获取脚本原始代码字符串需从 `globalThis.lx.currentScriptInfo.rawScript` 属性获取
- 自定义源新增支持`local`源的`musicUrl``pic``lyric`的获取操作详情看自定义源文档说明
## [2.5.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.4.1...v2.5.0) - 2023-09-28
落雪提前祝大家中秋快乐~🥮😘!
### 不兼容性变更
- 由于微软及Electron即将结束对 Windows 7、Windows 8 的支持所以从这个版本起LX的默认 Windows 版也不再支持这些版本的系统,但考虑到仍然有许多人使用 Windows 7我们特别构建了能在 Windows 7 上使用的免安装版文件名带win7需要注意的是这个版本将缺乏安全更新若非必要情况不要使用该版本
- 由于微软在 Windows 10 2004版本已删除对32位的OEM支持所以在这个版本起LX的默认 Windows 版已不再提供32位的支持
- 更改构建的文件名格式主要修改linux下deb、rpm文件命名格式
### 新增
- 新增Scheme URL对播放器的控制操作新增的操作包含 播放、暂停、下一首、上一首等详情看Scheme URL文档
### 优化
- 通过歌曲菜单添加不喜欢歌曲时需要二次确认防止手抖
### 修复
- 修复音频输出设备设置在重启软件后被重置的问题(#1568
- 修复更换语言设置后源名称未更新的问题
- 修复点击搜索、排行榜等在线列表歌曲右键菜单歌曲详情页会意外将该歌曲添加不喜欢的问题
### 其他
- 更新 electron 到 v25.8.3
## [2.4.1](https://github.com/lyswhut/lx-music-desktop/compare/v2.4.0...v2.4.1) - 2023-09-09
目前本项目的原始发布地址只有 **GitHub****蓝奏网盘** ,其他渠道均为第三方转载发布,可信度请自行鉴别。
本项目无微信公众号之类的官方账号,谨防被骗。
### 修复
- 修复 v2.4.0 的默认数据库版本号不对导致首次安装该版本的用户无法再次启动软件的问题
## [2.4.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.3.0...v2.4.0) - 2023-09-09
目前本项目的原始发布地址只有 **GitHub****蓝奏网盘** ,其他渠道均为第三方转载发布,可信度请自行鉴别。
本项目无微信公众号之类的官方账号,谨防被骗。
### 不兼容性变更
该版本修改了同步协议逻辑同步功能至少需要PC端v2.4.0或移动端v1.1.0版本或同步服务v2.0.0才能连接使用。
### 新增
- 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440
- 新增数据同步服务端模式已认证设备列表管理,该功能位置:设置-数据同步-服务端模式-已认证设备列表
- 新增“不喜欢歌曲”功能,可以在我的列表或者在线列表内歌曲的右击菜单使用,还可以去“设置-其他”手动编辑不喜欢规则,注:“上一曲”、“下一曲”功能将跳过符合“不喜欢歌曲”规则的歌曲,但你仍可以手动播放这些歌曲
- 新增同步功能对“不喜欢歌曲”列表的同步
- 新增软件内快捷键“不喜欢该歌曲”设置,全局快捷键“收藏歌曲”、“取消收藏”、“不喜欢该歌曲”设置
- 新增设置-播放设置-点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)选项,默认关闭
### 优化
- 优化音效设置-环境音效启用、禁用时的操作效果显示,修复禁用环境音效时仍然可以调整增益、新增预设的问题
- 过滤翻译歌词或罗马音歌词中只有“//”的行(#1499
- 点击打开歌单弹窗背景将不再自动关闭弹窗,防止选择输入框里的内容时意外关闭弹窗
- 优化数据传输逻辑,列表同步指令使用队列机制,保证列表同步操作的顺序
- 优化桌面歌词在开启 缩放当前播放的歌词 并关闭 延迟歌词滚动 时的歌词滚动位置计算问题,现在歌词滚动应该可以正确滚动到目标位置了
- 优化歌词在短时间内快速播放时的滚动效果,现在遇到这种情况时滚动将更平滑
### 修复
- 修复字体设置某些字体无法应用的问题
- 修复搜索提示功能失效的问题(#1452, @Folltoshe
- 修复我的列表名右键菜单-排序歌曲按专辑名排序无效的问题(#1440
- 修复若路径存在 # 字符时,软件无法启动的问题
- 修复搜索框在某些情况下输入内容后搜索时会自动清空的问题(#1472
- 修复某些tx源歌词因数据异常解析失败的问题
- 修复windows平台下隐藏窗口后再显示时任务栏按钮丢失的问题
- 修复首句歌词被提前播放的问题
- 修复潜在导致列表数据不同步的问题
- 修复kg无评论时的加载处理问题
### 变更
- 播放模式应该只适用于列表内的歌曲,所以单曲循环模式不应对“稍后播放”的歌曲有效,该行为现在与移动端一致
- 随机模式下,通过点击与播放列表相同的列表切歌时,将不再清空已播放列表,即已播放的歌曲不再重新参与随机,若想恢复之前的行为可以去设置-播放设置启用清空已播放列表选项
### 其他
- 更新 electron 到 v22.3.23
- 重构同步服务端功能部分代码,使其更易扩展新功能
## [2.3.0](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.2...v2.3.0) - 2023-06-29
### 新增
- 新增音效设置实验性功能支持10段均衡器设置、内置的一些环境混响音效、音调升降调节、3D立体环绕音效由于升降调需要实时处理音频数据这会导致额外的CPU占用已知问题如果CPU资源不够时将处理导致任务堆积而出现声音异常这时需要暂停播放一段时间等堆积的任务处理完毕再播放
- 播放速率设置面板新增是否音调补偿设置,在调整播放速率后,可以选择是否启用音调补偿,默认启用
### 优化
- Windows、MacOS平台下的字体列表改用原生方式获取现在Windows平台下能显示当前已安装的更多类型字体了MacOS平台未测可用性未知
- 移除桌面歌词窗口透明边距在Linux下的桌面歌词可以完全拖到贴合屏幕边缘了
- 过滤嵌入、下载的翻译、罗马音歌词时间标签,与主歌词时间不匹配的歌词将被丢弃,防止出现原歌词与翻译歌词顺序错乱的问题(#1358
### 修复
- 修复列表名翻译显示
- 修复因插入数字类型的ID导致其意外在末尾追加 .0 导致列表数据异常的问题,同时也可能导致同步数据丢失的问题(要完全修复这个问题还需要同时将移动端、同步服务更新到最新版本)
- 修复下载时出现302错误的问题
- 修复播放某些在线音频会没有声音的问题
- 修复改变播放速率时会导致歌词报错的问题
- 修复tx热门评论昵称被错误切割的问题 (#1397, By: @helloplhm-qwq, @Folltoshe)
- 修复wy源热搜词失效的问题#1401, @Folltoshe
- 修复Deepin 20下启用桌面歌词时可能会导致桌面卡死的问题#1288
- 修复添加单首歌曲弹窗列表创建按钮无法取消的问题
- 修复mg歌单搜索歌单播放数量显示问题
- 修复tx翻译歌词解析丢失的问题更新版本后需手动清理一次歌词缓存
### 其他
- 更新 electron 到 v22.3.15
## [2.2.2](https://github.com/lyswhut/lx-music-desktop/compare/v2.2.1...v2.2.2) - 2023-05-01
### 修复

View File

@@ -52,8 +52,6 @@
目前本项目的原始发布地址只有**GitHub**及**蓝奏网盘**,其他渠道均为第三方转载发布,与本项目无关!
为了提高使用门槛本软件内的默认设置、UI操作不以新手友好为目标所以使用前建议先根据你的喜好浏览调整一遍软件设置阅读一遍[音乐播放列表机制](https://lyswhut.github.io/lx-music-doc/desktop/faq/playlist)及[可用的鼠标、键盘快捷操作](https://lyswhut.github.io/lx-music-doc/desktop/faq/hotkey)
#### Scheme URL支持
从v1.17.0起支持 Scheme URL可以使用此功能从浏览器等场景下调用LX Music我们开发了一个[油猴脚本](https://github.com/lyswhut/lx-music-script#readme)配套使用,<br>
@@ -91,7 +89,25 @@
### 源码使用方法
已迁移至:<https://lyswhut.github.io/lx-music-doc/desktop/use-source-code>
环境要求Node.js 16+
```bash
# 开发模式
npm run dev
# 构建免安装版
npm run pack:dir
# 构建安装包Windows版
npm run pack:win
# 构建安装包Mac版
npm run pack:mac
# 构建安装包Linux版
npm run pack:linux
```
### UI界面
@@ -113,58 +129,23 @@
1. 参照[源码使用方法](https://lyswhut.github.io/lx-music-doc/desktop/use-source-code)设置开发环境
2. 克隆本仓库代码并切换到`dev`分支开发
3. 提交PR`dev`分支
3. 提交PR
### 项目协议
本项目基于 [Apache License 2.0](https://github.com/lyswhut/lx-music-desktop/blob/master/LICENSE) 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
---
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
#### 一、数据来源
1. 本项目的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的准确性负责。
2. 使用本项目的过程中可能会产生版权数据,对于这些版权数据,本项目不拥有它们的所有权,为了避免造成侵权,使用者务必在**24小时**内清除使用本项目的过程中所产生的版权数据。
3. 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意,如果官方音乐平台觉得不妥,可联系本项目更改或移除。
4. 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网,如果出现侵权可联系本项目移除。
5. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
6. 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本项目不对项目内的技术可能存在违反当地法律法规的行为作保证,**禁止在违反当地法律法规的情况下使用本项目**,对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据与未登录状态在官方平台APP获取的数据相同经过对数据简单地筛选与合并后进行展示因此本项目不对数据的合法性、准确性负责
若你使用了本项目,将代表你接受以上协议
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“音乐来源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名字、歌手名字等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题。
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责。
#### 二、版权数据
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在**24小时内**清除使用本项目的过程中所产生的版权数据。
#### 三、音乐平台别名
3.1 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意。如果官方音乐平台觉得不妥,可联系本项目更改或移除。
#### 四、资源使用
4.1 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网。如果出现侵权可联系本项目移除。
#### 五、免责声明
5.1 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
#### 六、使用限制
6.1 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流。本项目不对项目内的技术可能存在违反当地法律法规的行为作保证。
6.2 **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
#### 七、版权保护
7.1 音乐平台不易,请尊重版权,支持正版。
#### 八、非商业性质
8.1 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
#### 九、接受协议
9.1 若你使用了本项目,将代表你接受本协议。
---
若对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换成`@`)
音乐平台不易,请尊重版权,支持正版。<br>
本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。<br>
若对此有疑问请 mail to: lyswhut+qq.com (请将`+`替换成`@`)<br>

View File

@@ -16,7 +16,7 @@ module.exports = async(context) => {
const resPath = `${appOutDir}/${productFilename}.app/Contents/Resources`
// 创建APP语言包文件
return Promise.all(
return await Promise.all(
Object.entries(macLanguagesInfoPlistStrings).map(([lang, config]) => {
let infos = Object.entries(config).map(([k, v]) => `"${k}" = "${v}";`).join('\n')
return fs.writeFile(`${resPath}/${lang}.lproj/InfoPlist.strings`, infos)

View File

@@ -2,47 +2,45 @@ const fs = require('fs')
const fsPromises = require('fs').promises
const path = require('path')
const { Arch } = require('electron-builder')
const nodeAbi = require('node-abi')
const better_sqlite3_fileNameMap = {
[Arch.x64]: 'linux-x64',
[Arch.arm64]: 'linux-arm64',
[Arch.armv7l]: 'linux-arm',
[Arch.x64]: 'electron-v110-linux-x64',
[Arch.arm64]: 'electron-v110-linux-arm64',
[Arch.armv7l]: 'electron-v110-linux-arm',
}
const qrc_decode_fileNameMap = {
win32: {
[Arch.x64]: 'win32-x64',
[Arch.ia32]: 'win32-ia32',
[Arch.arm64]: 'win32-arm64',
[Arch.x64]: 'electron-v110-win32-x64',
[Arch.ia32]: 'electron-v110-win32-ia32',
[Arch.arm64]: 'electron-v110-win32-arm64',
},
linux: {
[Arch.x64]: 'linux-x64',
[Arch.arm64]: 'linux-arm64',
[Arch.armv7l]: 'linux-arm',
[Arch.x64]: 'electron-v110-linux-x64',
[Arch.arm64]: 'electron-v110-linux-arm64',
[Arch.armv7l]: 'electron-v110-linux-arm',
},
darwin: {
[Arch.x64]: 'darwin-x64',
[Arch.arm64]: 'darwin-arm64',
[Arch.x64]: 'electron-v110-darwin-x64',
[Arch.arm64]: 'electron-v110-darwin-arm64',
},
}
const replaceSqliteLib = async(electronNodeAbi, arch) => {
const replaceSqliteLib = async(arch) => {
// console.log(await fs.readdir(path.join(context.appOutDir, './resources/')))
// if (context.electronPlatformName != 'linux' || context.arch != Arch.arm64) return
// https://github.com/lyswhut/lx-music-desktop/issues/1102
// https://github.com/lyswhut/lx-music-desktop/issues/1161
console.log('replace sqlite lib...')
const filePath = path.join(__dirname, `./lib/better_sqlite3_electron-v${electronNodeAbi}-${better_sqlite3_fileNameMap[arch]}.node`)
console.log(filePath)
const filePath = path.join(__dirname, `./lib/better_sqlite3_${better_sqlite3_fileNameMap[arch]}.node`)
const targetPath = path.join(__dirname, '../node_modules/better-sqlite3/build/Release/better_sqlite3.node')
await fsPromises.unlink(targetPath).catch(_ => _)
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 replaceQrcDecodeLib = async(platform, arch) => {
console.log('replace qrc_decode lib...', platform, qrc_decode_fileNameMap[platform][arch])
const filePath = path.join(__dirname, `./lib/qrc_decode_${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(_ => _)
@@ -53,9 +51,7 @@ const replaceQrcDecodeLib = async(electronNodeAbi, platform, arch) => {
module.exports = async(context) => {
const { electronPlatformName, arch } = context
const electronVersion = context.packager?.info?._framework?.version ?? require('../package.json').devDependencies.electron.replace(/^[^\d]*?(\d+)/, '$1')
const electronNodeAbi = nodeAbi.getAbi(electronVersion, 'electron')
await replaceQrcDecodeLib(electronNodeAbi, electronPlatformName, arch)
await replaceQrcDecodeLib(electronPlatformName, arch)
if (electronPlatformName !== 'linux' || process.env.FORCE) return
const bindingFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp')
const bindingBakFilePath = path.join(__dirname, '../node_modules/better-sqlite3/binding.gyp.bak')
@@ -67,7 +63,7 @@ module.exports = async(context) => {
// console.log('rename binding file...')
await fsPromises.rename(bindingFilePath, bindingBakFilePath)
}
await replaceSqliteLib(electronNodeAbi, arch)
await replaceSqliteLib(arch)
break
default:

View File

@@ -1,303 +0,0 @@
/* eslint-disable no-template-curly-in-string */
const builder = require('electron-builder')
const beforePack = require('./build-before-pack')
const afterPack = require('./build-after-pack')
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const options = {
appId: 'cn.toside.music.desktop',
beforePack,
afterPack,
protocols: {
name: 'lx-music-protocol',
schemes: [
'lxmusic',
],
},
directories: {
buildResources: './resources',
output: './build',
},
files: [
'!node_modules/**/*',
'node_modules/font-list',
'node_modules/better-sqlite3/lib',
'node_modules/better-sqlite3/package.json',
'node_modules/better-sqlite3/build/Release/better_sqlite3.node',
'node_modules/electron-font-manager/index.js',
'node_modules/electron-font-manager/package.json',
'node_modules/electron-font-manager/build/Release/font_manager.node',
'node_modules/node-gyp-build',
'node_modules/bufferutil',
'node_modules/utf-8-validate',
'build/Release/qrc_decode.node',
'dist/**/*',
],
asar: {
smartUnpack: false,
},
extraResources: [
'./licenses',
],
publish: [
{
provider: 'github',
owner: 'lyswhut',
repo: 'lx-music-desktop',
},
],
}
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const winOptions = {
win: {
icon: './resources/icons/icon.ico',
legalTrademarks: 'lyswhut',
// artifactName: '${productName}-v${version}-${env.ARCH}-${env.TARGET}.${ext}',
},
nsis: {
oneClick: false,
language: '2052',
allowToChangeInstallationDirectory: true,
differentialPackage: true,
license: './licenses/license.rtf',
shortcutName: 'LX Music',
},
}
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const linuxOptions = {
linux: {
maintainer: 'lyswhut <lyswhut@qq.com>',
// artifactName: '${productName}-${version}.${env.ARCH}.${ext}',
icon: './resources/icons',
category: 'Utility;AudioVideo;Audio;Player;Music;',
desktop: {
Name: 'LX Music',
'Name[zh_CN]': 'LX Music',
'Name[zh_TW]': 'LX Music',
Encoding: 'UTF-8',
MimeType: 'x-scheme-handler/lxmusic',
StartupNotify: 'false',
},
},
appImage: {
license: './licenses/license_zh.txt',
category: 'Utility;AudioVideo;Audio;Player;Music;',
},
}
/**
* @type {import('electron-builder').Configuration}
* @see https://www.electron.build/configuration/configuration
*/
const macOptions = {
mac: {
icon: './resources/icons/icon.icns',
category: 'public.app-category.music',
// artifactName: '${productName}-${version}.${ext}',
},
dmg: {
window: {
width: 600,
height: 400,
},
contents: [
{
x: 106,
y: 252,
name: 'LX Music',
},
{
x: 490,
y: 252,
type: 'link',
path: '/Applications',
},
],
title: '洛雪音乐助手 v${version}',
},
}
// win: {
// tagret: {
// setup: ['nsis', '${productName}-v${version}-${env.ARCH}-Setup.${ext}'],
// green: ['7z', '${productName}-v${version}-${env.ARCH}-green.${ext}'],
// portable: ['portable', '${productName}-v${version}-${env.ARCH}-portable.${ext}'],
// },
// },
// linux: {
// platform: Platform.WINDOWS,
// arch: {
// x64: builder.Arch.x64,
// arm64: builder.Arch.arm64,
// armv7l: builder.Arch.armv7l,
// },
// tagret: {
// deb: ['deb', '${productName}_${version}_${env.ARCH}.${ext}'],
// appImage: ['AppImage', '${productName}_${version}_${env.ARCH}.${ext}'],
// pacman: ['pacman', '${productName}_${version}_${env.ARCH}.${ext}'],
// rpm: ['rpm', '${productName}-${version}.${env.ARCH}.${ext}'],
// },
// },
// mac: {
// arch: {
// x64: builder.Arch.x64,
// x86: builder.Arch.ia32,
// arm64: builder.Arch.arm64,
// },
// tagret: {
// dmg: ['dmg', '${productName}-${version}-${env.ARCH}.${ext}'],
// },
// },
const createTarget = {
/**
*
* @param {*} arch
* @param {*} packageType
* @returns {{ buildOptions: import('electron-builder').CliOptions, options: import('electron-builder').Configuration }}
*/
win(arch, packageType) {
switch (packageType) {
case 'setup':
winOptions.artifactName = `\${productName}-v\${version}-${arch}-Setup.\${ext}`
return {
buildOptions: { win: ['nsis'] },
options: winOptions,
}
case 'green':
winOptions.artifactName = `\${productName}-v\${version}-win_${arch}-green.\${ext}`
return {
buildOptions: { win: ['7z'] },
options: winOptions,
}
case 'win7_setup':
winOptions.artifactName = `\${productName}-v\${version}-win7_${arch}-Setup.\${ext}`
return {
buildOptions: { win: ['nsis'] },
options: winOptions,
}
case 'win7_green':
winOptions.artifactName = `\${productName}-v\${version}-win7_${arch}-green.\${ext}`
return {
buildOptions: { win: ['7z'] },
options: winOptions,
}
case 'portable':
winOptions.artifactName = `\${productName}-v\${version}-${arch}-portable.\${ext}`
return {
buildOptions: { win: ['portable'] },
options: winOptions,
}
default: throw new Error('Unknown package type: ' + packageType)
}
},
/**
*
* @param {*} arch
* @param {*} packageType
* @returns {{ buildOptions: import('electron-builder').CliOptions, options: import('electron-builder').Configuration }}
*/
linux(arch, packageType) {
switch (packageType) {
case 'deb':
linuxOptions.artifactName = `\${productName}_\${version}_${arch == 'x64' ? 'amd64' : arch}.\${ext}`
return {
buildOptions: { linux: ['deb'] },
options: linuxOptions,
}
case 'appImage':
linuxOptions.artifactName = `\${productName}_\${version}_${arch}.\${ext}`
return {
buildOptions: { linux: ['AppImage'] },
options: linuxOptions,
}
case 'pacman':
linuxOptions.artifactName = `\${productName}_\${version}_${arch}.\${ext}`
return {
buildOptions: { linux: ['pacman'] },
options: linuxOptions,
}
case 'rpm':
linuxOptions.artifactName = `\${productName}-\${version}.${arch}.\${ext}`
return {
buildOptions: { linux: ['rpm'] },
options: linuxOptions,
}
default: throw new Error('Unknown package type: ' + packageType)
}
},
/**
*
* @param {*} arch
* @param {*} packageType
* @returns {{ buildOptions: import('electron-builder').CliOptions, options: import('electron-builder').Configuration }}
*/
mac(arch, packageType) {
switch (packageType) {
case 'dmg':
macOptions.artifactName = `\${productName}-\${version}-${arch}.\${ext}`
return {
buildOptions: { mac: ['dmg'] },
options: macOptions,
}
default: throw new Error('Unknown package type: ' + packageType)
}
},
}
/**
*
* @param {'win' | 'mac' | 'linux' | 'dir'} target 构建目标平台
* @param {'x86_64' | 'x64' | 'x86' | 'arm64' | 'armv7l'} arch 包架构
* @param {*} packageType 包类型
* @param {'onTagOrDraft' | 'always' | 'never'} publishType 发布类型
*/
const build = async(target, arch, packageType, publishType) => {
if (target == 'dir') {
await builder.build({
dir: true,
config: { ...options, ...winOptions, ...linuxOptions, ...macOptions },
})
return
}
const targetInfo = createTarget[target](arch, packageType)
// Promise is returned
await builder.build({
...targetInfo.buildOptions,
publish: publishType ?? 'never',
x64: arch == 'x64' || arch == 'x86_64',
ia32: arch == 'x86' || arch == 'x86_64',
arm64: arch == 'arm64',
armv7l: arch == 'armv7l',
config: { ...options, ...targetInfo.options },
})
// .then((result) => {
// console.log(JSON.stringify(result))
// })
// .catch((error) => {
// console.error(error)
// })
}
const params = {}
for (const param of process.argv.slice(2)) {
const [name, value] = param.split('=')
params[name] = value
}
if (params.target == null) throw new Error('Missing target')
if (params.target != 'dir' && params.arch == null) throw new Error('Missing arch')
if (params.target != 'dir' && params.type == null) throw new Error('Missing type')
console.log(params.target, params.arch, params.type, params.publish ?? '')
build(params.target, params.arch, params.type, params.publish)

View File

@@ -1,38 +0,0 @@
// 修补依赖源码以使vite构建的依赖恢复正常工作
const fs = require('node:fs')
const path = require('node:path')
const rootPath = path.join(__dirname, '../')
const patchs = [
[
path.join(rootPath, './node_modules/ws/package.json'),
'\n "browser": "./browser.js",',
'',
],
[
path.join(rootPath, './node_modules/music-metadata/package.json'),
'"default": "./lib/core.js"',
'"default": "./lib/index.js"',
],
[
path.join(rootPath, './node_modules/strtok3/package.json'),
'"default": "./lib/core.js"',
'"default": "./lib/index.js"',
],
]
;(async() => {
for (const [filePath, fromStr, toStr] of patchs) {
console.log(`Patching ${filePath.replace(rootPath, '')}`)
try {
const file = (await fs.promises.readFile(filePath)).toString()
await fs.promises.writeFile(filePath, file.replace(fromStr, toStr))
} catch (err) {
console.error(`Patch ${filePath.replace(rootPath, '')} failed: ${err.message}`)
}
}
console.log('\nDependencies patch finished.\n')
})()

View File

@@ -1,53 +0,0 @@
const fs = require('fs')
const path = require('path')
const tar = require('tar')
const libDir = path.join(__dirname, 'lib')
const getGzipFiles = async() => {
const names = await fs.promises.readdir(libDir)
// for (const name of names) {
// if (name.endsWith('.node')) await fs.promises.unlink(path.join(libDir, name))
// }
return names.filter(name => name.endsWith('.gz'))
}
const unzip = async(filePath) => {
const targetDir = filePath.replace('.tar.gz', '')
if (fs.existsSync(targetDir)) await fs.promises.rm(targetDir, { recursive: true })
await fs.promises.mkdir(targetDir)
await tar.x({
file: filePath,
strip: 1,
C: targetDir,
})
return targetDir
}
const files = [
'qrc_decode',
'better_sqlite3',
]
const moveFile = async(filePath) => {
const name = 'electron-' + path.basename(filePath).split('-electron-')[1]
for (const fileName of files) {
if (fileName == 'better_sqlite3' && !name.includes('linux')) continue
const targetPath = path.join(libDir, `${fileName}_${name}.node`)
if (fs.existsSync(targetPath)) await fs.promises.unlink(targetPath)
await fs.promises.rename(path.join(filePath, 'Release', fileName + '.node'), targetPath)
}
await fs.promises.rm(filePath, { recursive: true })
}
const run = async() => {
const files = await getGzipFiles()
for (const name of files) {
await moveFile(await unzip(path.join(libDir, name)))
}
for (const name of files) {
await fs.promises.unlink(path.join(libDir, name))
}
}
run()

View File

@@ -15,7 +15,6 @@ module.exports = {
externals: {
'font-list': 'font-list',
'better-sqlite3': 'better-sqlite3',
'electron-font-manager': 'electron-font-manager',
bufferutil: 'bufferutil',
'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'),

View File

@@ -12,7 +12,6 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map',
entry: {
main: path.join(__dirname, '../../src/main/index.ts'),
// 'dbService.worker': path.join(__dirname, '../../src/main/worker/dbService/index.ts'),

View File

@@ -17,7 +17,6 @@ const { Worker, isMainThread, parentPort } = require('worker_threads')
function build() {
console.time('build')
del.sync(['dist/**', 'build/**'])
const spinners = new Spinnies({ color: 'blue' })
@@ -37,7 +36,6 @@ function build() {
process.stdout.write('\x1B[2J\x1B[0f')
console.log(`\n\n${results}`)
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
console.timeEnd('build')
process.exit()
}

View File

@@ -20,21 +20,26 @@ module.exports = {
type: 'commonjs2',
},
path: path.join(__dirname, '../../dist'),
publicPath: '',
publicPath: 'auto',
},
resolve: {
alias: {
'@root': path.join(__dirname, '../../src'),
'@': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,

View File

@@ -16,7 +16,6 @@ module.exports = merge(baseConfig, {
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],

View File

@@ -15,19 +15,10 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map',
devtool: false,
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
@@ -35,7 +26,6 @@ module.exports = merge(baseConfig, {
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
}),
],
optimization: {

View File

@@ -12,21 +12,26 @@ module.exports = {
type: 'commonjs2',
},
path: path.join(__dirname, '../../dist'),
publicPath: '',
publicPath: 'auto',
},
resolve: {
alias: {
'@root': path.join(__dirname, '../../src'),
'@': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,

View File

@@ -14,19 +14,10 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map',
devtool: false,
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {

View File

@@ -20,21 +20,26 @@ module.exports = {
type: 'commonjs2',
},
path: path.join(__dirname, '../../dist'),
publicPath: '',
publicPath: 'auto',
},
resolve: {
alias: {
'@root': path.join(__dirname, '../../src'),
'@': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
'@static': path.join(__dirname, '../../src/static'),
'@common': path.join(__dirname, '../../src/common'),
},
extensions: ['.tsx', '.ts', '.js', '.json', '.node'],
extensions: ['.tsx', '.ts', '.js', '.json', '.vue', '.node'],
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
@@ -44,12 +49,6 @@ module.exports = {
appendTsSuffixTo: [/\.vue$/],
},
},
parser: {
worker: [
'*audioContext.audioWorklet.addModule()',
'...',
],
},
},
{
test: /\.node$/,

View File

@@ -17,7 +17,6 @@ module.exports = merge(baseConfig, {
// ENVIRONMENT: 'process.env',
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
staticPath: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],

View File

@@ -15,19 +15,10 @@ const buildConfig = require('../webpack-build-config')
module.exports = merge(baseConfig, {
mode: 'production',
devtool: 'source-map',
devtool: false,
externals: [
// ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new CopyWebpackPlugin({
patterns: [
@@ -44,7 +35,6 @@ module.exports = merge(baseConfig, {
// ENVIRONMENT: 'process.env',
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
}),
],
optimization: {

View File

@@ -1,3 +1,3 @@
module.exports = {
minimize: true,
minimize: false,
}

View File

@@ -9,10 +9,5 @@
"@common/*": ["src/common/*"],
}
},
"vueCompilerOptions": {
"plugins": [
"@vue/language-plugin-pug"
]
},
"exclude": ["node_modules", "build", "dist"]
}

View File

@@ -38,12 +38,12 @@
\fs21\lang1033\langfe2052\kerning2\loch\f31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 \sbasedon0 \snext15 \slink16 \sunhideused Plain Text;}{\*\cs16 \additive \rtlch\fcs1 \af2 \ltrch\fcs0 \loch\f31505\hich\af2 \sbasedon10 \slink15 \slocked
\'b4\'bf\'ce\'c4\'b1\'be \'d7\'d6\'b7\'fb;}{\*\cs17 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid9533173 Hyperlink;}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21
\sbasedon10 \ssemihidden \sunhideused \styrsid9533173 Unresolved Mention;}{\*\cs19 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf22 \sbasedon10 \ssemihidden \sunhideused \styrsid9533173 FollowedHyperlink;}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3758332\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid14511311\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0
\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2023\mo10\dy5\hr14\min42}{\version10}{\edmins4}{\nofpages1}{\nofwords193}{\nofchars1105}
{\nofcharsws1296}{\vern77}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
{\*\rsidtbl \rsid927107\rsid1398824\rsid2109456\rsid3950508\rsid4133944\rsid4355753\rsid9533173\rsid10447395\rsid11081282\rsid12910709\rsid13643782\rsid14384001\rsid15226681}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0
\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author lysyw}{\operator lysyw}{\creatim\yr2019\mo8\dy17\hr10\min22}{\revtim\yr2020\mo4\dy28\hr13\min46}{\version8}{\edmins3}{\nofpages1}{\nofwords135}{\nofchars772}{\nofcharsws906}{\vern1}}
{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl2253\margr2253\margt1440\margb1440\gutter0\ltrsect
\deftab420\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\formshade\horzdoc\dgmargin\dghspace180\dgvspace156
\dghorigin450\dgvorigin0\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind5\viewscale100\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\dghorigin2253\dgvorigin1440\dghshow0\dgvshow2\jcompress\lnongrid
\viewkind1\viewscale120\splytwnine\ftnlytwnine\htmautsp\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct\asianbrkrule\rsidroot3950508\newtblstyruls
\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat {\upr{\*\fchars
!%),.:\'3b>?]\'7d\'a1\'e9\'a1\'a7\'a1\'e3\'a1\'a4\'a1\'a6\'a1\'a5\'a8\'44\'a1\'ac\'a1\'af\'a1\'b1\'a1\'ad\'a1\'eb\'a1\'e4\'a1\'e5?\'a1\'e6\'a1\'c3\'a1\'a2\'a1\'a3\'a1\'a8\'a1\'b5\'a1\'b7\'a1\'b9\'a1\'bb\'a1\'bf\'a1\'b3\'a1\'bd\'a8\'95\'a6\'e1\'a6\'e3\'a6\'e7\'a6\'e5\'a6\'eb\'a9\'77\'a9\'79\'a9\'7b\'a3\'a1\'a3\'a2\'a3\'a5\'a3\'a7\'a3\'a9\'a3\'ac\'a3\'ae\'a3\'ba\'a3\'bb\'a3\'bf\'a3\'dd\'a3\'e0\'a3\'fc\'a3\'fd\'a1\'ab\'a1\'e9
}{\*\ud\uc0{\*\fchars
@@ -53,120 +53,64 @@ $([\'7b{\uc2\u163 \'a1\'ea\u165 \'a3\'a4\'a1\'a4\'a1\'ae\'a1\'b0\'a1\'b4\'a1\'b6
\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery851\footery992\colsx425\endnhere\sectlinegrid312\sectspecifyl\sectrsid2109456\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang
{\pntxta \dbch .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta \dbch .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta \dbch )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl6
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}{\*\pnseclvl9
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf
\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2\'d0\'d0\'a3\'ac\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 Apache License 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b5\'c4
\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'cf\'ee\'c4\'bf\'a1\'b1\'d6\'b8
\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb \dbch (}{\pntxta \dbch )}}\pard\plain \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid1398824 \rtlch\fcs1 \af2\afs22\alang1025 \ltrch\fcs0
\fs21\lang1033\langfe2052\kerning2\loch\af31505\hich\af2\dbch\af31505\cgrid\langnp1033\langfenp2052 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'a3\'a8
\'c8\'ed\'bc\'fe\'a3\'a9\'bb\'f9\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid4133944 \hich\af13\dbch\af13\loch\f13 Apache License 2.0}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'d0\'ed\'bf\'c9\'d6\'a4\'b7\'a2
\'d0\'d0\'a3\'ac\'d4\'da\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'c7\'b0\'a3\'ac\'c4\'e3\'a3\'a8\'ca\'b9\'d3\'c3\'d5\'df\'a3\'a9\'d0\'e8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b2\'c5\'bf\'c9\'bc\'cc\'d0\'f8\'ca\'b9\'d3\'c3\'a3\'ac\'d2\'d4\'cf\'c2
\'d0\'ad\'d2\'e9\'ca\'c7\'b6\'d4\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 Apache Li\hich\af13\dbch\af13\loch\f13 cense 2.0 }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b5\'c4\'b2\'b9\'b3\'e4\'a3\'ac\'c8\'e7\'d3\'d0\'b3\'e5\'cd\'bb\'a3\'ac\'d2\'d4\'d2\'d4\'cf\'c2\'d0\'ad\'d2\'e9\'ce\'aa\'d7\'bc\'a1\'a3}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid10447395\charrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'b4\'ca\'d3\'ef\'d4\'bc\'b6\'a8\'a3\'ba\'b1\'be\'d0\'ad\'d2\'e9\'d6\'d0\'b5\'c4\'a1\'b0\'b1\'be\'c8\'ed\'bc\'fe\'a1\'b1\'d6\'b8
\'c2\'e5\'d1\'a9\'d2\'f4\'c0\'d6\'d7\'c0\'c3\'e6\'b0\'e6\'cf\'ee\'c4\'bf\'a3\'bb\'a1\'b0\'ca\'b9\'d3\'c3\'d5\'df\'a1\'b1\'d6\'b8\'c7\'a9\'ca\'f0\'b1\'be\'d0\'ad\'d2\'e9\'b5\'c4\'ca\'b9\'d3\'c3\'d5\'df\'a3\'bb\'a1\'b0\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6
\'c6\'bd\'cc\'a8\'a1\'b1\'d6\'b8\'b6\'d4\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3
\'c6\'bd\'cc\'a8\'a1\'b1\'d6\'b8\'b6\'d4\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'d6\'c3\'b5\'c4\'b0\'fc\'c0\'a8\'bf\'e1\'ce\'d2\'a1\'a2\'bf\'e1\'b9\'b7\'a1\'a2\'df\'e4\'b9\'be\'b5\'c8\'d2\'f4\'c0\'d6\'d4\'b4\'b5\'c4\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'cd\'b3
\'b3\'c6\'a3\'bb\'a1\'b0\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'b1\'d6\'b8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'cd\'bc\'cf\'f1\'a1\'a2\'d2\'f4\'c6\'b5\'a1\'a2\'c3\'fb\'d7\'d6\'b5\'c8\'d4\'da\'c4\'da\'b5\'c4\'cb\'fb\'c8\'cb\'d3\'b5\'d3\'d0
\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\'cb\'f9\'ca\'f4\'b0\'e6\'c8\'a8\'b5\'c4\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'bb\'a1\'a2\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 1.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b8\'f7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'d4\'da\'cf\'df
\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7\'b4\'d3\'c6\'e4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'a8\'d3\'eb\'ce\'b4\'b5\'c7\'c2\'bc\'d7\'b4\'cc\'ac\'d4\'da\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 APP}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13
\'bb\'f1\'c8\'a1\'b5\'c4\'ca\'fd\'be\'dd\'cf\'e0\'cd\'ac\'a3\'a9\'a3\'ac\'be\'ad\'b9\'fd\'b6\'d4\'ca\'fd\'be\'dd\'bc\'f2\'b5\'a5\'b5\'d8\'c9\'b8\'d1\'a1\'d3\'eb\'ba\'cf\'b2\'a2\'ba\'f3\'bd\'f8\'d0\'d0\'d5\'b9\'ca\'be\'a3\'ac\'d2\'f2\'b4\'cb\'b1\'be
\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.2 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b1\'be\'c9\'ed\'c3\'bb\'d3\'d0\'bb\'f1\'c8\'a1\'c4\'b3\'b8\'f6
\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b5\'c4\'c4\'dc\'c1\'a6\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'ca\'b9\'d3\'c3\'b5\'c4\'d4\'da\'cf\'df\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'c0\'b4\'d7\'d4\'c8\'ed\'bc\'fe\'c9\'e8\'d6\'c3\'c4\'da}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c0\'b4\'d4\'b4}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c9\'e8\'d6\'c3
\'cb\'f9\'d1\'a1\'d4\'f1\'b5\'c4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'b5\'c4\'d4\'da\'cf\'df\'c1\'b4\'bd\'d3\'a1\'a3\'c0\'fd\'c8\'e7\'b2\'a5\'b7\'c5\'c4\'b3\'ca\'d7\'b8\'e8\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d7\'f6\'b5\'c4\'d6\'bb\'ca\'c7\'bd\'ab\'cf\'a3\'cd\'fb\'b2\'a5
\'b7\'c5\'b5\'c4\'b8\'e8\'c7\'fa\'c3\'fb\'d7\'d6\'a1\'a2\'b8\'e8\'ca\'d6\'c3\'fb\'d7\'d6\'b5\'c8\'d0\'c5\'cf\'a2\'b4\'ab\'b5\'dd\'b8\'f8}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'a3\'ac\'c8\'f4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8220\'a1\'b0}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d4\'b4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \u8221\'a1\'b1}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b7\'b5\'bb\'d8\'c1\'cb\'d2\'bb\'b8\'f6\'c1\'b4\'bd\'d3\'a3\'ac\'d4\'f2\'b1\'be\'cf\'ee\'c4\'bf\'bd\'ab\'c8\'cf\'ce\'aa\'d5\'e2
\'be\'cd\'ca\'c7\'b8\'c3\'b8\'e8\'c7\'fa\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b6\'f8\'bd\'f8\'d0\'d0\'ca\'b9\'d3\'c3\'a3\'ac\'d6\'c1\'d3\'da\'d5\'e2\'ca\'c7\'b2\'bb\'ca\'c7\'d5\'fd\'c8\'b7\'b5\'c4\'d2\'f4\'c6\'b5\'ca\'fd\'be\'dd\'b1\'be\'cf\'ee
\'c4\'bf\'ce\'de\'b7\'a8\'d0\'a3\'d1\'e9\'c6\'e4\'d7\'bc\'c8\'b7\'d0\'d4\'a3\'ac\'cb\'f9\'d2\'d4\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc\'bb\'e1\'b3\'f6\'cf\'d6\'cf\'a3\'cd\'fb\'b2\'a5\'b7\'c5\'b5\'c4
\'d2\'f4\'c6\'b5\'d3\'eb\'ca\'b5\'bc\'ca\'b2\'a5\'b7\'c5\'b5\'c4\'d2\'f4\'c6\'b5\'b2\'bb\'b6\'d4\'d3\'a6\'bb\'f2\'d5\'df\'ce\'de\'b7\'a8\'b2\'a5\'b7\'c5\'b5\'c4\'ce\'ca\'cc\'e2\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par \hich\af13\dbch\af13\loch\f13 1.3 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b7\'c7\'b9\'d9\'b7\'bd\'c6\'bd\'cc\'a8\'ca\'fd\'be\'dd
\'a3\'a8\'c0\'fd\'c8\'e7\'ce\'d2\'b5\'c4\'ca\'d5\'b2\'d8\'c1\'d0\'b1\'ed\'a3\'a9\'c0\'b4\'d7\'d4\'ca\'b9\'d3\'c3\'d5\'df\'b1\'be\'b5\'d8\'cf\'b5\'cd\'b3\'bb\'f2\'d5\'df\'ca\'b9\'d3\'c3\'d5\'df\'c1\'ac\'bd\'d3\'b5\'c4\'cd\'ac\'b2\'bd\'b7\'fe\'ce\'f1
\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'d5\'e2\'d0\'a9\'ca\'fd\'be\'dd\'b5\'c4\'ba\'cf\'b7\'a8\'d0\'d4\'a1\'a2\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b6\'fe\'a1\'a2\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 2.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc
\'bb\'e1\'b2\'fa\'c9\'fa\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0\'c8\'a8\'a1\'a3\'ce\'aa
\'c1\'cb\'b1\'dc\'c3\'e2\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 **24}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\hich\af13\dbch\af13\loch\f13 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'cb\'f9
\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'fd\'a1\'a2\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b1\'f0\'c3\'fb}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 3.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'b1\'f0\'c3\'fb\'ce\'aa\'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'b6\'d4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'d2\'bb\'b8\'f6\'b3\'c6\'ba\'f4\'a3\'ac\'b2\'bb\'b0\'fc\'ba\'ac\'b6\'f1\'d2\'e2\'a1\'a3\'c8\'e7\'b9\'fb\'b9\'d9\'b7\'bd\'d2\'f4
\'c0\'d6\'c6\'bd\'cc\'a8\'be\'f5\'b5\'c3\'b2\'bb\'cd\'d7\'a3\'ac\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'cb\'c4\'a1\'a2\'d7\'ca\'d4\'b4\'ca\'b9\'d3\'c3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 4.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8
\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d7\'d6\'cc\'e5\'a1\'a2\'cd\'bc\'c6\'ac\'b5\'c8\'d7\'ca\'d4\'b4\'c0\'b4\'d4\'b4\'d3\'da\'bb\'a5\'c1\'aa\'cd\'f8\'a1\'a3\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'cf\'ee\'c4\'bf
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'ce\'e5\'a1\'a2\'c3\'e2\'d4\'f0\'c9\'f9\'c3\'f7}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 5.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc
\'c0\'a8\'d3\'c9\'d3\'da\'b1\'be\'d0\'ad\'d2\'e9\'bb\'f2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'bb\'f2\'ce\'de\'b7\'a8\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'b6\'f8\'d2\'fd\'c6\'f0\'b5\'c4\'c8\'ce\'ba\'ce\'d0\'d4\'d6\'ca\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1
\par \hich\af13\dbch\af13\loch\f13 1}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'ca\'fd\'be\'dd\'c0\'b4\'d4\'b4\'d4\'ad\'c0\'ed\'ca\'c7
\'b4\'d3\'b8\'f7\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'b9\'ab\'bf\'aa\'b7\'fe\'ce\'f1\'c6\'f7\'d6\'d0\'c0\'ad\'c8\'a1\'ca\'fd\'be\'dd\'a3\'ac\'be\'ad\'b9\'fd\'b6\'d4\'ca\'fd\'be\'dd\'bc\'f2\'b5\'a5\'b5\'d8\'c9\'b8\'d1\'a1\'d3\'eb
\'ba\'cf\'b2\'a2\'ba\'f3\'bd\'f8\'d0\'d0\'d5\'b9\'ca\'be\'a3\'ac\'d2\'f2\'b4\'cb\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b6\'d4\'ca\'fd\'be\'dd\'b5\'c4\'d7\'bc\'c8\'b7\'d0\'d4\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 2}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b9\'fd\'b3\'cc\'d6\'d0\'bf\'c9\'c4\'dc
\'bb\'e1\'b2\'fa\'c9\'fa\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b6\'d4\'d3\'da\'d5\'e2\'d0\'a9\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'d3\'b5\'d3\'d0\'cb\'fc\loch\af13\hich\af13\dbch\f13 \'c3\'c7\'b5\'c4\'cb\'f9\'d3\'d0
\'c8\'a8\'a3\'ac\'ce\'aa\'c1\'cb\'b1\'dc\'c3\'e2\'d4\'ec\'b3\'c9\'c7\'d6\'c8\'a8\'a3\'ac\'ca\'b9\'d3\'c3\'d5\'df\'ce\'f1\'b1\'d8\'d4\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\hich\af13\dbch\af13\loch\f13 24}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'d0\'a1\'ca\'b1\'c4\'da\'c7\'e5\'b3\'fd\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b9\'fd
\'b3\'cc\'d6\'d0\'cb\'f9\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'e6\'c8\'a8\'ca\'fd\'be\'dd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'b5\'c4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'b1\'f0\'c3\'fb\'ce\'aa\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'b6\'d4\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b5\'c4\'d2\'bb\'b8\'f6\'b3\'c6\'ba\'f4\'a3\'ac\'b2\'bb\'b0\'fc\'ba\'ac\'b6\'f1\'d2\'e2\'a3\'ac\'c8\'e7\'b9\'fb\'b9\'d9\'b7\'bd\'d2\'f4
\'c0\'d6\'c6\'bd\'cc\'a8\'be\'f5\'b5\'c3\'b2\'bb\'cd\'d7\'a3\'ac\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'c8\'ed\'bc\'fe\'b8\'fc\'b8\'c4\'bb\'f2\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'c8\'ed\'bc\'fe\'c4\'da\'ca\'b9\'d3\'c3\'b5\'c4\'b2\'bf\'b7\'d6\'b0\'fc\'c0\'a8
\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d7\'d6\'cc\'e5\'a1\'a2\'cd\'bc\'c6\'ac\'b5\'c8\'d7\'ca\'d4\'b4\'c0\'b4\'d4\'b4\'d3\'da\'bb\'a5\'c1\'aa\'cd\'f8\'a3\'ac\'c8\'e7\'b9\'fb\'b3\'f6\'cf\'d6\'c7\'d6\'c8\'a8\'bf\'c9\'c1\'aa\'cf\'b5\'b1\'be\'c8\'ed\'bc\'fe
\'d2\'c6\'b3\'fd\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 5}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b2\'fa\'c9\'fa\'b5\'c4\'b0\'fc
\'c0\'a8\'d3\'c9\'d3\'da\'b1\'be\'d0\'ad\'d2\'e9\'bb\'f2\'d3\'c9\'d3\'da\'ca\'b9\'d3\'c3\'bb\'f2\'ce\'de\'b7\'a8\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'b6\'f8\'d2\'fd\'c6\'f0\'b5\'c4\'c8\'ce\'ba\'ce\'d0\'d4\'d6\'ca\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1
\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'cb\'f0\'ba\'a6\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'d2\'f2\'c9\'cc\'d3\'fe\'cb\'f0\'ca\'a7\'a1\'a2\'cd\'a3\'b9\'a4
\'a1\'a2\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\'cb\'f9\loch\af13\hich\af13\dbch\f13 \'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0
\'ba\'a6\'bb\'f2\'cb\'f0\'ca\'a7\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c1\'f9\'a1\'a2\'ca\'b9\'d3\'c3\'cf\'de\'d6\'c6}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 6.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4
\'b7\'a2\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\loch\af13\hich\af13\dbch\f13 \'c3\'e6\'cf\'f2\'c8\'ab\'ca\'c0\'bd\'e7\'c8\'cb\'d3\'c3\'d7\'f7\'b6\'d4\'bc\'bc\'ca\'f5\'b5\'c4\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'a1\'a3\'b1\'be\'cf\'ee\'c4\'bf\'b2\'bb\'b6\'d4\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'bc\'bc\'ca\'f5
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par \hich\af13\dbch\af13\loch\f13 6.2 **}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 ** }{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed
\'b5\'c4\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'cf\'ee\'c4\'bf\'cb\'f9\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'ce\'a5\'b7\'a8\'ce\'a5\'b9\'e6\'d0\'d0\'ce\'aa\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b3\'d0\'b5\'a3\'a3\'ac\'b1\'be\'cf\'ee\'c4\'bf
\'b2\'bb\'b3\'d0\'b5\'a3\'d3\'c9\'b4\'cb\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'d4\'f0\'c8\'ce\'a1\'a3}{\rtlch\fcs1 \af13
\ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311
\par
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid3758332 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c6\'df
\'a1\'a2\'b0\'e6\'c8\'a8\'b1\'a3\'bb\'a4}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par \hich\af13\dbch\af13\loch\f13 7.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'c7\'eb\'d7\'f0\'d6\'d8\'b0\'e6\'c8\'a8
\'a3\'ac\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b0\'cb\'a1\'a2\'b7\'c7\'c9\'cc\'d2\'b5\'d0\'d4\'d6\'ca}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par \hich\af13\dbch\af13\loch\f13 8.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'b1\'be\'cf\'ee\'c4\'bf\'bd\'f6\'d3\'c3\'d3\'da\'b6\'d4\'bc\'bc\'ca\'f5\'bf\'c9\'d0\'d0\'d0\'d4
\'b5\'c4\'cc\'bd\'cb\'f7\'bc\'b0\'d1\'d0\'be\'bf\'a3\'ac\'b2\'bb\'bd\'d3\'ca\'dc\'c8\'ce\'ba\'ce\'c9\'cc\'d2\'b5\'a3\'a8\'b0\'fc\'c0\'a8\'b5\'ab\'b2\'bb\'cf\'de\'d3\'da\'b9\'e3\'b8\'e6\'b5\'c8\'a3\'a9\'ba\'cf\'d7\'f7\'bc\'b0\'be\'e8\'d4\'f9\'a1\'a3}{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'be\'c5\'a1\'a2\'bd\'d3\'ca\'dc\'d0\'ad\'d2\'e9}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332
\par
\par \hich\af13\dbch\af13\loch\f13 9.1 }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid3758332 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'c4\'e3\'ca\'b9\'d3\'c3\'c1\'cb\'b1\'be\'cf\'ee\'c4\'bf\'a3\'ac\'bd\'ab\'b4\'fa\'b1\'ed
\'c4\'e3\'bd\'d3\'ca\'dc\'b1\'be\'d0\'ad\'d2\'e9\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid3758332\charrsid14511311
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid14511311 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \hich\af13\dbch\af13\loch\f13 * }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid14511311 \loch\af13\hich\af13\dbch\f13 \'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\loch\af13\hich\af13\dbch\f13 \'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa
\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid9533173
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid14511311\charrsid15226681
\'a1\'a2\'bc\'c6\'cb\'e3\'bb\'fa\'b9\'ca\'d5\'cf\'bb\'f2\'b9\'ca\'d5\'cf\'d2\'fd\'c6\'f0\'b5\'c4\'cb\'f0\'ba\'a6\'c5\'e2\'b3\'a5\'a3\'ac\'bb\'f2\'c8\'ce\'ba\'ce\'bc\'b0\loch\af13\hich\af13\dbch\f13 \'cb\'f9\'d3\'d0\'c6\'e4\'cb\'fb\'c9\'cc\'d2\'b5\'cb\'f0
\'ba\'a6\'bb\'f2\'cb\'f0\'ca\'a7\'a3\'a9\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b8\'ba\'d4\'f0\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\par \hich\af13\dbch\af13\loch\f13 6}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13 \'a1\'a2\'b1\'be\'cf\'ee\'c4\'bf\'cd\'ea\'c8\'ab\'c3\'e2\'b7\'d1\'a3\'ac\'c7\'d2\'bf\'aa\'d4\'b4
\'b7\'a2\'b2\'bc\'d3\'da}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 GitHub }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824
\loch\af13\hich\af13\dbch\f13 \'c3\'e6\'cf\'f2\'c8\'ab\'ca\'c0\'bd\'e7\'c8\'cb\'d3\'c3\'d7\'f7\'b6\'d4\'bc\'bc\'ca\'f5\'b5\'c4\'d1\'a7\'cf\'b0\'bd\'bb\'c1\'f7\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b6\'d4\'cf\'ee\'c4\'bf\'c4\'da\'b5\'c4\'bc\'bc\'ca\'f5
\'bf\'c9\'c4\'dc\'b4\'e6\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4\'d0\'d0\'ce\'aa\'d7\'f7\'b1\'a3\'d6\'a4\'a3\'ac\'bd\'fb\'d6\'b9\'d4\'da\'ce\'a5\'b7\'b4\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b5\'c4
\'c7\'e9\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'a3\'ac\'b6\'d4\'d3\'da\'ca\'b9\'d3\'c3\'d5\'df\'d4\'da\'c3\'f7\'d6\'aa\'bb\'f2\'b2\'bb\'d6\'aa\'b5\'b1\'b5\'d8\'b7\'a8\'c2\'c9\'b7\'a8\'b9\'e6\'b2\'bb\'d4\'ca\'d0\'ed\'b5\'c4\'c7\'e9
\'bf\'f6\'cf\'c2\'ca\'b9\'d3\'c3\'b1\'be\'c8\'ed\'bc\'fe\'cb\'f9\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'ce\'a5\'b7\'a8\'ce\'a5\'b9\'e6\'d0\'d0\'ce\'aa\'d3\'c9\'ca\'b9\'d3\'c3\'d5\'df\'b3\'d0\'b5\'a3\'a3\'ac\'b1\'be\'c8\'ed\'bc\'fe\'b2\'bb\'b3\'d0
\'b5\'a3\'d3\'c9\'b4\'cb\'d4\'ec\'b3\'c9\'b5\'c4\'c8\'ce\'ba\'ce\'d6\'b1\'bd\'d3\'a1\'a2\'bc\'e4\'bd\'d3\'a1\'a2\'cc\'d8\'ca\'e2\'a1\'a2\'c5\'bc\'c8\'bb\'bb\'f2\'bd\'e1\'b9\'fb\'d0\'d4\'d4\'f0\'c8\'ce\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid10447395\charrsid1398824
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
\'c8\'f4\'d0\'ad\'d2\'e9\'b8\'fc\'d0\'c2\'a3\'ac\'cb\'a1\'b2\'bb\'c1\'ed\'d0\'d0\'cd\'a8\'d6\'aa\'a3\'ac\'bf\'c9\'b5\'bd\'bf\'aa\'d4\'b4\'b5\'d8\'d6\'b7\'b2\'e9\'bf\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
\'b1\'be\'c8\'ed\'bc\'fe\'b5\'c4\'b3\'f5\'d6\'d4\'ca\'c7\'b0\'ef\'d6\'fa\'b9\'d9\'b7\'bd\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'bc\'f2\'bb\'af\'ca\'fd\'be\'dd\'ba\'f3\'b4\'fa\'ce\'aa\'d5\'b9\'ca\'be\'a3\'ac\'b0\'ef\'d6\'fa\'ca\'b9\'d3\'c3\'d5\'df\'b8\'f9
\'be\'dd\'b8\'e8\'c7\'fa\loch\af13\hich\af13\dbch\f13 \'c3\'fb\'a1\'a2\'d2\'d5\'ca\'f5\'bc\'d2\'b5\'c8\'b9\'d8\'bc\'fc\'d7\'d6\'bf\'ec\'cb\'d9\'b5\'d8\'b6\'a8\'ce\'bb\'cb\'f9\'d0\'e8\'c4\'da\'c8\'dd\'cb\'f9\'d4\'da\'b5\'c4\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8
\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid15226681
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \hich\af13\dbch\af13\loch\f13 * }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid1398824\charrsid1398824 \loch\af13\hich\af13\dbch\f13
\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'b2\'bb\'d2\'d7\'a3\'ac\'bd\'a8\'d2\'e9\'b5\'bd\'b6\'d4\'d3\'a6\'d2\'f4\'c0\'d6\'c6\'bd\'cc\'a8\'d6\'a7\'b3\'d6\'d5\'fd\'b0\'e6\'d7\'ca\'d4\'b4\'a1\'a3}{\rtlch\fcs1 \af13 \ltrch\fcs0
\loch\af13\hich\af13\dbch\af13\insrsid12910709
\par }{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid9533173\charrsid15226681
\par }\pard \ltrpar\s15\qj \li0\ri0\nowidctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid12910709 {\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \hich\af13\dbch\af13\loch\f13 By: }{
\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709 \loch\af13\hich\af13\dbch\f13 \'c2\'e4\'d1\'a9\'ce\'de\'ba\'db}{\rtlch\fcs1 \af13 \ltrch\fcs0 \loch\af13\hich\af13\dbch\af13\insrsid12910709\charrsid12910709
@@ -311,8 +255,8 @@ fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000c03d
9b1957f7d901feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000b026
8d59201dd601feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000105000000000000}}

View File

@@ -1,48 +1,18 @@
This project is issued based on the Apache License 2.0 license. The following protocols are supplemented by Apache License 2.0.
This project (software) is issued based on the Apache License 2.0 license. Before using this software, you (users) need to sign this agreement before you can continue to use it. The following agreement is a supplement to the Apache License 2.0. In case of conflict, use the following agreement Shall prevail.
Words stipulate: "this project" in this agreement refers to the Luoxue Music Desktop Edition project; "user" refers to the user who signed this agreement; the "official music platform" refers to The official platform of the music source is collectively referred to; "copyright data" refers to the data that includes but not limited to images, audio, names, etc.
1. Data source
Terms agreed: "this software" in this agreement refers to the Luo Xue Music desktop version of the project; "user" refers to the user who signed this agreement; "official music platform" refers to the built-in software including Kuwo, Kugou, Migu The official platforms of music sources, etc. are collectively referred to; "copyright data" refers to data including but not limited to images, audio, names, etc. that others own the copyright.
1.1 The principle of online data sources of various official platforms of this project is to draw data from its open server (the same data obtained from the unsproting state in the official platform app). After simply screening and merging data, this project Responsible for the legitimacy and accuracy of the data.
1.2 The ability of this project itself does not obtain a certain audio data. The online audio data source used in this project comes from the online link of the "Source" selected in the software settings. For example, when playing a song, the project only transmits information such as the song name, singer name and other information to be played to "source". If the "source" returns a link, this project will think that this is the audio data of the song For use, as for whether this is the correct audio data, this project cannot verify its accuracy, so the audio that you want to play may occur during the process of using this project.
1.3 The unofficial platform data (such as my collection list) of this project comes from synchronous services connected by the user's local system or user connection. This project is not responsible for the legality and accuracy of these data.
1. The principle of the data source of this software is to pull data from the public servers of each official music platform. After simply filtering and merging the data for display, this software is not responsible for the accuracy of the data.
2. Copyright data may be generated during the use of this software. For this copyright data, the software does not own their ownership. In order to avoid infringement, users must clear the copyright generated during the use of this software within 24 hours data.
3. The alias of the official music platform in this software is a name for the official music platform in the software. It does not contain maliciousness. If the official music platform feels inappropriate, you can contact the software to change or remove it.
4. The parts used in this software include but are not limited to fonts, pictures and other resources from the Internet. If there is infringement, you can contact this software to remove it.
5. Any direct, indirect, special, accidental or consequential damage of any nature arising from the use of this software, including this agreement or the use or inability to use this software (including but not limited to due to loss of goodwill, work stoppage, Compensation for computer failure or damage caused by the failure, or any and all other commercial damage or loss) is the responsibility of the user.
6. This project is completely free, and the open source is published on GitHub. It is used by people all over the world to learn and exchange technology. This software does not guarantee that the technology in the project may violate local laws and regulations. It is prohibited to violate local laws and regulations. Using this software, the user shall be responsible for any violations caused by the user's use of the software without knowing or not permitted by local laws and regulations. The software shall not be responsible for any direct, indirect, special, accidental Or consequential responsibility.
2. Copyright data
2.1 During the process of using this project, copyright data may be generated. For these copyright data, this project does not have their ownership. In order to avoid infringement, users must remove copyright data generated during the process of using this project within ** 24 hours.
3. Alias of Music Platform
3.1 The official music platform in this project is named a name for the official music platform in this project, without maliciousness. If the official music platform feels inappropriate, you can contact this project to change or remove.
Fourth, resource use
4.1 The parts used in this project include, but not limited to fonts, pictures and other resources from the Internet. If infringement occurs, you can contact this project.
5. Disclaimer
5.1 The use of this project includes any direct, indirect, special, accidental, or result damage due to any nature caused by this agreement or use or unable to use this item (including but not limited to the loss of goodwill, stop work, computer, computer Damage compensation caused by faults or faults, or any or all other commercial damage or losses) is responsible for users.
6. Use restrictions
6.1 This project is completely free, and open source is published in GitHub for the learning exchanges of technology for people all over the world. This project does not guarantee that the technology in the project may violate local laws and regulations.
6.2 ** This project is prohibited in violation of local laws and regulations. ** For the user's use of any illegal and illegal acts caused by the use of the project if the user is not allowed to be allowed to the user, it is undertaken by the user. Sexual responsibility.
7. Copyright protection
7.1 Music platform is not easy, please respect the copyright and support genuine.
8. Non -commercial nature
8.1 This project is only used for exploring and research on technical feasibility, and does not accept any business (including but not limited to advertising, etc.) cooperation and donations.
9. Accepting agreement
9.1 If you use this project, it will represent you accept this agreement.
* If the agreement is updated, you will not be notified separately, you can check the open source address.
* The content of this agreement is translated from the Chinese version of the agreement
* If the agreement is updated without prior notice, you can check it at the open source address.
* The original intention of this software is to help the official music platform to simplify the data generation for display, and to help users quickly locate the music platform where the desired content is based on the song title, artist and other keywords.
* Music platform is not easy, it is recommended to support genuine resources to the corresponding music platform.
By: lyswhut

View File

@@ -1,47 +1,18 @@
本项目基于 Apache License 2.0 许可证发行,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
本项目(软件)基于Apache License 2.0 许可证发行,在使用本软件前,你(使用者)需签署本协议才可继续使用,以下协议是对于 Apache License 2.0 的补充,如有冲突,以以下协议为准。
词语约定:本协议中的“本项目”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
一、数据来源
词语约定:本协议中的“本软件”指洛雪音乐桌面版项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本软件内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。
1.1 本项目的各官方平台在线数据来源原理是从其公开服务器中拉取数据与未登录状态在官方平台APP获取的数据相同,经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的合法性、准确性负责。
1.2 本项目本身没有获取某个音频数据的能力,本项目使用的在线音频数据来源来自软件设置内“音乐来源”设置所选择的“源”返回的在线链接。例如播放某首歌,本项目所做的只是将希望播放的歌曲名字、歌手名字等信息传递给“源”,若“源”返回了一个链接,则本项目将认为这就是该歌曲的音频数据而进行使用,至于这是不是正确的音频数据本项目无法校验其准确性,所以使用本项目的过程中可能会出现希望播放的音频与实际播放的音频不对应或者无法播放的问题
1.3 本项目的非官方平台数据(例如我的收藏列表)来自使用者本地系统或者使用者连接的同步服务,本项目不对这些数据的合法性、准确性负责
1、本软件的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本软件不对数据的准确性负责。
2、使用本软件的过程中可能会产生版权数据对于这些版权数据本软件不拥有它们的所有权为了避免造成侵权使用者务必在24小时内清除使用本软件的过程中所产生的版权数据
3、本软件内的官方音乐平台别名为本软件内对官方音乐平台的一个称呼不包含恶意如果官方音乐平台觉得不妥可联系本软件更改或移除
4、本软件内使用的部分包括但不限于字体、图片等资源来源于互联网如果出现侵权可联系本软件移除。
5、由于使用本软件产生的包括由于本协议或由于使用或无法使用本软件而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿或任何及所有其他商业损害或损失由使用者负责。
6、本项目完全免费且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本软件不对项目内的技术可能存在违反当地法律法规的行为作保证,禁止在违反当地法律法规的情况下使用本软件,对于使用者在明知或不知当地法律法规不允许的情况下使用本软件所造成的任何违法违规行为由使用者承担,本软件不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
二、版权数据
2.1 使用本项目的过程中可能会产生版权数据。对于这些版权数据,本项目不拥有它们的所有权。为了避免侵权,使用者务必在**24小时内**清除使用本项目的过程中所产生的版权数据。
三、音乐平台别名
3.1 本项目内的官方音乐平台别名为本项目内对官方音乐平台的一个称呼,不包含恶意。如果官方音乐平台觉得不妥,可联系本项目更改或移除。
四、资源使用
4.1 本项目内使用的部分包括但不限于字体、图片等资源来源于互联网。如果出现侵权可联系本项目移除。
五、免责声明
5.1 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
六、使用限制
6.1 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流。本项目不对项目内的技术可能存在违反当地法律法规的行为作保证。
6.2 **禁止在违反当地法律法规的情况下使用本项目。** 对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。
七、版权保护
7.1 音乐平台不易,请尊重版权,支持正版。
八、非商业性质
8.1 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。
九、接受协议
9.1 若你使用了本项目,将代表你接受本协议。
* 若协议更新,恕不另行通知,可到开源地址查看。
* 本软件的初衷是帮助官方音乐平台简化数据后代为展示,帮助使用者根据歌曲名、艺术家等关键字快速地定位所需内容所在的音乐平台。
* 音乐平台不易,建议到对应音乐平台支持正版资源。
By: 落雪无痕

12197
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,74 @@
{
"name": "lx-music-desktop",
"version": "2.6.0",
"version": "2.3.0-beta.3",
"description": "一个免费的音乐查找助手",
"main": "./dist/main.js",
"productName": "lx-music-desktop",
"scripts": {
"pack": "node build-config/pack.js && npm run pack:win:setup:x64",
"pack:win": "node build-config/pack.js && npm run pack:win:setup:x64 && npm run pack:win:setup:x86 && npm run pack:win:setup:arm64 && npm run pack:win:setup:x86_64 && npm run pack:win:7z",
"pack:win:setup:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=setup",
"pack:win:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=setup",
"pack:win:setup:x86": "node build-config/build-pack.js target=win arch=x86 type=setup",
"pack:win:setup:arm64": "node build-config/build-pack.js target=win arch=arm64 type=setup",
"pack:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p never",
"pack:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p never",
"pack:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p never",
"pack:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p never",
"pack:win:portable": "npm run pack:win:portable:x86_64 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86",
"pack:win:portable:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=portable",
"pack:win:portable:x64": "node build-config/build-pack.js target=win arch=x64 type=portable",
"pack:win:portable:x86": "node build-config/build-pack.js target=win arch=x86 type=portable",
"pack:win:7z": "npm run pack:win:7z:x64",
"pack:win:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=green",
"pack:win:7z:arm64": "node build-config/build-pack.js target=win arch=arm64 type=green",
"pack:win7:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_setup",
"pack:win7:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_green",
"pack:win7:7z:x86": "node build-config/build-pack.js target=win arch=x86 type=win7_green",
"pack:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p never",
"pack:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p never",
"pack:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p never",
"pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86",
"pack:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p never",
"pack:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p never",
"pack:win:7z:arm64": "cross-env TARGET=green ARCH=win_arm64 electron-builder -w=7z --arm64 -p never",
"pack:linux": "node build-config/pack.js && npm run pack:linux:deb && npm run pack:linux:appImage && npm run pack:linux:rpm && npm run pack:linux:pacman",
"pack:linux:appImage": "node build-config/build-pack.js target=linux arch=x64 type=appImage",
"pack:linux:deb": "npm run pack:linux:deb:amd64 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
"pack:linux:deb:amd64": "node build-config/build-pack.js target=linux arch=x64 type=deb",
"pack:linux:deb:arm64": "node build-config/build-pack.js target=linux arch=arm64 type=deb",
"pack:linux:deb:armv7l": "node build-config/build-pack.js target=linux arch=armv7l type=deb",
"pack:linux:rpm": "node build-config/build-pack.js target=linux arch=x64 type=rpm",
"pack:linux:pacman": "node build-config/build-pack.js target=linux arch=x64 type=pacman",
"pack:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p never",
"pack:linux:deb": "npm run pack:linux:deb:x64 && npm run pack:linux:deb:arm64 && npm run pack:linux:deb:armv7l",
"pack:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p never",
"pack:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p never",
"pack:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p never",
"pack:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p never",
"pack:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p never",
"pack:mac": "node build-config/pack.js && npm run pack:mac:dmg && npm run pack:mac:dmg:arm64",
"pack:mac:dmg": "node build-config/build-pack.js target=mac arch=x64 type=dmg",
"pack:mac:dmg:arm64": "node build-config/build-pack.js target=mac arch=arm64 type=dmg",
"pack:dir": "node build-config/pack.js && node build-config/build-pack.js target=dir",
"pack:mac:dmg": "cross-env electron-builder -m=dmg -p never",
"pack:mac:dmg:arm64": "cross-env electron-builder -m=dmg --arm64 -p never",
"pack:dir": "node build-config/pack.js && electron-builder --dir",
"publish": "node publish",
"publish:win:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=setup publish=always",
"publish:win:setup:x86": "node build-config/build-pack.js target=win arch=x86 type=setup publish=always",
"publish:win:setup:arm64": "node build-config/build-pack.js target=win arch=arm64 type=setup publish=always",
"publish:win:setup:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=setup publish=always",
"publish:win:setup:x64:always": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
"publish:win:setup:x64": "cross-env TARGET=Setup ARCH=x64 electron-builder -w=nsis --x64 -p always",
"publish:win:setup:x86": "cross-env TARGET=Setup ARCH=x86 electron-builder -w=nsis --ia32 -p onTagOrDraft",
"publish:win:setup:arm64": "cross-env TARGET=Setup ARCH=arm64 electron-builder -w=nsis --arm64 -p onTagOrDraft",
"publish:win:setup:x86_64": "cross-env TARGET=Setup ARCH=x86_64 electron-builder -w=nsis --x64 --ia32 -p onTagOrDraft",
"publish:win:portable": "npm run publish:win:portable:x86_64 && npm run publish:win:portable:x64 && npm run publish:win:portable:x86",
"publish:win:portable:x86_64": "node build-config/build-pack.js target=win arch=x86_64 type=portable publish=always",
"publish:win:portable:x64": "node build-config/build-pack.js target=win arch=x64 type=portable publish=always",
"publish:win:portable:x86": "node build-config/build-pack.js target=win arch=x86 type=portable publish=always",
"publish:win:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=green publish=always",
"publish:win:7z:arm64": "node build-config/build-pack.js target=win arch=arm64 type=green publish=always",
"publish:win7:setup:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_setup publish=always",
"publish:win7:7z:x64": "node build-config/build-pack.js target=win arch=x64 type=win7_green publish=always",
"publish:win7:7z:x86": "node build-config/build-pack.js target=win arch=x86 type=win7_green publish=always",
"publish:mac:dmg": "node build-config/build-pack.js target=mac arch=x64 type=dmg publish=always",
"publish:mac:dmg:arm64": "node build-config/build-pack.js target=mac arch=arm64 type=dmg publish=always",
"publish:linux:deb:amd64": "node build-config/build-pack.js target=linux arch=x64 type=deb publish=always",
"publish:linux:deb:arm64": "node build-config/build-pack.js target=linux arch=arm64 type=deb publish=always",
"publish:linux:deb:armv7l": "node build-config/build-pack.js target=linux arch=armv7l type=deb publish=always",
"publish:linux:appImage": "node build-config/build-pack.js target=linux arch=x64 type=appImage publish=always",
"publish:linux:rpm": "node build-config/build-pack.js target=linux arch=x64 type=rpm publish=always",
"publish:linux:pacman": "node build-config/build-pack.js target=linux arch=x64 type=pacman publish=always",
"publish:win:portable:x86_64": "cross-env TARGET=portable ARCH=x86_64 electron-builder -w=portable --x64 --ia32 -p onTagOrDraft",
"publish:win:portable:x64": "cross-env TARGET=portable ARCH=x64 electron-builder -w=portable --x64 -p onTagOrDraft",
"publish:win:portable:x86": "cross-env TARGET=portable ARCH=x86 electron-builder -w=portable --ia32 -p onTagOrDraft",
"publish:win:7z:x64": "cross-env TARGET=green ARCH=win_x64 electron-builder -w=7z --x64 -p onTagOrDraft",
"publish:win:7z:x86": "cross-env TARGET=green ARCH=win_x86 electron-builder -w=7z --ia32 -p onTagOrDraft",
"publish:win:7z:arm64": "cross-env TARGET=green ARCH=win_arm64 electron-builder -w=7z --arm64 -p onTagOrDraft",
"publish:mac:dmg:always": "electron-builder -m=dmg -p always",
"publish:mac:dmg": "electron-builder -m=dmg -p onTagOrDraft",
"publish:mac:dmg:arm64": "electron-builder -m=dmg --arm64 -p onTagOrDraft",
"publish:linux:deb:x64:always": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p always",
"publish:linux:deb:x64": "cross-env ARCH=x64 electron-builder -l=deb --x64 -p onTagOrDraft",
"publish:linux:deb:arm64": "cross-env ARCH=arm64 electron-builder -l=deb --arm64 -p onTagOrDraft",
"publish:linux:deb:armv7l": "cross-env ARCH=armv7l electron-builder -l=deb --armv7l -p onTagOrDraft",
"publish:linux:appImage": "cross-env ARCH=x64 electron-builder -l=AppImage -p onTagOrDraft",
"publish:linux:rpm": "cross-env ARCH=x64 electron-builder -l=rpm --x64 -p onTagOrDraft",
"publish:linux:pacman": "cross-env ARCH=x64 electron-builder -l=pacman --x64 -p onTagOrDraft",
"dev": "cross-env NODE_OPTIONS=--max-http-header-size=200000 node build-config/runner-dev.js",
"clean:electron": "rimraf dist",
"clean": "rimraf dist && rimraf build",
"build:theme": "node src/common/theme/createThemes.js",
"build": "node build-config/pack.js",
"build:src": "node build-config/pack.js",
"build:main": "cross-env NODE_ENV=production webpack --config build-config/main/webpack.config.prod.js --progress",
"build:renderer": "cross-env NODE_ENV=production webpack --config build-config/renderer/webpack.config.prod.js --progress",
"build:renderer-lyric": "cross-env NODE_ENV=production webpack --config build-config/renderer-lyric/webpack.config.prod.js --progress",
"build:renderer-scripts": "cross-env NODE_ENV=production webpack --config build-config/renderer-scripts/webpack.config.prod.js --progress",
"build": "npm run clean:electron && npm run build:main && npm run build:renderer && npm run build:renderer-lyric && npm run build:renderer-scripts",
"lint": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly src",
"lint:fix": "eslint --ext .ts,.js,.vue -f node_modules/eslint-formatter-friendly --fix src",
"postinstall": "electron-builder install-app-deps",
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm run pack",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:2081 npm i"
"dp": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm run pack",
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://127.0.0.1:1081 npm i"
},
"browserslist": [
"Electron 22.3.0"
@@ -75,6 +77,101 @@
"node": ">= 16",
"npm": ">=8.5.2"
},
"build": {
"appId": "cn.toside.music.desktop",
"beforePack": "./build-config/build-before-pack.js",
"afterPack": "./build-config/build-after-pack.js",
"protocols": {
"name": "lx-music-protocol",
"schemes": [
"lxmusic"
]
},
"directories": {
"buildResources": "./resources",
"output": "./build"
},
"files": [
"!node_modules/**/*",
"node_modules/font-list",
"node_modules/better-sqlite3/lib",
"node_modules/better-sqlite3/package.json",
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
"node_modules/node-gyp-build",
"node_modules/bufferutil",
"node_modules/utf-8-validate",
"build/Release/qrc_decode.node",
"dist/**/*"
],
"asar": {
"smartUnpack": false
},
"extraResources": [
"./licenses"
],
"win": {
"icon": "./resources/icons/icon.ico",
"legalTrademarks": "lyswhut",
"artifactName": "${productName} v${version} ${env.ARCH} ${env.TARGET}.${ext}"
},
"mac": {
"icon": "./resources/icons/icon.icns",
"category": "public.app-category.music"
},
"linux": {
"maintainer": "lyswhut <lyswhut@qq.com>",
"artifactName": "${productName} v${version} ${env.ARCH}.${ext}",
"icon": "./resources/icons",
"category": "Utility;AudioVideo;Audio;Player;Music;",
"desktop": {
"Name": "LX Music",
"Name[zh_CN]": "LX Music",
"Name[zh_TW]": "LX Music",
"Encoding": "UTF-8",
"MimeType": "x-scheme-handler/lxmusic",
"StartupNotify": "false"
}
},
"nsis": {
"oneClick": false,
"language": "2052",
"allowToChangeInstallationDirectory": true,
"differentialPackage": true,
"license": "./licenses/license.rtf",
"shortcutName": "LX Music"
},
"dmg": {
"window": {
"width": 600,
"height": 400
},
"contents": [
{
"x": 106,
"y": 252,
"name": "LX Music"
},
{
"x": 490,
"y": 252,
"type": "link",
"path": "/Applications"
}
],
"title": "洛雪音乐助手 v${version}"
},
"appImage": {
"license": "./licenses/license_zh.txt",
"category": "Utility;AudioVideo;Audio;Player;Music;"
},
"publish": [
{
"provider": "github",
"owner": "lyswhut",
"repo": "lx-music-desktop"
}
]
},
"macLanguagesInfoPlistStrings": {
"en": {
"CFBundleDisplayName": "LX Music",
@@ -108,104 +205,101 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/eslint-parser": "^7.23.10",
"@babel/core": "^7.21.8",
"@babel/eslint-parser": "^7.21.8",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.23.3",
"@babel/plugin-transform-runtime": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-typescript": "^7.23.3",
"@tsconfig/recommended": "^1.0.3",
"@types/better-sqlite3": "^7.6.9",
"@types/needle": "^3.3.0",
"@types/tunnel": "^0.0.7",
"@types/ws": "8.5.4",
"@volar/vue-language-plugin-pug": "^1.6.5",
"@vue/language-plugin-pug": "^1.8.27",
"babel-loader": "^9.1.3",
"browserslist": "^4.22.3",
"@babel/plugin-transform-modules-umd": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.21.4",
"@babel/preset-env": "^7.21.5",
"@babel/preset-typescript": "^7.21.5",
"@types/better-sqlite3": "^7.6.4",
"@types/needle": "^3.2.0",
"@types/tunnel": "^0.0.3",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"@volar/vue-language-plugin-pug": "^1.6.4",
"babel-loader": "^9.1.2",
"browserslist": "^4.21.5",
"chalk": "^4.1.2",
"changelog-parser": "^3.0.1",
"copy-webpack-plugin": "^12.0.2",
"core-js": "^3.35.1",
"copy-webpack-plugin": "^11.0.0",
"core-js": "^3.30.2",
"cross-env": "^7.0.3",
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^5.0.0",
"del": "^6.1.1",
"electron": "^25.9.8",
"electron-builder": "^24.10.0",
"electron": "^22.3.8",
"electron-builder": "^24.3.0",
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.653",
"electron-updater": "^6.1.7",
"eslint": "^8.56.0",
"eslint-config-standard": "^17.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
"electron-to-chromium": "^1.4.385",
"electron-updater": "^6.1.0",
"eslint": "^8.40.0",
"eslint-config-standard": "^17.0.0",
"eslint-config-standard-with-typescript": "^34.0.1",
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
"eslint-plugin-html": "^7.1.0",
"eslint-plugin-vue": "^9.21.1",
"eslint-plugin-vue-pug": "^0.6.1",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.11.1",
"eslint-webpack-plugin": "^4.0.1",
"html-webpack-plugin": "^5.6.0",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.7.7",
"html-webpack-plugin": "^5.5.1",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"mini-css-extract-plugin": "^2.7.5",
"node-loader": "^2.0.0",
"postcss": "^8.4.33",
"postcss-loader": "^8.1.0",
"postcss-pxtorem": "^6.1.0",
"postcss": "^8.4.23",
"postcss-loader": "^7.3.0",
"postcss-pxtorem": "^6.0.0",
"pug": "^3.0.2",
"pug-plain-loader": "^1.1.0",
"rimraf": "^5.0.5",
"rimraf": "^5.0.0",
"spinnies": "github:lyswhut/spinnies#233305c58694aa3b053e3ab9af9049993f918b9d",
"svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13",
"svgo-loader": "^4.0.0",
"terser": "^5.27.0",
"terser-webpack-plugin": "^5.3.10",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3",
"vue-eslint-parser": "^9.4.2",
"vue-loader": "^17.4.2",
"vue-template-compiler": "^2.7.16",
"webpack": "^5.90.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"terser": "^5.17.1",
"terser-webpack-plugin": "^5.3.8",
"ts-loader": "^9.4.2",
"typescript": "^5.0.4",
"vue-eslint-parser": "^9.2.1",
"vue-loader": "^17.1.0",
"vue-template-compiler": "^2.7.14",
"webpack": "^5.82.0",
"webpack-cli": "^5.1.0",
"webpack-dev-server": "^4.15.0",
"webpack-hot-middleware": "github:lyswhut/webpack-hot-middleware#329c4375134b89d39da23a56a94db651247c74a1",
"webpack-merge": "^5.10.0"
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@simonwep/pickr": "^1.9.0",
"better-sqlite3": "^9.3.0",
"bufferutil": "^4.0.8",
"@simonwep/pickr": "^1.8.2",
"better-sqlite3": "^8.3.0",
"bufferutil": "^4.0.7",
"comlink": "~4.3.1",
"crypto-js": "^4.2.0",
"electron-log": "^5.1.0",
"font-list": "^1.5.1",
"crypto-js": "^4.1.1",
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",
"font-list": "^1.4.5",
"iconv-lite": "^0.6.3",
"image-size": "^1.1.0",
"image-size": "^1.0.2",
"jschardet": "^3.0.0",
"long": "^5.2.3",
"message2call": "^0.1.3",
"music-metadata": "^8.1.4",
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
"node-id3": "^0.2.6",
"sortablejs": "^1.15.2",
"sortablejs": "^1.15.0",
"tunnel": "^0.0.6",
"utf-8-validate": "^6.0.3",
"vue": "~3.3.13",
"vue-router": "^4.2.5",
"ws": "^8.16.0"
"vue": "^3.2.47",
"vue-router": "^4.1.6",
"ws": "^8.13.0"
},
"overrides": {
"got": "^11",
"json5": "latest",
"minimatch": "latest",
"semver": "latest",
"svg-transform-loader": {
"postcss": "latest"
},
"svg-sprite-loader": {
"postcss": "latest"
},

View File

@@ -1,54 +1,7 @@
提交祝大家新年快乐!
更新前需要注意:
由于自定义源的调用方式变更可能会导致某些第三方源停止工作如果出现这种情况你需要将LX回退到 v2.5.0
### 新增
- 若自定义源初始化失败,将会出现弹窗提示初始化失败的详情
- 添加win7_x64架构的安装版安装包构建
- 新增播放歌曲时阻止电脑休眠,默认启用,可到设置-播放设置关闭(#1563
### 优化
- 更新zh-tw翻译
- 自定义源列显示源版本号、作者名字
- 优化列表全选机制,修复列表未获得焦点时仍然可以全选的问题
- 优化搜索框交互逻辑,防止鼠标操作时意外搜索候选列表的内容
- 添加对wy源某些歌曲有问题的歌词进行修复
- 改进本地音乐在线信息的匹配机制
- 优化任务下载状态显示,现在下载时若数据传输完成但数据写入未完成时会显示相应的状态
- 添加对下载歌曲时封面图片大小的控制处理(#1609
- 添加创建同名列表时的二次确认(#1621
### 修复
- 修复备份文件无法导入json格式的问题
- Windows、MacOS平台下的字体列表取消使用原生方式获取以修复某些字体应用后无效的问题#1596
- 修复亮暗主题自动切换功能无效的问题(#1697
- 修复 MacOS 平台在 Finder 打开文件或目录时应用卡死的问题(#1684
- 修复下载模块在数据写入速度较慢的情况下出现任务及文件异常的问题
- 修复临时列表变更会意外触发同步的问题
- 修复最小化后再隐藏窗口时,托盘菜单的显示主界面功能异常的问题
### 变更
- 播放歌曲时默认会阻止系统进入休眠状态,若你不行软件阻止系统休眠,可以到设置-播放设置取消勾选“播放歌曲时阻止电脑休眠”设置
- 新增音效设置实验性功能支持10段均衡器设置、内置的一些环境混响音效、3D立体环绕音效
### 其他
- 移除所有内置源由于收到腾讯投诉要求停止提供软件内置的连接到他们平台的在线播放及下载服务所以从即日2023年10月18日起LX本身不再提供上述服务
- 更新 electron 到 v25.9.8
- 更新许可协议的排版,使其看起来更加清晰明了,更新数据来源原理说明
### 自定义源的不兼容变更与新增内容(源开发者需要看)
自定义源的调用方式已改变:
- 为了与移动端的调用方式统一,不再推荐使用 `window.lx` 对象(移动端无`window`对象),改用 `globalThis.lx`
- `inited` 事件不再需要传递 `status` 属性,脚本运行过程中,在成功调用 `inited` 事件之前的任何首次未捕获的错误都将视为初始化失败,所以现在若想人为让脚本初始化失败,直接抛出一个错误即可
- 新增 `globalThis.lx.env` 属性,桌面端环境固定为 `desktop`,移动端环境固定为 `mobile`
- 新增 `globalThis.lx.currentScriptInfo` 对象,可以从这里获取解析后的脚本头部注释信息及脚本原始内容,具体可用属性看文档说明
- `globalThis.lx.version` 属性更新到 `2.0.0`
- 自定义源不再使用`script`标签的形式执行,若要获取脚本原始代码字符串需从 `globalThis.lx.currentScriptInfo.rawScript` 属性获取
- 自定义源新增支持`local`源的`musicUrl``pic``lyric`的获取操作详情看自定义源文档说明
- 更新 electron 到 v22.3.8

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +0,0 @@
/* eslint-env node */
const { base, typescript } = require('../../.eslintrc.base.cjs')
module.exports = {
root: true,
...base,
overrides: [
{
...typescript,
parserOptions: {
project: './tsconfig.json',
},
},
],
}

View File

@@ -1,9 +1,5 @@
export const URL_SCHEME_RXP = /^lxmusic:\/\//
export const SPLIT_CHAR = {
DISLIKE_NAME: '@',
DISLIKE_NAME_ALIAS: '#',
} as const
export const STORE_NAMES = {
APP_SETTINGS: 'config_v2',
@@ -54,8 +50,8 @@ export const DEFAULT_SETTING = {
},
songList: {
source: 'kw',
sortId: 'new',
source: 'kg',
sortId: '5',
tagId: '',
},
@@ -80,3 +76,24 @@ export const DOWNLOAD_STATUS = {
} as const
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
export const SYNC_CODE = {
helloMsg: 'Hello~::^-^::~v3~',
idPrefix: 'OjppZDo6',
authMsg: 'lx-music auth::',
authFailed: 'Auth failed',
missingAuthCode: 'Missing auth code',
getServiceIdFailed: 'Get service id failed',
connectServiceFailed: 'Connect service failed',
connecting: 'Connecting...',
unknownServiceAddress: 'Unknown service address',
msgBlockedIp: 'Blocked IP',
msgConnect: 'lx-music connect',
msgAuthFailed: 'Auth failed',
} as const
export const SYNC_CLOSE_CODE = {
normal: 1000,
failed: 4100,
} as const

View File

@@ -1,74 +0,0 @@
export const ENV_PARAMS = [
'PORT',
'BIND_IP',
'CONFIG_PATH',
'LOG_PATH',
'DATA_PATH',
'PROXY_HEADER',
'MAX_SNAPSHOT_NUM',
'LIST_ADD_MUSIC_LOCATION_TYPE',
'LX_USER_',
] as const
export const LIST_IDS = {
DEFAULT: 'default',
LOVE: 'love',
TEMP: 'temp',
DOWNLOAD: 'download',
PLAY_LATER: null,
} as const
export const SYNC_CODE = {
helloMsg: 'Hello~::^-^::~v4~',
idPrefix: 'OjppZDo6',
authMsg: 'lx-music auth::',
msgAuthFailed: 'Auth failed',
msgBlockedIp: 'Blocked IP',
msgConnect: 'lx-music connect',
authFailed: 'Auth failed',
missingAuthCode: 'Missing auth code',
getServiceIdFailed: 'Get service id failed',
connectServiceFailed: 'Connect service failed',
connecting: 'Connecting...',
unknownServiceAddress: 'Unknown service address',
} as const
export const SYNC_CLOSE_CODE = {
normal: 1000,
failed: 4100,
} as const
export const TRANS_MODE: Readonly<Record<LX.Sync.List.SyncMode, LX.Sync.List.SyncMode>> = {
merge_local_remote: 'merge_remote_local',
merge_remote_local: 'merge_local_remote',
overwrite_local_remote: 'overwrite_remote_local',
overwrite_remote_local: 'overwrite_local_remote',
overwrite_local_remote_full: 'overwrite_remote_local_full',
overwrite_remote_local_full: 'overwrite_local_remote_full',
cancel: 'cancel',
} as const
export const File = {
serverDataPath: 'sync/server',
clientDataPath: 'sync/client',
serverInfoJSON: 'serverInfo.json',
userDir: 'users',
userDevicesJSON: 'devices.json',
listDir: 'list',
listSnapshotDir: 'snapshot',
listSnapshotInfoJSON: 'snapshotInfo.json',
dislikeDir: 'dislike',
dislikeSnapshotDir: 'snapshot',
dislikeSnapshotInfoJSON: 'snapshotInfo.json',
syncAuthKeysJSON: 'syncAuthKey.json',
} as const
export const FeaturesList = [
'list',
'dislike',
] as const

View File

@@ -1,5 +1,5 @@
import path from 'node:path'
import os from 'node:os'
import { join } from 'path'
import { homedir } from 'os'
const isMac = process.platform == 'darwin'
const isWin = process.platform == 'win32'
@@ -27,10 +27,8 @@ const defaultSetting: LX.AppSetting = {
'player.highQuality': false,
'player.isShowTaskProgess': true,
'player.volume': 1,
'player.powerSaveBlocker': true,
'player.isMute': false,
'player.playbackRate': 1,
'player.preservesPitch': true,
'player.mediaDeviceId': 'default',
'player.isMediaDeviceRemovedStopPlay': false,
'player.isShowLyricTranslation': false,
@@ -42,7 +40,6 @@ const defaultSetting: LX.AppSetting = {
'player.waitPlayEndStop': true,
'player.waitPlayEndStopTime': '',
'player.autoSkipOnError': true,
'player.isAutoCleanPlayedList': false,
'player.soundEffect.convolution.fileName': '',
'player.soundEffect.convolution.mainGain': 10,
'player.soundEffect.convolution.sendGain': 0,
@@ -59,7 +56,6 @@ const defaultSetting: LX.AppSetting = {
'player.soundEffect.panner.enable': false,
'player.soundEffect.panner.soundR': 5,
'player.soundEffect.panner.speed': 25,
'player.soundEffect.pitchShifter.playbackRate': 1,
'playDetail.isZoomActiveLrc': false,
'playDetail.isShowLyricProgressSetting': false,
@@ -77,7 +73,7 @@ const defaultSetting: LX.AppSetting = {
'desktopLyric.height': 300,
'desktopLyric.x': null,
'desktopLyric.y': null,
'desktopLyric.isLockScreen': isWin,
'desktopLyric.isLockScreen': true,
'desktopLyric.isDelayScroll': true,
'desktopLyric.scrollAlign': 'center',
'desktopLyric.isHoverHide': false,
@@ -104,7 +100,7 @@ const defaultSetting: LX.AppSetting = {
'list.actionButtonsVisible': false,
'download.enable': false,
'download.savePath': path.join(os.homedir(), 'Desktop'),
'download.savePath': join(homedir(), 'Desktop'),
'download.fileName': '歌名 - 歌手',
'download.maxDownloadNum': 3,
'download.skipExistFile': true,
@@ -138,8 +134,8 @@ const defaultSetting: LX.AppSetting = {
'sync.server.maxSsnapshotNum': 5,
'sync.client.host': '',
// 'theme.id': 'blue_plus',
'theme.id': 'green',
'theme.id': 'blue_plus',
// 'theme.id': 'green',
'theme.lightId': 'green',
'theme.darkId': 'black',

View File

@@ -6,7 +6,7 @@ const ignoreErrorMessage = [
]
process.on('uncaughtException', err => {
if (ignoreErrorMessage.includes(err?.message)) return
if (ignoreErrorMessage.includes(err.message)) return
console.error('An uncaught error occurred!')
console.error(err)
log.error(err)

View File

@@ -66,21 +66,6 @@ const hotKey = {
action: 'volume_mute',
type: '',
},
music_love: {
name: 'music_love',
action: 'music_love',
type: '',
},
music_unlove: {
name: 'music_unlove',
action: 'music_unlove',
type: '',
},
music_dislike: {
name: 'music_dislike',
action: 'music_dislike',
type: '',
},
},
desktop_lyric: {
toggle_visible: {

View File

@@ -36,12 +36,6 @@ const modules = {
list_music_check_exist: 'list_music_check_exist',
list_music_get_list_ids: 'list_music_get_list_ids',
},
dislike: {
get_dislike_music_infos: 'get_dislike_music_infos',
add_dislike_music_infos: 'add_dislike_music_infos',
overwrite_dislike_music_infos: 'overwrite_dislike_music_infos',
clear_dislike_music_infos: 'clear_dislike_music_infos',
},
winMain: {
focus: 'focus',
close: 'close',
@@ -55,9 +49,7 @@ const modules = {
show_save_dialog: 'show_save_dialog',
show_select_dialog: 'show_select_dialog',
show_dialog: 'show_dialog',
open_dir_in_explorer: 'open_dir_in_explorer',
open_dev_tools: 'open_dev_tools',
set_power_save_blocker: 'set_power_save_blocker',
progress: 'progress',
change_tray: 'change_tray',
@@ -102,8 +94,6 @@ const modules = {
save_sound_effect_eq_preset: 'save_sound_effect_eq_preset',
get_sound_effect_convolution_preset: 'get_sound_effect_convolution_preset',
save_sound_effect_convolution_preset: 'save_sound_effect_convolution_preset',
// get_sound_effect_pitch_shifter_preset: 'get_sound_effect_pitch_shifter_preset',
// save_sound_effect_pitch_shifter_preset: 'save_sound_effect_pitch_shifter_preset',
get_hot_key: 'get_hot_key',
import_user_api: 'import_user_api',
@@ -135,8 +125,6 @@ const modules = {
get_music_url_count: 'get_music_url_count',
sync_action: 'sync_action',
sync_get_server_devices: 'sync_get_server_devices',
sync_remove_server_device: 'sync_remove_server_device',
process_new_desktop_lyric_client: 'process_new_desktop_lyric_client',
@@ -190,7 +178,6 @@ for (const moduleName of Object.keys(modules) as Array<keyof typeof modules>) {
export const CMMON_EVENT_NAME = modules.common
export const PLAYER_EVENT_NAME = modules.player
export const DISLIKE_EVENT_NAME = modules.dislike
export const WIN_MAIN_RENDERER_EVENT_NAME = modules.winMain
export const WIN_LYRIC_RENDERER_EVENT_NAME = modules.winLyric
export const HOTKEY_RENDERER_EVENT_NAME = modules.hotKey

View File

@@ -30,7 +30,7 @@ export function mainHandle<V>(name: string, listener: LX.IpcMainInvokeEventListe
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
export function mainHandle<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
ipcMain.handle(name, async(event, params) => {
return listener({ event, params })
return await listener({ event, params })
})
}
@@ -40,7 +40,7 @@ export function mainHandleOnce<V>(name: string, listener: LX.IpcMainInvokeEventL
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void
export function mainHandleOnce<T, V>(name: string, listener: LX.IpcMainInvokeEventListenerParamsValue<T, V>): void {
ipcMain.handleOnce(name, async(event, params) => {
return listener({ event, params })
return await listener({ event, params })
})
}
export const mainHandleRemove = (name: string) => {

View File

@@ -17,7 +17,7 @@ export async function rendererInvoke<V>(name: string): Promise<V>
export async function rendererInvoke<T>(name: string, params: T): Promise<void>
export async function rendererInvoke<T, V>(name: string, params: T): Promise<V>
export async function rendererInvoke <T, V>(name: string, params?: T): Promise<V> {
return ipcRenderer.invoke(name, params)
return await ipcRenderer.invoke(name, params)
}
export function rendererOn(name: string, listener: LX.IpcRendererEventListener): void

View File

@@ -2766,16 +2766,16 @@
"--color-primary-light-900-alpha-700": "rgba(59, 59, 59, 0.30)",
"--color-primary-light-900-alpha-800": "rgba(59, 59, 59, 0.20)",
"--color-primary-light-900-alpha-900": "rgba(59, 59, 59, 0.10)",
"--color-primary-light-1000": "rgb(38,38,38)",
"--color-primary-light-1000-alpha-100": "rgba(38, 38, 38, 0.90)",
"--color-primary-light-1000-alpha-200": "rgba(38, 38, 38, 0.80)",
"--color-primary-light-1000-alpha-300": "rgba(38, 38, 38, 0.70)",
"--color-primary-light-1000-alpha-400": "rgba(38, 38, 38, 0.60)",
"--color-primary-light-1000-alpha-500": "rgba(38, 38, 38, 0.50)",
"--color-primary-light-1000-alpha-600": "rgba(38, 38, 38, 0.40)",
"--color-primary-light-1000-alpha-700": "rgba(38, 38, 38, 0.30)",
"--color-primary-light-1000-alpha-800": "rgba(38, 38, 38, 0.20)",
"--color-primary-light-1000-alpha-900": "rgba(38, 38, 38, 0.10)",
"--color-primary-light-1000": "rgb(47,47,47)",
"--color-primary-light-1000-alpha-100": "rgba(47, 47, 47, 0.90)",
"--color-primary-light-1000-alpha-200": "rgba(47, 47, 47, 0.80)",
"--color-primary-light-1000-alpha-300": "rgba(47, 47, 47, 0.70)",
"--color-primary-light-1000-alpha-400": "rgba(47, 47, 47, 0.60)",
"--color-primary-light-1000-alpha-500": "rgba(47, 47, 47, 0.50)",
"--color-primary-light-1000-alpha-600": "rgba(47, 47, 47, 0.40)",
"--color-primary-light-1000-alpha-700": "rgba(47, 47, 47, 0.30)",
"--color-primary-light-1000-alpha-800": "rgba(47, 47, 47, 0.20)",
"--color-primary-light-1000-alpha-900": "rgba(47, 47, 47, 0.10)",
"--color-theme": "rgb(59,59,59)",
"--color-1000": "rgb(229, 229, 229)",
"--color-950": "rgb(218,218,218)",

View File

@@ -22,7 +22,7 @@ exports.createThemeColors = (rgbaColor, fontRgbaColor, isDark) => {
colors[`--color-primary-light-${i * 100}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)
}
}
preColor = RGB_Linear_Shade(isDark ? -0.35 : 1, preColor)
preColor = RGB_Linear_Shade(isDark ? -0.2 : 1, preColor)
colors[`--color-primary-light-${1000}`] = preColor
for (let j = 1; j < 10; j += 1) {
colors[`--color-primary-light-${1000}-alpha-${j * 100}`] = RGB_Alpha_Shade(0.1 * j, preColor)

View File

@@ -1,11 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"typeRoots": [
"module": "esnext",
"moduleResolution": "nodenext",
"typeRoots": [ /* Specify multiple folders that act like './node_modules/@types'. */
"./types"
],
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"@common/*": ["common/*"],
},
},
// "include": [
// "**/*.ts",
// "**/*.js",
// "**/*.vue",
// "**/*.json",
// ],
}

View File

@@ -1,4 +1,4 @@
import type { I18n } from '@root/lang/i18n'
import type { I18n } from '@/lang/i18n'
declare global {
@@ -103,11 +103,6 @@ declare global {
*/
'player.volume': number
/**
* 播放歌曲时是否阻止电脑休眠
*/
'player.powerSaveBlocker': boolean
/**
* 是否静音
*/
@@ -118,11 +113,6 @@ declare global {
*/
'player.playbackRate': number
/**
* 是否自动调整音频的音高以补偿对播放速率设置所做的更改
*/
'player.preservesPitch': boolean
/**
* 音频输出设备id
*/
@@ -253,21 +243,11 @@ declare global {
*/
'player.soundEffect.panner.speed': number
/**
* 升降声调
*/
'player.soundEffect.pitchShifter.playbackRate': number
/**
* 是否启用音频加载失败时自动切歌
*/
'player.autoSkipOnError': boolean
/**
* 点击相同列表内的歌曲切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)
*/
'player.isAutoCleanPlayedList': boolean
/**
* 播放详情页-是否缩放当前播放的歌词行
*/

View File

@@ -1,37 +0,0 @@
declare namespace LX {
namespace Dislike {
// interface ListItemMusicText {
// id?: string
// // type: 'music'
// name: string | null
// singer: string | null
// }
// interface ListItemMusic {
// id?: number
// type: 'musicId'
// musicId: string
// meta: LX.Music.MusicInfo
// }
// type ListItem = ListItemMusicText
// type ListItem = string
// type ListItem = ListItemMusic | ListItemMusicText
interface DislikeMusicInfo {
name: string
singer: string
}
type DislikeRules = string
interface DislikeInfo {
// musicIds: Set<string>
names: Set<string>
musicNames: Set<string>
singerNames: Set<string>
// list: LX.Dislike.ListItem[]
rules: DislikeRules
}
}
}

View File

@@ -1,30 +0,0 @@
declare namespace LX {
namespace Sync {
namespace Dislike {
interface ListInfo {
lastSyncDate?: number
snapshotKey: string
}
interface SyncActionBase <A> {
action: A
}
interface SyncActionData<A, D> extends SyncActionBase<A> {
data: D
}
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
type ActionList = SyncAction<'dislike_data_overwrite', LX.Dislike.DislikeRules>
| SyncAction<'dislike_music_add', LX.Dislike.DislikeMusicInfo[]>
| SyncAction<'dislike_music_clear'>
type SyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
// | 'none'
| 'cancel'
}
}
}

View File

@@ -1,4 +1,4 @@
import { type Message } from '@root/lang'
import { type Message } from '@/lang'
// interface DownloadList {
@@ -21,7 +21,6 @@ declare global {
speed: string
downloaded: number
total: number
writeQueue: number
}
interface DownloadTaskActionBase <A> {
@@ -51,7 +50,6 @@ declare global {
total: number
progress: number
speed: string
writeQueue: number
metadata: {
musicInfo: LX.Music.MusicInfoOnline
url: string | null

View File

@@ -139,5 +139,6 @@ declare namespace LX {
userList: UserListInfoFull[]
tempList: LX.Music.MusicInfo[]
}
}
}

View File

@@ -1,34 +0,0 @@
declare namespace LX {
namespace Sync {
namespace List {
interface ListInfo {
lastSyncDate?: number
snapshotKey: string
}
type ActionList = LX.Sync.SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
| SyncAction<'list_create', LX.List.ListActionAdd>
| SyncAction<'list_remove', LX.List.ListActionRemove>
| SyncAction<'list_update', LX.List.ListActionUpdate>
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
type SyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
| 'overwrite_local_remote_full'
| 'overwrite_remote_local_full'
// | 'none'
| 'cancel'
}
}
}

View File

@@ -21,10 +21,5 @@ declare namespace LX {
mainGain: number
sendGain: number
}
// interface PitchShifterPreset {
// id: string
// name: string
// playbackRate: number
// }
}
}

View File

@@ -19,27 +19,52 @@ declare namespace LX {
}
type SyncAction<A, D = undefined> = D extends undefined ? SyncActionBase<A> : SyncActionData<A, D>
interface ModeTypes {
list: LX.Sync.List.SyncMode
dislike: LX.Sync.Dislike.SyncMode
}
type ModeType = { [K in keyof ModeTypes]: { type: K, mode: ModeTypes[K] } }[keyof ModeTypes]
type SyncMainWindowActions = SyncAction<'select_mode', { deviceName: string, type: keyof ModeTypes }>
type SyncMainWindowActions = SyncAction<'select_mode', string>
| SyncAction<'close_select_mode'>
| SyncAction<'client_status', ClientStatus>
| SyncAction<'server_status', ServerStatus>
type SyncServiceActions = SyncAction<'select_mode', ModeType>
type SyncServiceActions = SyncAction<'select_mode', Mode>
| SyncAction<'get_server_status'>
| SyncAction<'get_client_status'>
| SyncAction<'generate_code'>
| SyncAction<'enable_server', EnableServer>
| SyncAction<'enable_client', EnableClient>
type ServerDevices = ServerKeyInfo[]
type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite>
| SyncAction<'list_create', LX.List.ListActionAdd>
| SyncAction<'list_remove', LX.List.ListActionRemove>
| SyncAction<'list_update', LX.List.ListActionUpdate>
| SyncAction<'list_update_position', LX.List.ListActionUpdatePosition>
| SyncAction<'list_music_add', LX.List.ListActionMusicAdd>
| SyncAction<'list_music_move', LX.List.ListActionMusicMove>
| SyncAction<'list_music_remove', LX.List.ListActionMusicRemove>
| SyncAction<'list_music_update', LX.List.ListActionMusicUpdate>
| SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition>
| SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite>
| SyncAction<'list_music_clear', LX.List.ListActionMusicClear>
type ActionSync = SyncAction<'list:sync:list_sync_get_md5', string>
| SyncAction<'list:sync:list_sync_get_list_data', ListData>
| SyncAction<'list:sync:list_sync_get_sync_mode', Mode>
| SyncAction<'list:sync:action', ActionList>
// | SyncAction<'finished'>
type ActionSyncType = Actions<ActionSync>
type ActionSyncSend = SyncAction<'list:sync:list_sync_get_md5'>
| SyncAction<'list:sync:list_sync_get_list_data'>
| SyncAction<'list:sync:list_sync_get_sync_mode'>
| SyncAction<'list:sync:list_sync_set_data', LX.Sync.ListData>
| SyncAction<'list:sync:action', ActionList>
| SyncAction<'list:sync:finished'>
type ActionSyncSendType = Actions<ActionSyncSend>
interface List {
action: string
data: any
}
interface ServerStatus {
status: boolean
@@ -65,21 +90,21 @@ declare namespace LX {
clientId: string
key: string
deviceName: string
lastConnectDate?: number
lastSyncDate?: number
snapshotKey: string
isMobile: boolean
}
interface ListConfig {
skipSnapshot: boolean
}
interface DislikeConfig {
skipSnapshot: boolean
}
type ServerType = 'desktop-app' | 'server'
interface EnabledFeatures {
list?: false | ListConfig
dislike?: false | DislikeConfig
}
type SupportedFeatures = Partial<{ [k in keyof EnabledFeatures]: number }>
type ListData = Omit<LX.List.ListDataFull, 'tempList'>
type Mode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
| 'overwrite_local_remote_full'
| 'overwrite_remote_local_full'
// | 'none'
| 'cancel'
}
}

View File

@@ -1,7 +1,7 @@
declare namespace LX {
namespace UserApi {
type UserApiSourceInfoType = 'music'
type UserApiSourceInfoActions = 'musicUrl' | 'lyric' | 'pic'
type UserApiSourceInfoActions = 'musicUrl'
interface UserApiSourceInfo {
name: string
@@ -13,20 +13,15 @@ declare namespace LX {
type UserApiSources = Record<LX.Source, UserApiSourceInfo>
interface UserApiInfoFull {
interface UserApiInfo {
id: string
name: string
description: string
script: string
allowShowUpdateAlert: boolean
author?: string
homepage?: string
version?: string
sources?: UserApiSources
}
type UserApiInfo = Omit<UserApiInfoFull, 'script'>
interface UserApiStatus {
status: boolean
message?: string

View File

@@ -10,13 +10,3 @@ type Modify<T, R> = Omit<T, keyof R> & R
type Actions<T extends { action: string, data?: any }> = {
[U in T as U['action']]: 'data' extends keyof U ? U['data'] : undefined
}
type WarpPromiseValue<T> = T extends ((...args: infer P) => Promise<infer R>)
? ((...args: P) => Promise<R>)
: T extends ((...args: infer P2) => infer R2)
? ((...args: P2) => Promise<R2>)
: Promise<T>
type WarpPromiseRecord<T extends Record<string, any>> = {
[K in keyof T]: WarpPromiseValue<T[K]>
}

View File

@@ -225,23 +225,3 @@ export const arrPushByPosition = <T>(list: T[], newList: T[], position: number)
}
return list
}
// https://stackoverflow.com/a/2450976
export const arrShuffle = <T>(array: T[]) => {
let currentIndex = array.length
let randomIndex
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex)
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]]
}
return array
}

View File

@@ -8,7 +8,6 @@ import { request, type Options as RequestOptions } from './request'
export interface Options {
forceResume: boolean
timeout: number
requestOptions: RequestOptions
}
@@ -24,7 +23,6 @@ const defaultRequestOptions: Options['requestOptions'] = {
}
const defaultOptions: Options = {
forceResume: true,
timeout: 20_000,
requestOptions: { ...defaultRequestOptions },
}
@@ -39,11 +37,6 @@ class Task extends EventEmitter {
progress = { total: 0, downloaded: 0, speed: 0, progress: 0 }
statsEstimate = { time: 0, bytes: 0, prevBytes: 0 }
requestInstance: http.ClientRequest | null = null
maxRedirectNum = 2
private redirectNum = 0
private dataWriteQueueLength = 0
private closeWaiting = false
private timeout: null | NodeJS.Timeout = null
constructor(url: string, savePath: string, filename: string, options: Partial<Options> = {}) {
@@ -66,14 +59,9 @@ class Task extends EventEmitter {
async __init() {
const { path, startByte, endByte } = this.chunkInfo
this.redirectNum = 0
this.progress.downloaded = 0
this.progress.progress = 0
this.progress.speed = 0
this.dataWriteQueueLength = 0
this.closeWaiting = false
this.__clearTimeout()
this.__startTimeout()
if (startByte) this.requestOptions.headers!.range = `bytes=${startByte}-${endByte}`
if (!path) return
@@ -124,7 +112,6 @@ class Task extends EventEmitter {
__httpFetch(url: string, options: Options['requestOptions']) {
// console.log(options)
let redirected = false
this.requestInstance = request(url, options)
.on('response', response => {
if (response.statusCode !== 200 && response.statusCode !== 206) {
@@ -138,18 +125,8 @@ class Task extends EventEmitter {
})
return
}
if ((response.statusCode == 301 || response.statusCode == 302) && response.headers.location && this.redirectNum < this.maxRedirectNum) {
console.log('current url:', url)
console.log('redirect to:', response.headers.location)
redirected = true
this.redirectNum++
const location = response.headers.location
this.__httpFetch(location, options)
return
}
this.status = STATUS.failed
this.emit('fail', response)
this.__clearTimeout()
this.__closeRequest()
void this.__closeWriteStream()
return
@@ -162,7 +139,6 @@ class Task extends EventEmitter {
return
}
this.status = STATUS.running
this.__startTimeout()
response
.on('data', this.__handleWriteData.bind(this))
.on('error', err => { this.__handleError(err) })
@@ -177,7 +153,6 @@ class Task extends EventEmitter {
})
.on('error', err => { this.__handleError(err) })
.on('close', () => {
if (redirected) return
void this.__closeWriteStream()
})
.end()
@@ -213,7 +188,6 @@ class Task extends EventEmitter {
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
this.ws.on('finish', () => {
if (this.closeWaiting) return
void this.__closeWriteStream()
})
this.ws.on('error', err => {
@@ -229,7 +203,6 @@ class Task extends EventEmitter {
__handleComplete() {
if (this.status == STATUS.error) return
this.__clearTimeout()
void this.__closeWriteStream().then(() => {
if (this.progress.downloaded == this.progress.total) {
this.status = STATUS.completed
@@ -245,7 +218,6 @@ class Task extends EventEmitter {
__handleError(error: Error) {
if (this.status == STATUS.error) return
this.status = STATUS.error
this.__clearTimeout()
this.__closeRequest()
void this.__closeWriteStream()
if (error.message == 'aborted') return
@@ -259,21 +231,16 @@ class Task extends EventEmitter {
return
}
// console.log('close write stream')
if (this.closeWaiting || this.dataWriteQueueLength) {
this.closeWaiting ||= true
this.ws.on('close', resolve)
} else {
this.ws.close(err => {
if (err) {
this.status = STATUS.error
this.emit('error', err)
reject(err)
return
}
this.ws = null
resolve()
})
}
this.ws.close(err => {
if (err) {
this.status = STATUS.error
this.emit('error', err)
reject(err)
return
}
this.ws = null
resolve()
})
})
}
@@ -289,7 +256,7 @@ class Task extends EventEmitter {
const result = this.__handleDiffChunk(chunk)
if (result) chunk = result
else {
void this.__handleStop().finally(() => {
this.__handleStop().finally(() => {
// this.__handleError(new Error('Resume failed, response chunk does not match.'))
// Resume failed, response chunk does not match, remove file and restart download
console.log('Resume failed, response chunk does not match.')
@@ -312,18 +279,12 @@ class Task extends EventEmitter {
console.log('cancel write')
return
}
this.dataWriteQueueLength++
this.__startTimeout()
this.__calculateProgress(chunk.length)
this.ws.write(chunk, err => {
this.dataWriteQueueLength--
if (this.status == STATUS.running) this.__calculateProgress(0)
if (err) {
console.log(err)
this.__handleError(err)
void this.stop()
}
if (this.closeWaiting && !this.dataWriteQueueLength) this.ws?.close()
if (!err) return
console.log(err)
this.__handleError(err)
void this.stop()
})
}
@@ -333,36 +294,36 @@ class Task extends EventEmitter {
let chunkLen = chunk.length
let isOk
if (chunkLen >= resumeLastChunkLen) {
isOk = chunk.subarray(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk!.toString('hex')
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk!.toString('hex')
if (!isOk) return null
this.resumeLastChunk = null
return chunk.subarray(resumeLastChunkLen)
return chunk.slice(resumeLastChunkLen)
} else {
isOk = chunk.subarray(0, chunkLen).toString('hex') === this.resumeLastChunk!.subarray(0, chunkLen).toString('hex')
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk!.slice(0, chunkLen).toString('hex')
if (!isOk) return null
this.resumeLastChunk = this.resumeLastChunk!.subarray(chunkLen)
return chunk.subarray(chunkLen)
this.resumeLastChunk = this.resumeLastChunk!.slice(chunkLen)
return chunk.slice(chunkLen)
}
}
async __handleStop() {
this.__clearTimeout()
this.__closeRequest()
return this.__closeWriteStream()
}
private __clearTimeout() {
if (!this.timeout) return
clearTimeout(this.timeout)
this.timeout = null
}
private __startTimeout() {
this.__clearTimeout()
this.timeout = setTimeout(() => {
this.__handleError(new Error('download timeout'))
}, this.options.timeout)
return new Promise<void>((resolve, reject) => {
this.__closeRequest()
if (this.ws) {
this.ws.close(err => {
if (err) {
reject(err)
this.emit('error', err)
return
}
this.ws = null
resolve()
})
} else {
resolve()
}
})
}
__calculateProgress(receivedBytes: number) {
@@ -375,7 +336,7 @@ class Task extends EventEmitter {
// emit the progress every second or if finished
if ((progress.downloaded === progress.total && this.dataWriteQueueLength == 0) || elaspsedTime > 1000) {
if (progress.downloaded === progress.total || elaspsedTime > 1000) {
this.statsEstimate.time = currentTime
this.statsEstimate.bytes = progress.downloaded - this.statsEstimate.prevBytes
this.statsEstimate.prevBytes = progress.downloaded
@@ -384,7 +345,6 @@ class Task extends EventEmitter {
downloaded: progress.downloaded,
progress: progress.progress,
speed: this.statsEstimate.bytes,
writeQueue: this.dataWriteQueueLength,
})
}
}

View File

@@ -74,7 +74,6 @@ export const createDownload = ({
speed,
downloaded: stats.downloaded,
total: stats.total,
writeQueue: stats.writeQueue,
})
// if (debugDownload) {
// const downloaded = sizeFormate(stats.downloaded)

View File

@@ -27,7 +27,7 @@ const sendRequest = (url: string, options: Options, callback?: HttpCallback) =>
}
if (options.params) {
(httpOptions.path!) += `${urlParse.search ? '&' : '?'}${Object.entries(options.params)
(httpOptions.path as string) += `${urlParse.search ? '&' : '?'}${Object.entries(options.params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&')}`
}

View File

@@ -4,24 +4,24 @@
// -------------------------------------------------------------------------------------------------
// Change the following variables to customize the appearance of the particles
let numParticles = window.innerWidth / 50
let maxSpeed = 2.5
let minSpeed = 1
let maxSize = 5
let minSize = 3
let maxOpacity = 1
let minOpacity = 0.2
var numParticles = window.innerWidth / 50;
var maxSpeed = 2.5;
var minSpeed = 1;
var maxSize = 5;
var minSize = 3;
var maxOpacity = 1;
var minOpacity = 0.2;
// If the following is true, the canvas resolution will be set to match the full viewport width and height.
// var fixCanvasResolution = true;
let snow_particles = []
var snow_particles = [];
let canvas
var canvas
window.onload = function() {
canvas = document.createElement('canvas')
canvas.style.position = 'fixed'
canvas.style.position = 'fixed';
canvas.style.top = '0px'
canvas.style.left = '0px'
canvas.style.pointerEvents = 'none'
@@ -30,62 +30,62 @@ window.onload = function() {
canvas.height = document.documentElement.clientHeight
document.body.appendChild(canvas)
// if (fixCanvasResolution) {
// // canvas.width = window.innerWidth;
// canvas.height = window.innerHeight;
// }
// if (fixCanvasResolution) {
// // canvas.width = window.innerWidth;
// canvas.height = window.innerHeight;
// }
InitPoints()
InitPoints();
window.requestAnimationFrame(Redraw, 15)
requestAnimationFrame(Redraw, 15);
window.addEventListener('resize', this.onWindowResize)
}
// function onWindowResize(e) {
// canvas.width = document.documentElement.clientWidth
// canvas.height = document.documentElement.clientHeight
// }
function onWindowResize (e) {
canvas.width = document.documentElement.clientWidth
canvas.height = document.documentElement.clientHeight
}
function Redraw() {
let ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
for (let i = 0; i < snow_particles.length; i++) {
let newYPos = snow_particles[i].yPos + snow_particles[i].speed
if (newYPos > window.innerHeight) {
newYPos = getRandomInt(-100, -10)
snow_particles[i].xPos = getRandomInt(0, window.innerWidth)
snow_particles[i].speed = Math.random() * (maxSpeed - minSpeed) + minSpeed
snow_particles[i].opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity
snow_particles[i].size = Math.random() * (maxSize - minSize) + minSize
}
snow_particles[i].yPos = newYPos
ctx.beginPath()
ctx.arc(snow_particles[i].xPos, newYPos, snow_particles[i].size, 0, 2 * Math.PI)
ctx.fillStyle = 'rgba(255, 255, 255, ' + snow_particles[i].opacity + ')'
ctx.fill()
}
window.requestAnimationFrame(Redraw)
var ctx = canvas.getContext("2d");
ctx.clearRect(0,0,canvas.width,canvas.height);
for (var i = 0; i < snow_particles.length; i++) {
var newYPos = snow_particles[i].yPos + snow_particles[i].speed;
if (newYPos > window.innerHeight) {
newYPos = getRandomInt(-100,-10);
snow_particles[i].xPos = getRandomInt(0, window.innerWidth);
snow_particles[i].speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
snow_particles[i].opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
snow_particles[i].size = Math.random() * (maxSize - minSize) + minSize;
}
snow_particles[i].yPos = newYPos;
ctx.beginPath();
ctx.arc(snow_particles[i].xPos, newYPos, snow_particles[i].size, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255, 255, 255, " + snow_particles[i].opacity + ")";
ctx.fill();
}
requestAnimationFrame(Redraw)
}
function InitPoints() {
for (let i = 0; i < numParticles; i++) {
let startX = getRandomInt(0, window.innerWidth)
let startY = getRandomInt(0, window.innerHeight)
let speed = Math.random() * (maxSpeed - minSpeed) + minSpeed
let opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity
let size = Math.random() * (maxSize - minSize) + minSize
for (var i = 0; i < numParticles; i++) {
var startX = getRandomInt(0, window.innerWidth);
var startY = getRandomInt(0, window.innerHeight);
var speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
var opacity = Math.random() * (maxOpacity - minOpacity) + minOpacity;
var size = Math.random() * (maxSize - minSize) + minSize;
snow_particles.push({
xPos: startX,
yPos: startY,
speed,
opacity,
size,
})
}
snow_particles.push({
"xPos": startX,
"yPos": startY,
"speed": speed,
"opacity": opacity,
"size": size,
});
}
}
function getRandomInt(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min)) + min
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}

View File

@@ -39,6 +39,5 @@ export const clipboardReadText = (): string => {
export const encodePath = (path: string) => {
// https://github.com/lyswhut/lx-music-desktop/issues/963
// https://github.com/lyswhut/lx-music-desktop/issues/1461
return path.replaceAll('%', '%25').replaceAll('#', '%23')
return path.replaceAll('%', '%25')
}

View File

@@ -1,5 +1,6 @@
import log from 'electron-log/node'
import log from 'electron-log'
log.transports.file.level = 'info'
export const isLinux = process.platform == 'linux'
export const isWin = process.platform == 'win32'

View File

@@ -1,4 +1,4 @@
import { getNow, TimeoutTools } from './utils'
const { getNow, TimeoutTools } = require('./utils')
// const fontFormateRxp = /(?=<\d+,\d+>).*?/g
const fontSplitRxp = /(?=<\d+,\d+>).*?/g
@@ -25,7 +25,7 @@ const createAnimation = (dom, duration, isVertical) => new window.Animation(new
// https://jsfiddle.net/ceqpnbky/
// https://jsfiddle.net/ceqpnbky/1/
export default class FontPlayer {
module.exports = class FontPlayer {
constructor({
time = 0,
rate = 1,
@@ -69,8 +69,8 @@ export default class FontPlayer {
this.lineContent = null
this.timeoutTools = new TimeoutTools(50)
this.waitPlayTimeout = new TimeoutTools(50)
this.timeoutTools = new TimeoutTools(80)
this.waitPlayTimeout = new TimeoutTools(80)
this._init()
}
@@ -133,8 +133,8 @@ export default class FontPlayer {
// let lineText = ''
let lrcShadowContent
for (const font of fonts) {
if (!timeRxp.test(font)) return this._handleLineParse()
text = font.replace(timeRxp, '')
if (RegExp.$2 == '') return this._handleLineParse()
const time = parseInt(RegExp.$2)
const dom = document.createElement('span')

View File

@@ -1,9 +1,9 @@
import LinePlayer from './line-player'
import FontPlayer from './font-player'
const LinePlayer = require('./line-player')
const FontPlayer = require('./font-player')
const fontTimeExp = /<(\d+),(\d+)>/g
export default class Lyric {
module.exports = class Lyric {
constructor({
lyric = '',
extendedLyrics = [],
@@ -68,7 +68,7 @@ export default class Lyric {
_handleLinePlayerOnPlay = (num, text, curTime) => {
if (this.isLineMode) {
if (num < this.playingLineNum + 1) {
for (let i = this.playingLineNum, minNum = Math.max(num, 0) - 1; i > minNum; i--) {
for (let i = this.playingLineNum; i > num - 1; i--) {
const font = this._lineFonts[i]
font.reset()
font.lineContent.classList.remove(this.activeLineClassName)
@@ -86,7 +86,7 @@ export default class Lyric {
}
} else {
if (num < this.playingLineNum + 1) {
for (let i = this.playingLineNum, minNum = Math.max(num, 0) - 1; i > minNum; i--) {
for (let i = this.playingLineNum; i > num - 1; i--) {
const font = this._lineFonts[i]
font.lineContent.classList.remove(this.activeLineClassName)
font.reset()
@@ -103,12 +103,10 @@ export default class Lyric {
}
}
this.playingLineNum = num
if (num > -1) {
const font = this._lineFonts[num]
font.lineContent.classList.add(this.activeLineClassName)
font.play(curTime - this._lines[num].time)
}
this.onPlay(num, this._lines[num]?.text ?? '')
const font = this._lineFonts[num]
font.lineContent.classList.add(this.activeLineClassName)
font.play(curTime - this._lines[num].time)
this.onPlay(num, this._lines[num].text)
}
_initLines = (lyricLines, offset, isUpdate) => {

View File

@@ -1,4 +1,4 @@
import { getNow, TimeoutTools } from './utils'
const { getNow, TimeoutTools } = require('./utils')
const timeFieldExp = /^(?:\[[\d:.]+\])+/g
const timeExp = /\d{1,3}(:\d{1,3}){0,2}(?:\.\d{1,3})/g
@@ -29,8 +29,7 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
if (result) {
const timeField = result[0]
const text = line.replace(timeFieldExp, '').trim()
// https://github.com/lyswhut/lx-music-desktop/issues/1499
if (text && text != '//') {
if (text) {
const times = timeField.match(timeExp)
if (times == null) continue
for (let time of times) {
@@ -43,7 +42,7 @@ const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
}
}
export default class LinePlayer {
module.exports = class LinePlayer {
constructor({ offset = 0, rate = 1, onPlay = function() { }, onSetLyric = function() { } } = {}) {
this.tags = {}
this.lines = null
@@ -148,7 +147,7 @@ export default class LinePlayer {
const currentTime = this._currentTime()
const driftTime = currentTime - curLine.time
if (driftTime >= 0) {
if (driftTime >= 0 || this.curLineNum === 0) {
let nextLine = this.lines[this.curLineNum + 1]
const delay = (nextLine.time - curLine.time - driftTime) / this._rate
@@ -167,17 +166,6 @@ export default class LinePlayer {
this._refresh()
return
}
} else if (this.curLineNum == 0) {
let firstLine = this.lines[0]
const delay = (firstLine.time - currentTime) / this._rate
if (this.isPlay) {
timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, delay)
}
this.onPlay(-1, '', currentTime)
return
}
this.curLineNum = this._findCurLineNum(currentTime, this.curLineNum) - 1

View File

@@ -1,8 +1,8 @@
export const getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
const getNow = exports.getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
export class TimeoutTools {
constructor(thresholdTime = 80) {
exports.TimeoutTools = class TimeoutTools {
constructor(thresholdTime = 200) {
this.invokeTime = 0
this.animationFrameId = null
this.timeoutId = null
@@ -17,7 +17,6 @@ export class TimeoutTools {
// console.log('diff', diff)
if (diff > 0) {
if (diff < this.thresholdTime) return this.run()
// console.log('run timeout', diff, diff - this.thresholdTime)
return this.timeoutId = window.setTimeout(() => {
this.timeoutId = null
this.run()

View File

@@ -59,7 +59,7 @@ const writeMeta = async(filePath, meta, picPath) => {
module.exports = (filePath, meta) => {
if (!meta.APIC) return writeMeta(filePath, meta)
let picUrl = meta.APIC
const picUrl = meta.APIC
delete meta.APIC
if (!/^http/.test(picUrl)) {
return writeMeta(filePath, meta)
@@ -67,7 +67,6 @@ module.exports = (filePath, meta) => {
let ext = path.extname(picUrl)
let picPath = filePath.replace(/\.flac$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
if (success) {
writeMeta(filePath, meta, picPath).finally(() => {

View File

@@ -23,10 +23,7 @@ module.exports = (filePath, meta) => {
}
let ext = path.extname(meta.APIC)
let picPath = filePath.replace(/\.mp3$/, '') + (ext ? ext.replace(extReg, '$1') : '.jpg')
let picUrl = meta.APIC
if (picUrl.includes('music.126.net')) picUrl += `${picUrl.includes('?') ? '&' : '?'}param=500y500`
download(picUrl, picPath).then(success => {
download(meta.APIC, picPath).then(success => {
if (success) {
meta.APIC = picPath
handleWriteMeta(meta, filePath)

View File

@@ -1,8 +1,8 @@
import fs from 'node:fs'
import crypto from 'node:crypto'
import { gzip, gunzip } from 'node:zlib'
import path from 'node:path'
import fs from 'fs'
import crypto from 'crypto'
import { gzip, gunzip } from 'zlib'
import { log } from '@common/utils'
import path from 'path'
export const joinPath = (...paths: string[]): string => path.join(...paths)
@@ -15,7 +15,7 @@ export const dirname = (p: string): string => path.dirname(p)
* @param {*} path 路径
*/
export const checkPath = async(path: string): Promise<boolean> => {
return new Promise(resolve => {
return await new Promise(resolve => {
if (!path) {
resolve(false)
return
@@ -31,7 +31,7 @@ export const checkPath = async(path: string): Promise<boolean> => {
}
export const getFileStats = async(path: string): Promise<fs.Stats | null> => {
return new Promise(resolve => {
return await new Promise(resolve => {
if (!path) {
resolve(null)
return
@@ -97,7 +97,7 @@ export const readFile = async(path: string) => fs.promises.readFile(path)
export const toMD5 = (str: string) => crypto.createHash('md5').update(str).digest('hex')
export const gzipData = async(str: string): Promise<Buffer> => {
return new Promise((resolve, reject) => {
return await new Promise((resolve, reject) => {
gzip(str, (err, result) => {
if (err) {
reject(err)
@@ -109,7 +109,7 @@ export const gzipData = async(str: string): Promise<Buffer> => {
}
export const gunzipData = async(buf: Buffer): Promise<string> => {
return new Promise((resolve, reject) => {
return await new Promise((resolve, reject) => {
gunzip(buf, (err, result) => {
if (err) {
reject(err)
@@ -140,8 +140,8 @@ export const saveLxConfigFile = async(path: string, data: any) => {
export const readLxConfigFile = async(path: string): Promise<any> => {
let isJSON = path.endsWith('.json')
let data: string | Buffer = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
if (!data) return data
if (!isJSON) data = await gunzipData(Buffer.from(data, 'binary'))
if (!data || isJSON) return data
data = await gunzipData(Buffer.from(data, 'binary'))
data = JSON.parse(data)
// 修复v1.14.0出现的导出数据被序列化两次的问题

View File

@@ -8,31 +8,15 @@ const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
type Noop = () => void
const noop: Noop = () => {}
type ScrollElement<T> = {
lx_scrollLockKey?: number
lx_scrollNextParams?: [ScrollElement<HTMLElement>, number, number, Noop]
lx_scrollTimeout?: number
lx_scrollDelayTimeout?: number
} & T
const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = noop): Noop => {
const handleScrollY = (element: HTMLElement, to: number, duration = 300, fn = noop): Noop => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollTop ?? element.scrollY ?? 0
let cancel = false
if (to > start) {
let maxScrollTop = element.scrollHeight - element.clientHeight
if (to > maxScrollTop) to = maxScrollTop
@@ -50,19 +34,9 @@ const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration
}
let currentTime = 0
let val: number
let key = Math.random()
let val
const animateScroll = () => {
element.lx_scrollTimeout = undefined
// if (element.lx_scrollLockKey != key) {
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
@@ -71,23 +45,19 @@ const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration
element.scrollTop = val
}
if (currentTime < duration) {
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
} else {
fn()
}
}
element.lx_scrollLockKey = key
animateScroll()
return clean
return () => {
cancel = true
}
}
/**
* 设置滚动条位置
@@ -97,24 +67,16 @@ const handleScrollY = (element: ScrollElement<HTMLElement>, to: number, duration
* @param {*} fn 滚动完成后的回调
* @param {*} delay 延迟执行时间
*/
export const scrollTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: () => void
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
let timeout: number | null
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
}
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
timeout = window.setTimeout(() => {
timeout = null
scrollCancelFn = handleScrollY(element, to, duration, fn)
}, delay)
} else {
@@ -122,24 +84,14 @@ export const scrollTo = (element: ScrollElement<HTMLElement>, to: number, durati
}
return cancelFn
}
const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
const handleScrollX = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollLeft || element.scrollX || 0
let cancel = false
if (to > start) {
let maxScrollLeft = element.scrollWidth - element.clientWidth
if (to > maxScrollLeft) to = maxScrollLeft
@@ -157,18 +109,9 @@ const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration
}
let currentTime = 0
let val: number
let key = Math.random()
let val
const animateScroll = () => {
element.lx_scrollTimeout = undefined
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
@@ -177,21 +120,19 @@ const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration
element.scrollLeft = val
}
if (currentTime < duration) {
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
} else {
fn()
}
}
element.lx_scrollLockKey = key
animateScroll()
return clean
return () => {
cancel = true
}
}
/**
* 设置滚动条位置
@@ -201,24 +142,16 @@ const handleScrollX = (element: ScrollElement<HTMLElement>, to: number, duration
* @param {*} fn 滚动完成后的回调
* @param {*} delay 延迟执行时间
*/
export const scrollXTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollXTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: Noop
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
let timeout: number | null
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
}
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
timeout = window.setTimeout(() => {
timeout = null
scrollCancelFn = handleScrollX(element, to, duration, fn)
}, delay)
} else {
@@ -227,24 +160,14 @@ export const scrollXTo = (element: ScrollElement<HTMLElement>, to: number, durat
return cancelFn
}
const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}): () => void => {
const handleScrollXR = (element: HTMLElement, to: number, duration = 300, fn = () => {}): () => void => {
if (!element) {
fn()
return noop
}
const clean = () => {
element.lx_scrollLockKey = undefined
element.lx_scrollNextParams = undefined
if (element.lx_scrollTimeout) window.clearTimeout(element.lx_scrollTimeout)
element.lx_scrollTimeout = undefined
}
if (element.lx_scrollLockKey) {
element.lx_scrollNextParams = [element, to, duration, fn]
element.lx_scrollLockKey = -1
return clean
}
// @ts-expect-error
const start = element.scrollLeft || element.scrollX as number || 0
let cancel = false
if (to < start) {
let maxScrollLeft = -element.scrollWidth + element.clientWidth
if (to < maxScrollLeft) to = maxScrollLeft
@@ -263,18 +186,9 @@ const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duratio
}
let currentTime = 0
let val: number
let key = Math.random()
let val
const animateScroll = () => {
element.lx_scrollTimeout = undefined
if (element.lx_scrollNextParams && currentTime > duration * 0.75) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
@@ -284,23 +198,19 @@ const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duratio
element.scrollLeft = val
}
if (currentTime < duration) {
element.lx_scrollTimeout = window.setTimeout(animateScroll, increment)
} else {
if (element.lx_scrollNextParams) {
const [_element, to, duration, fn] = element.lx_scrollNextParams
clean()
handleScrollY(_element, to, duration, fn)
} else {
clean()
if (cancel) {
fn()
return
}
window.setTimeout(animateScroll, increment)
} else {
fn()
}
}
element.lx_scrollLockKey = key
animateScroll()
return clean
return () => {
cancel = true
}
}
/**
* 设置滚动条位置 writing-mode: vertical-rl 专用)
@@ -310,24 +220,16 @@ const handleScrollXR = (element: ScrollElement<HTMLElement>, to: number, duratio
* @param fn 滚动完成后的回调
* @param delay 延迟执行时间
*/
export const scrollXRTo = (element: ScrollElement<HTMLElement>, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
export const scrollXRTo = (element: HTMLElement, to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: Noop
if (element.lx_scrollDelayTimeout != null) {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
let timeout: number | null
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
if (element.lx_scrollDelayTimeout == null) {
scrollCancelFn?.()
} else {
window.clearTimeout(element.lx_scrollDelayTimeout)
element.lx_scrollDelayTimeout = undefined
}
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
}
element.lx_scrollDelayTimeout = window.setTimeout(() => {
element.lx_scrollDelayTimeout = undefined
timeout = window.setTimeout(() => {
timeout = null
scrollCancelFn = handleScrollXR(element, to, duration, fn)
}, delay)
} else {

View File

@@ -17,7 +17,11 @@ import {
type ComputedRef,
type Ref,
type ShallowRef,
defineProps,
defineEmits,
defineComponent,
shallowReactive,
defineExpose,
withDefaults,
} from 'vue'
// import { useStore } from 'vuex'
@@ -71,7 +75,11 @@ export {
unref,
onMounted,
markRaw,
defineProps,
defineEmits,
defineComponent,
shallowReactive,
defineExpose,
withDefaults,
}

View File

@@ -1,15 +0,0 @@
/* eslint-env node */
const { base, typescript } = require('../../.eslintrc.base.cjs')
module.exports = {
root: true,
...base,
overrides: [
{
...typescript,
parserOptions: {
project: './tsconfig.json',
},
},
],
}

View File

@@ -8,7 +8,6 @@
"btn_confirm": "Confirm",
"btn_save": "Save",
"cancel_button_text": "Cancel",
"cancel_button_text_2": "No, no, no, wrong click",
"close": "Close",
"comment__hot_load_error": "Hot comments failed to load, click to try to reload",
"comment__hot_loading": "Hot comments are loading",
@@ -71,7 +70,6 @@
"download_status_error_write": "The song save location is occupied or does not have write permission, please try to change the song save directory or restart the software or restart the computer, the error details:",
"download_status_start": "start download",
"download_status_url_geting": "Getting music link...",
"download_status_write_queue": "Data is being written ({num})",
"duplicate_list_tip": "You have favorited this list [{name}] before, do you need to update the songs in it?",
"export": "Export",
"fullscreen_exit": "Exit Full Screen",
@@ -83,7 +81,6 @@
"list__add_to": "Add to ...",
"list__collect": "Collect",
"list__copy_name": "Copy name",
"list__dislike": "Dislike",
"list__download": "Download",
"list__export_part_desc": "Choose where to save the list file",
"list__file": "Locate the file",
@@ -119,7 +116,6 @@
"list_add__title_first_add": "Add",
"list_add__title_first_move": "Move",
"list_add__title_last": "to...",
"list_duplicate_tip": "A list with the same name already exists. Do you want to continue creating it?",
"list_import_tip__alldata": "Import failed, this is an all data backup file, you need to go here to import: \nSettings -> Backup and Restore -> All Data -> Import",
"list_import_tip__playlist": "Import failed, this is a list backup file, you need to go here to import: \nSettings -> Backup and Restore -> Import List",
"list_import_tip__playlist_part": "The import failed, this is a single-list file, you need to go here to import: \nMy List -> Right-click on any list name -> Select Import in the pop-up menu",
@@ -129,7 +125,6 @@
"list_sort_modal_by_down": "Descending",
"list_sort_modal_by_field": "Sort field",
"list_sort_modal_by_name": "Song name",
"list_sort_modal_by_random": "Random",
"list_sort_modal_by_singer": "Singer name",
"list_sort_modal_by_source": "Song source",
"list_sort_modal_by_time": "Duration",
@@ -141,7 +136,6 @@
"list_update_modal__title": "List update management",
"list_update_modal__update": "Sync",
"lists__add_local_file_desc": "Select song file",
"lists__dislike_music_tip": "Do you really dislike {name}?",
"lists__duplicate": "Duplicate song",
"lists__export": "Export",
"lists__export_part_desc": "Choose where to save the list file",
@@ -153,7 +147,7 @@
"lists__new_list_btn": "Create list",
"lists__new_list_input": "New list...",
"lists__remove": "Remove",
"lists__remove_music_tip": "Do you really want to remove the selected {len} songs?",
"lists__remove music_tip": "Do you really want to remove the selected {len} songs?",
"lists__remove_tip": "Do you really want to remove {name}?",
"lists__remove_tip_button": "Yes, that's right",
"lists__rename": "Rename",
@@ -164,7 +158,7 @@
"lists__sync_confirm_tip": "This will replace the songs in {name} with the songs in the online list, are you sure you want to update?",
"load_list_file_error_detail": "We have helped you back up the old list file to {path}\nIt is stored in JSON format, you can try to repair and restore it manually\n\nError details: {detail}",
"load_list_file_error_title": "Error loading playlist data",
"loding": "Loading...",
"loding_list": "Loading...",
"love_list": "Favorites",
"lyric__load_error": "Failed to get lyrics",
"lyric__select": "Lyric text selection",
@@ -229,7 +223,6 @@
"player__play_toggle_mode_off": "Disable",
"player__play_toggle_mode_random": "List Random",
"player__play_toggle_mode_single_loop": "Single Loop",
"player__playback_preserves_pitch": "Pitch compensation",
"player__playback_rate": "Current playback rate:",
"player__playback_rate_reset_btn": "Reset",
"player__playing": "Now playing...",
@@ -260,6 +253,7 @@
"player__sound_effect_convolution_file_matrix_2": "Matrix 2",
"player__sound_effect_convolution_file_s2_r4_bd": "Church",
"player__sound_effect_convolution_file_s3_r1_bd": "Stereo",
"player__sound_effect_convolution_file_spreader25_125ms": "Indoor 2",
"player__sound_effect_convolution_file_spreader50_65ms": "Indoor",
"player__sound_effect_convolution_file_telephone": "Telephone",
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "Rock 2",
@@ -269,10 +263,6 @@
"player__sound_effect_panner_enabled": "enable",
"player__sound_effect_panner_sound_r": "Sound distance",
"player__sound_effect_panner_sound_speed": "Surround speed",
"player__sound_effect_pitch_shifter": "Pitch adjustment",
"player__sound_effect_pitch_shifter_preset_semitones": "{num} semitones",
"player__sound_effect_pitch_shifter_reset_btn": "Reset",
"player__sound_effect_pitch_shifter_tip": "Since ups and downs need to process audio data in real time, this will cause additional CPU usage\n\nKnown issues:\nIf the CPU resources are not enough, the processing will cause the task to accumulate and the sound will be abnormal. At this time, it is necessary to pause the playback for a period of time and wait for the accumulated tasks to be processed before playing.",
"player__stop": "Paused",
"player__volume": "Volume: ",
"player__volume_mute_label": "Mute",
@@ -386,10 +376,6 @@
"setting__desktop_lyric_shadow_color": "Shadow color",
"setting__desktop_lyric_show_taskbar": "Display lyrics progress on the taskbar (this setting is used as a workaround when the screen recording software cannot capture the lyrics window)",
"setting__desktop_lyric_unplay_color": "Color not playing",
"setting__dislike_list_input_tip": "song name@artist name\nSong name\n@ singer name",
"setting__dislike_list_save_btn": "Save",
"setting__dislike_list_tips": "1. If there is a \"@\" symbol in the song or singer's name, you need to replace it with \"#\"\n2. Specify a song of a singer: Name@Singer\n3. Specify a song: Name\n4. Specify a certain singer: @Singer",
"setting__dislike_list_title": "List of Disliked Song Rules",
"setting__download": "Download",
"setting__download_data_embed": "Whether to embed the following content in the audio file",
"setting__download_embed_lyric": "Embedding lyric",
@@ -429,9 +415,6 @@
"setting__hot_key_desktop_lyric_toggle_visible": "Turn on/off desktop lyrics",
"setting__hot_key_global_title": "Global Shortcut Key",
"setting__hot_key_local_title": "Shortcut Keys in Software",
"setting__hot_key_player_music_dislike": "Dislike the song",
"setting__hot_key_player_music_love": "Favorites Song",
"setting__hot_key_player_music_unlove": "Cancel collection",
"setting__hot_key_player_next": "Next Song",
"setting__hot_key_player_prev": "Previous Song",
"setting__hot_key_player_toggle_play": "Play/Pause Control",
@@ -460,9 +443,6 @@
"setting__odc_clear_search_input": "Clear the search box when you are not searching",
"setting__odc_clear_search_list": "Clear the search list when you are not searching",
"setting__other": "Extras",
"setting__other_dislike_list": "dislike song rule",
"setting__other_dislike_list_label": "Number of rules:",
"setting__other_dislike_list_show_btn": "Edit dislike song rules",
"setting__other_listdata": "List Data Cleanup",
"setting__other_listdata_clear_btn": "Clear my list data",
"setting__other_listdata_clear_tip_confirm": "This will clear all lists you have created and all songs in your favourites, do you really want to continue?",
@@ -488,7 +468,6 @@
"setting__other_tray_theme_native": "White",
"setting__other_tray_theme_origin": "Primary Color",
"setting__play": "Play",
"setting__play_auto_clean_played_list": "Whether to clear the existing playlist when clicking the same list as the playlist to switch songs (all songs in the list in random mode will participate in the random again)",
"setting__play_auto_skip_on_error": "Automatically switch songs on playback error",
"setting__play_detail": "Play details page settings",
"setting__play_detail_align": "Lyric Alignment",
@@ -509,7 +488,6 @@
"setting__play_mediaDevice_title": "Select a media device for audio output",
"setting__play_media_device_error_tip": "This function conflicts with advanced audio functions (audio visualization, sound effect settings). These functions have been enabled when you start the software this time. This setting is not available for now. Please close these functions and restart the software before modifying this setting.",
"setting__play_media_device_tip": "This feature conflicts with Audio Visualization, both cannot be enabled at the same time, would you like to turn Audio Visualization off and apply the selected audio output settings?",
"setting__play_power_save_blocker": "Prevent computer from sleeping while playing songs",
"setting__play_quality": "Priority playback of 320K quality songs (if available)",
"setting__play_save_play_time": "Remember playback progress",
"setting__play_startup_auto_play": "Play music automatically after launching the software",
@@ -538,16 +516,10 @@
"setting__sync_server_address": "Synchronization service address: {address}",
"setting__sync_server_auth_code": "Connection code: {code}",
"setting__sync_server_device": "Connected devices: {devices}",
"setting__sync_server_device_list_btn_remove": "Remove",
"setting__sync_server_device_list_noitem": "Nothing here ┗( ▔, ▔ )┛",
"setting__sync_server_device_list_time": "Last connection time: {time}",
"setting__sync_server_device_list_tips": "💡 After the device is removed, you need to re-enter the connection code when reconnecting",
"setting__sync_server_device_list_title": "Certified device",
"setting__sync_server_mode": "Server mode (since the data is transmitted in clear text, please use it under a trusted network)",
"setting__sync_server_port": "Sync port settings",
"setting__sync_server_port_tip": "Please enter the synchronization service port number",
"setting__sync_server_refresh_code": "Refresh the connection code",
"setting__sync_server_show_device_list": "List of certified devices",
"setting__sync_tip": "For how to use it, please see the \"Sync function\" section of the FAQ",
"setting__update": "Update",
"setting__update_checking": "Checking for updates...",
@@ -562,7 +534,6 @@
"setting__update_show_change_log": "Show changelog on first boot after version update",
"setting__update_try_auto_update": "Attempt to download updates automatically when a new version is found",
"setting__update_unknown": "Unknown",
"setting__update_unknown_tip": "❓ Failed to obtain the latest version information, it is recommended to go to the About interface to open the project release address to check whether the current version is the latest",
"setting_sync_status_enabled": "connected",
"song_list": "Playlists",
"songlist__import_input_btn_confirm": "Open",
@@ -593,20 +564,14 @@
"source_xm": "Xiami",
"sync__auth_code_input_tip": "Please enter the connection code",
"sync__auth_code_title": "Need to enter the connection code",
"sync__dislike_merge_tip_desc": "Merge the content of the two lists and remove the duplicates",
"sync__dislike_other_tip_desc": "\"Cancel sync\" will not use the dislike list sync feature",
"sync__dislike_overwrite_tip_desc": "The list of overriddens will be replaced with the list of overriders",
"sync__dislike_title": "Choose how to sync with {name}'s dislike list",
"sync__list_merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
"sync__list_other_tip_desc": "\"Cancel Sync\" will not use list sync.",
"sync__list_overwrite_tip_desc": "Lists with the same ID as the overwritten list and the overwritten list will be deleted and replaced with the overrider's list (lists with different list IDs will be merged together). If full coverage is checked, all lists of the covered one will be moved. Remove and replace with a list of overrides.",
"sync__list_title": "Choose how to synchronize the list with {name}",
"sync__merge_btn_local_remote": "Local list merge remote list",
"sync__merge_btn_remote_local": "Remote list merge local list",
"sync__merge_label": "Merge",
"sync__merge_tip": "Merge:",
"sync__merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
"sync__other_label": "Other",
"sync__other_tip": "Other: ",
"sync__other_tip_desc": "\"Cancel Sync\" will directly disconnect the two parties.",
"sync__overwrite": "Full coverage",
"sync__overwrite_btn_cancel": "Cancel sync",
"sync__overwrite_btn_local_remote": "Local list Overwrite remote list",
@@ -614,6 +579,8 @@
"sync__overwrite_btn_remote_local": "Remote list Overwrite local list",
"sync__overwrite_label": "Cover",
"sync__overwrite_tip": "Over: ",
"sync__overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
"sync__title": "Choose how to synchronize the list with {name}",
"sync_status_disabled": "not connected",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
@@ -686,7 +653,6 @@
"user_api__btn_import": "Import",
"user_api__btn_remove": "Remove",
"user_api__import_file": "Select music API script file",
"user_api__init_failed_alert": "Custom source [{name}] failed to initialize:",
"user_api__max_tip": "There can only be a maximum of 20 sources at the same time🤪\nIf you want to continue importing, please remove some old sources to make room",
"user_api__noitem": "There is nothing here...😲",
"user_api__note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.",

View File

@@ -76,7 +76,7 @@ const createI18n = (): I18n => {
return val ? this.fillMessage(targetMessage, val) : targetMessage
},
t(key: keyof Message, val?: TranslateValues): string {
trackReactivityValues()
// trackReactivityValues()
return this.getMessage(key, val)
},
}

View File

@@ -1,8 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
// "typeRoots": [
// "./types"
// ],
"module": "esnext", /* Specify what module code is generated. */
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
},
// "include": [
// "**/*.ts",
// "**/*.js",
// "**/*.vue",
// "**/*.json",
// ],
// "exclude": ["dbService"]
}

View File

@@ -8,7 +8,6 @@
"btn_confirm": "确定",
"btn_save": "保存",
"cancel_button_text": "我不",
"cancel_button_text_2": "不不不,点错了",
"close": "关闭",
"comment__hot_load_error": "热门评论加载失败,点击尝试重新加载",
"comment__hot_loading": "热门评论加载中",
@@ -71,7 +70,6 @@
"download_status_error_write": "歌曲保存位置被占用或没有写入权限,请尝试更改歌曲保存目录或重启软件或重启电脑,错误详情:",
"download_status_start": "开始下载",
"download_status_url_geting": "音乐链接获取中...",
"download_status_write_queue": "数据写入中({num}",
"duplicate_list_tip": "你之前已收藏过该列表 [{name}],是否需要更新里面的歌曲?",
"export": "导出",
"fullscreen_exit": "退出全屏",
@@ -83,7 +81,6 @@
"list__add_to": "添加到...",
"list__collect": "收藏",
"list__copy_name": "复制歌曲名",
"list__dislike": "不喜欢",
"list__download": "下载",
"list__export_part_desc": "选择列表文件保存位置",
"list__file": "定位文件",
@@ -119,7 +116,6 @@
"list_add__title_first_add": "添加",
"list_add__title_first_move": "移动",
"list_add__title_last": "到...",
"list_duplicate_tip": "已存在同名列表,是否仍要继续创建?",
"list_import_tip__alldata": "导入失败,这是一个所有数据备份文件,你需要去这里导入:\n设置 -> 备份与恢复 -> 所有数据 -> 导入",
"list_import_tip__playlist": "导入失败,这是一个列表备份文件,你需要去这里导入:\n设置 -> 备份与恢复 -> 部分数据 -> 导入列表",
"list_import_tip__playlist_part": "导入失败,这是一个单列表文件,你需要去这里导入:\n我的列表 -> 右击任意一个列表名 -> 在弹出的菜单中选择导入",
@@ -129,7 +125,6 @@
"list_sort_modal_by_down": "降序",
"list_sort_modal_by_field": "排序字段",
"list_sort_modal_by_name": "歌曲名",
"list_sort_modal_by_random": "随机乱序",
"list_sort_modal_by_singer": "歌手名",
"list_sort_modal_by_source": "歌曲源",
"list_sort_modal_by_time": "时长",
@@ -141,7 +136,6 @@
"list_update_modal__title": "列表更新管理",
"list_update_modal__update": "立即更新",
"lists__add_local_file_desc": "选择歌曲文件",
"lists__dislike_music_tip": "你真的不喜欢 {name} 吗?",
"lists__duplicate": "重复歌曲",
"lists__export": "导出",
"lists__export_part_desc": "选择列表文件保存位置",
@@ -153,7 +147,7 @@
"lists__new_list_btn": "新建列表",
"lists__new_list_input": "新列表...",
"lists__remove": "删除",
"lists__remove_music_tip": "你真的要移除所选的 {len} 首歌曲吗?",
"lists__remove music_tip": "你真的要移除所选的 {len} 首歌曲吗?",
"lists__remove_tip": "你真的想要移除 {name} 吗?",
"lists__remove_tip_button": "是的 没错",
"lists__rename": "重命名",
@@ -229,7 +223,6 @@
"player__play_toggle_mode_off": "禁用",
"player__play_toggle_mode_random": "列表随机",
"player__play_toggle_mode_single_loop": "单曲循环",
"player__playback_preserves_pitch": "音调补偿",
"player__playback_rate": "当前播放速率:",
"player__playback_rate_reset_btn": "重置",
"player__playing": "播放中...",
@@ -269,10 +262,6 @@
"player__sound_effect_panner_enabled": "启用",
"player__sound_effect_panner_sound_r": "声音距离",
"player__sound_effect_panner_sound_speed": "环绕速度",
"player__sound_effect_pitch_shifter": "音调升降调节",
"player__sound_effect_pitch_shifter_preset_semitones": "{num} 半音",
"player__sound_effect_pitch_shifter_reset_btn": "重置",
"player__sound_effect_pitch_shifter_tip": "由于升降调需要实时处理音频数据这会导致额外的CPU占用\n\n已知问题\n如果CPU资源不够时将处理导致任务堆积而出现声音异常这时需要暂停播放一段时间等堆积的任务处理完毕再播放",
"player__stop": "暂停播放",
"player__volume": "当前音量:",
"player__volume_mute_label": "静音",
@@ -386,10 +375,6 @@
"setting__desktop_lyric_shadow_color": "阴影颜色",
"setting__desktop_lyric_show_taskbar": "在任务栏显示歌词进程(此设置用于在录屏软件无法捕获歌词窗口时的变通解决方法)",
"setting__desktop_lyric_unplay_color": "未播放颜色",
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
"setting__dislike_list_save_btn": "保存",
"setting__dislike_list_tips": "1. 每条一行,若歌曲或者歌手名字中存在“@”符号,需要将其替换成“#”\n2. 指定某歌手的某首歌:歌曲名@歌手名\n3. 指定某首歌:歌曲名\n4. 指定某歌手:@歌手名",
"setting__dislike_list_title": "不喜欢的歌曲规则列表",
"setting__download": "下载设置",
"setting__download_data_embed": "是否将以下内容嵌入到音频文件中",
"setting__download_embed_lyric": "歌词嵌入",
@@ -429,9 +414,6 @@
"setting__hot_key_desktop_lyric_toggle_visible": "开/关桌面歌词",
"setting__hot_key_global_title": "全局快捷键",
"setting__hot_key_local_title": "软件内快捷键",
"setting__hot_key_player_music_dislike": "不喜欢该歌曲",
"setting__hot_key_player_music_love": "收藏歌曲",
"setting__hot_key_player_music_unlove": "取消收藏",
"setting__hot_key_player_next": "下一首歌曲",
"setting__hot_key_player_prev": "上一首歌曲",
"setting__hot_key_player_toggle_play": "播放/暂停控制",
@@ -460,9 +442,6 @@
"setting__odc_clear_search_input": "离开搜索界面时清空搜索框",
"setting__odc_clear_search_list": "离开搜索界面时清空搜索列表",
"setting__other": "其他",
"setting__other_dislike_list": "不喜欢的歌曲规则",
"setting__other_dislike_list_label": "规则数量:",
"setting__other_dislike_list_show_btn": "编辑不喜欢歌曲规则",
"setting__other_listdata": "列表数据清理",
"setting__other_listdata_clear_btn": "清空我的列表数据",
"setting__other_listdata_clear_tip_confirm": "这将清理你创建的 所有列表 及收藏的 所有歌曲,是否真的要继续?",
@@ -488,7 +467,6 @@
"setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色",
"setting__play": "播放设置",
"setting__play_auto_clean_played_list": "点击与播放列表相同的列表切歌时是否清空已播放列表(随机模式下列表内所有歌曲会重新参与随机)",
"setting__play_auto_skip_on_error": "播放错误时自动切换歌曲",
"setting__play_detail": "播放详情页设置",
"setting__play_detail_align": "歌词对齐方式",
@@ -509,7 +487,6 @@
"setting__play_mediaDevice_title": "选择声音输出的媒体设备",
"setting__play_media_device_error_tip": "此功能与高级音频功能(音频可视化、音效设置)冲突,你本次启动软件时已启用这些功能,此设置暂不可用,请 关闭这些功能 并 重启 软件后,再来修改此设置。",
"setting__play_media_device_tip": "此功能与音频可视化功能冲突,两者无法同时启用,是否将音频可视化关闭 并 应用所选音频输出设置?",
"setting__play_power_save_blocker": "播放歌曲时阻止电脑休眠",
"setting__play_quality": "优先播放320K品质的歌曲如果可用",
"setting__play_save_play_time": "记住播放进度",
"setting__play_startup_auto_play": "启动软件后自动播放音乐",
@@ -538,16 +515,10 @@
"setting__sync_server_address": "同步服务地址:{address}",
"setting__sync_server_auth_code": "连接码:{code}",
"setting__sync_server_device": "已连接的设备:{devices}",
"setting__sync_server_device_list_btn_remove": "移除",
"setting__sync_server_device_list_noitem": "这里啥也没有 ┗( ▔, ▔ )┛",
"setting__sync_server_device_list_time": "最后连接时间:{time}",
"setting__sync_server_device_list_tips": "💡 设备被移除后,再连接时需要重新输入连接码",
"setting__sync_server_device_list_title": "已认证设备",
"setting__sync_server_mode": "服务端模式(由于数据是明文传输,请在受信任的网络下使用)",
"setting__sync_server_port": "同步端口设置",
"setting__sync_server_port_tip": "请输入同步服务端口号",
"setting__sync_server_refresh_code": "刷新连接码",
"setting__sync_server_show_device_list": "已认证设备列表",
"setting__sync_tip": "使用方式请看常见问题“同步功能”部分",
"setting__update": "软件更新",
"setting__update_checking": "检查更新中...",
@@ -562,7 +533,6 @@
"setting__update_show_change_log": "更新版本后的首次启动时显示更新日志",
"setting__update_try_auto_update": "发现新版本时尝试自动下载更新",
"setting__update_unknown": "未知",
"setting__update_unknown_tip": "❓ 获取最新版本信息失败,建议去关于界面打开项目发布地址查看当前版本是否最新",
"setting_sync_status_enabled": "已连接",
"song_list": "歌单",
"songlist__import_input_btn_confirm": "打开",
@@ -593,20 +563,14 @@
"source_xm": "虾米音乐",
"sync__auth_code_input_tip": "请输入连接码",
"sync__auth_code_title": "需要输入连接码",
"sync__dislike_merge_tip_desc": "合并两边列表内容并去重",
"sync__dislike_other_tip_desc": "“取消同步”将不使用不喜欢列表同步功能",
"sync__dislike_overwrite_tip_desc": "被覆盖者的列表将被替换成覆盖者的列表",
"sync__dislike_title": "选择与 {name} 的不喜欢列表同步方式",
"sync__list_merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
"sync__list_other_tip_desc": "“取消同步”将不使用列表同步功能。",
"sync__list_overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表列表ID不同的列表将被合并到一起若勾选完全覆盖则被覆盖者的所有列表将被移除然后替换成覆盖者的列表。",
"sync__list_title": "选择与 {name} 的列表同步方式",
"sync__merge_btn_local_remote": "本机列表 合并 远程列表",
"sync__merge_btn_remote_local": "远程列表 合并 本机列表",
"sync__merge_label": "合并",
"sync__merge_tip": "合并:",
"sync__merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip_desc": "“取消同步”将直接断开双方的连接。",
"sync__overwrite": "完全覆盖",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
@@ -614,6 +578,8 @@
"sync__overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
"sync__overwrite_label": "覆盖",
"sync__overwrite_tip": "覆盖:",
"sync__overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表列表ID不同的列表将被合并到一起若勾选完全覆盖则被覆盖者的所有列表将被移除然后替换成覆盖者的列表。",
"sync__title": "选择与 {name} 的列表同步方式",
"sync_status_disabled": "未连接",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
@@ -686,7 +652,6 @@
"user_api__btn_import": "导入",
"user_api__btn_remove": "移除",
"user_api__import_file": "选择音乐API脚本文件",
"user_api__init_failed_alert": "自定义源 [{name}] 初始化失败:",
"user_api__max_tip": "最多只能同时存在20个源哦🤪\n想要继续导入的话请先移除一些旧的源腾出位置吧",
"user_api__noitem": "这里竟然是空的 😲",
"user_api__note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",

View File

@@ -2,20 +2,19 @@
"action": "操作",
"agree": "接受",
"alert_button_text": "好吧",
"audio_visualization": "音訊視覺化(實驗性)",
"audio_visualization": "音頻可視化(實驗性)",
"back": "返回",
"btn_cancel": "取消",
"btn_confirm": "確定",
"btn_save": "存",
"btn_save": "存",
"cancel_button_text": "取消",
"cancel_button_text_2": "不不不,點錯了",
"close": "關閉",
"comment__hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載",
"comment__hot_loading": "熱門評論加載中",
"comment__hot_title": "熱門評論",
"comment__location": "來自{location}",
"comment__new_load_error": "最新評論加載失敗,點擊嘗試重新加載",
"comment__new_loading": "最新評論載中",
"comment__new_loading": "最新評論載中",
"comment__new_title": "最新評論",
"comment__no_content": "暫無評論",
"comment__refresh": "刷新評論",
@@ -27,23 +26,23 @@
"date_format_hour": "{num}小時前",
"date_format_minute": "{num}分鐘前",
"date_format_second": "{num}秒前",
"deep_link__handle_error_tip": "呼叫失敗:{message}",
"default": "預設",
"deep_link__handle_error_tip": "調用失敗:{message}",
"default": "默認",
"default_list": "試聽列表",
"desktop_lyric__back": "返回",
"desktop_lyric__close": "關閉",
"desktop_lyric__font_decrease": "減小字體大小",
"desktop_lyric__font_increase": "增加字體大小",
"desktop_lyric__lock": "鎖定歌詞",
"desktop_lyric__lrc_active_zoom_off": "取消縮放前播放的歌詞",
"desktop_lyric__lrc_active_zoom_on": "縮放前播放的歌詞",
"desktop_lyric__opactiy_decrease": "增加透明度(右可微調)",
"desktop_lyric__opactiy_increase": "減小透明度(右可微調)",
"desktop_lyric__lrc_active_zoom_off": "取消縮放前播放的歌詞",
"desktop_lyric__lrc_active_zoom_on": "縮放前播放的歌詞",
"desktop_lyric__opactiy_decrease": "增加透明度(右可微調)",
"desktop_lyric__opactiy_increase": "減小透明度(右可微調)",
"desktop_lyric__theme": "主題配色",
"desktop_lyric__unlock": "解鎖歌詞",
"desktop_lyric__win_top_off": "取消置頂歌詞面",
"desktop_lyric__win_top_on": "置頂歌詞面",
"download": "下載",
"desktop_lyric__win_top_off": "取消置頂歌詞面",
"desktop_lyric__win_top_on": "置頂歌詞面",
"download": "下載管理",
"download___status_complated": "下載完成",
"download___status_error": "任務出錯",
"download___status_paused": "暫停下載",
@@ -63,42 +62,40 @@
"download__quality": "品質",
"download__runing": "正在下載",
"download__status": "狀態",
"download_status_error_check_path": "檢查下載路徑出錯,請檢查設的下載目錄是否正常",
"download_status_error_check_path": "檢查下載路徑出錯,請檢查設的下載目錄是否正常",
"download_status_error_check_path_exist": "存在同名文件,跳過下載",
"download_status_error_refresh_url": "連結失效,正在刷新連結",
"download_status_error_refresh_url": "鏈接失效,正在刷新鏈接",
"download_status_error_response": "下載失敗:",
"download_status_error_url_failed": "取得音樂連結失敗",
"download_status_error_write": "歌曲存位置被佔用或沒有寫入權限,請嘗試更歌曲存目錄或重新啟動軟體或重新啟動電腦,錯誤詳情:",
"download_status_error_url_failed": "獲取音樂鏈接失敗",
"download_status_error_write": "歌曲存位置被佔用或沒有寫入權限,請嘗試更歌曲存目錄或重啟軟件或重啟電腦,錯誤詳情:",
"download_status_start": "開始下載",
"download_status_url_geting": "音樂連結獲取中...",
"download_status_write_queue": "資料寫入中({num}",
"duplicate_list_tip": "你之前已收藏過該清單 [{name}],是否需要更新裡面的歌曲?",
"export": "匯出",
"fullscreen_exit": "退出全螢幕",
"history_clear": "清空搜尋紀錄",
"history_remove": "右鍵移除該歷史",
"history_search": "歷史搜尋",
"download_status_url_geting": "音樂鏈接獲取中...",
"duplicate_list_tip": "你之前已收藏過該列表 [{name}],是否需要更新里面的歌曲?",
"export": "導出",
"fullscreen_exit": "退出全屏",
"history_clear": "清空搜索歷史",
"history_remove": "右擊移除該歷史",
"history_search": "歷史搜索",
"import": "導入",
"leaderboard": "排行榜",
"list__add_to": "加到...",
"list__add_to": "加到...",
"list__collect": "收藏",
"list__copy_name": "複製歌曲名",
"list__dislike": "不喜歡",
"list__download": "下載",
"list__export_part_desc": "選擇清單檔案儲存位置",
"list__export_part_desc": "選擇列表文件保存位置",
"list__file": "定位文件",
"list__import_part_button_cancel": "不要啊",
"list__import_part_button_confirm": "覆蓋掉",
"list__import_part_confirm": "導入的清單{importName})與本地列表({localName}的ID相同是否覆本地列表?",
"list__import_part_confirm": "導入的列表{importName})與本地列表({localName}的ID相同是否覆本地列表?",
"list__import_part_desc": "選擇列表文件",
"list__load_failed": "啊,載失敗了😭",
"list__loading": "清單加載中...⏳",
"list__load_failed": "啊,載失敗了😭",
"list__loading": "列表加載中...⏳",
"list__move_to": "移動到...",
"list__movedown": "下移",
"list__moveup": "上移",
"list__name_default": "試聽列表",
"list__name_default": "試聽清單",
"list__name_love": "我的收藏",
"list__new_list_btn": "新列表",
"list__new_list_btn": "新列表",
"list__new_list_input": "新列表...",
"list__pause": "暫停任務",
"list__play": "播放",
@@ -106,30 +103,28 @@
"list__remove": "刪除",
"list__remove_tip": "你真的想要移除 {name} 嗎?",
"list__remove_tip_button": "是的 沒錯",
"list__rename": "重命名",
"list__search": "搜",
"list__rename": "重命名",
"list__search": "搜",
"list__sort": "調整位置",
"list__source_detail": "歌曲詳情頁",
"list__start": "開始任務",
"list__sync": "更新",
"list_add__btn_title": "把該歌曲加到 {name}",
"list_add__multiple_btn_title": "把這些歌曲加到 {name}",
"list_add__multiple_title_add": "新增已選的 {num} 首歌曲到...",
"list_add__multiple_title_move": "移動已選的 {num} 首歌曲到...",
"list_add__btn_title": "把該歌曲加到 {name}",
"list_add__multiple_btn_title": "把這些歌曲加到 {name}",
"list_add__multiple_title_add": "添加已選的 {num} 首歌曲到...",
"list_add__multiple_title_move": "添加移動已選的 {num} 首歌曲到...",
"list_add__title_first_add": "添加",
"list_add__title_first_move": "移動",
"list_add__title_last": "到...",
"list_duplicate_tip": "已存在同名列表,是否仍要繼續建立?",
"list_import_tip__alldata": "導入失敗,這是一個所有資料備份文件,你需要去這裡導入:\n設 -> 備份與還原 -> 所有資料 -> 匯入",
"list_import_tip__playlist": "導入失敗,這是一個列表備份文件,你需要去這裡導入:\n設定 -> 備份與還原 -> 部分資料 -> 匯入列表",
"list_import_tip__playlist_part": "導入失敗,這是一個單列表文件,你需要去這裡導入:\n我的清單 -> 右鍵點選任何一個清單名稱 -> 在彈出的選單中選擇導入",
"list_import_tip__setting": "導入失敗,這是一個設定備份文件,你需要去這裡導入:\n設定 -> 備份與還原 -> 部分資料 -> 設定導入",
"list_import_tip__alldata": "導入失敗,這是一個所有數據備份文件,你需要去這裡導入:\n設置 -> 備份與恢復 -> 所有數據 -> 導入",
"list_import_tip__playlist": "導入失敗,這是一個列表備份文件,你需要去這裡導入:\n設 -> 備份與恢復 -> 部分數據 -> 導入列表",
"list_import_tip__playlist_part": "導入失敗,這是一個列表文件,你需要去這裡導入:\n我的列表 -> 右擊任意一個列表名 -> 在彈出的菜單中選擇導入",
"list_import_tip__setting": "導入失敗,這是一個設置備份文件,你需要去這裡導入:\n設置 -> 備份與恢復 -> 部分數據 -> 設置導入",
"list_import_tip__unknown": "導入失敗,未知的文件類型,請嘗試升級到最新版本後再試",
"list_sort_modal_by_album": "專輯名",
"list_sort_modal_by_down": "降序",
"list_sort_modal_by_field": "排序字段",
"list_sort_modal_by_name": "歌曲名",
"list_sort_modal_by_random": "隨機亂序",
"list_sort_modal_by_singer": "歌手名",
"list_sort_modal_by_source": "歌曲源",
"list_sort_modal_by_time": "時長",
@@ -137,49 +132,48 @@
"list_sort_modal_by_up": "升序",
"list_sort_modal_tip_confirm": "你確定要這麼做嗎?",
"list_update_modal__auto_update": "自動更新",
"list_update_modal__tips": "💡 每次啟動軟時將會自動更新已勾選自動更新的列表",
"list_update_modal__title": "清單更新管理",
"list_update_modal__tips": "💡 每次啟動軟時將會自動更新已勾選自動更新的列表",
"list_update_modal__title": "列表更新管理",
"list_update_modal__update": "立即更新",
"lists__add_local_file_desc": "選擇歌曲文件",
"lists__dislike_music_tip": "你真的不喜歡 {name} 嗎?",
"lists__duplicate": "重複歌曲",
"lists__export": "出",
"lists__export_part_desc": "選擇清單檔案儲存位置",
"lists__export": "出",
"lists__export_part_desc": "選擇列表文件保存位置",
"lists__import": "導入",
"lists__import_part_button_cancel": "不要啊",
"lists__import_part_button_confirm": "覆蓋掉",
"lists__import_part_confirm": "導入的清單{importName})與本地列表({localName}的ID相同是否覆本地列表?",
"lists__import_part_confirm": "導入的列表{importName})與本地列表({localName}的ID相同是否覆本地列表?",
"lists__import_part_desc": "選擇列表文件",
"lists__new_list_btn": "新列表",
"lists__new_list_btn": "新列表",
"lists__new_list_input": "新列表...",
"lists__remove": "刪除",
"lists__remove_music_tip": "你真的要移除所選的 {len} 首歌曲嗎?",
"lists__remove music_tip": "你真的要移除所選的 {len} 首歌曲嗎?",
"lists__remove_tip": "你真的想要移除 {name} 嗎?",
"lists__remove_tip_button": "是的 沒錯",
"lists__rename": "重命名",
"lists__select_local_file": "新增本地歌曲",
"lists__rename": "重命名",
"lists__select_local_file": "添加本地歌曲",
"lists__sort_list": "排序歌曲",
"lists__source_detail": "歌單詳情頁",
"lists__sync": "更新",
"lists__sync_confirm_tip": "這將會把 {name} 內的歌曲替換成線上清單的歌曲,你確認要更新嗎?",
"load_list_file_error_detail": "我們已經幫你把舊的清單檔案備份到{path}\n它以 JSON 格式存儲,你可以嘗試手動修復並恢復它\n\n錯誤詳情{detail}",
"load_list_file_error_title": "播放清單資料載入錯誤建議到GitHub或加群回饋",
"loding": "載中...",
"love_list": "收藏",
"lists__sync_confirm_tip": "這將會把 {name} 內的歌曲替換成在線列表的歌曲,你確認要更新嗎?",
"load_list_file_error_detail": "我們已經幫你把舊的列表文件備份到{path}\n它以 JSON 格式存儲,你可以嘗試手動修復並恢復它\n\n錯誤詳情{detail}",
"load_list_file_error_title": "播放列表數據加載錯誤",
"loding": "載中...",
"love_list": "收藏列表",
"lyric__load_error": "歌詞獲取失敗",
"lyric__select": "歌詞文本選擇",
"lyric_menu__align": "歌詞對齊方式",
"lyric_menu__align_center": "居中",
"lyric_menu__align_left": "居左",
"lyric_menu__lrc_size": "字體大小 [ {size} ]",
"lyric_menu__lrc_size": "字體大小 [ {size} ]",
"lyric_menu__offset": "歌詞偏移 [ {offset}ms ]",
"lyric_menu__offset_add_10": "加快10毫秒",
"lyric_menu__offset_add_10": "加快10毫秒右擊加快5毫秒",
"lyric_menu__offset_add_100": "加快100毫秒",
"lyric_menu__offset_dec_10": "減慢10毫秒",
"lyric_menu__offset_dec_100": "減慢100毫秒",
"lyric_menu__offset_reset": "重置",
"lyric_menu__size_add": "加大字體(右可微調)",
"lyric_menu__size_dec": "減小字體(右可微調)",
"lyric_menu__offset_reset": "重置偏移",
"lyric_menu__size_add": "加大字體(右可微調)",
"lyric_menu__size_dec": "減小字體(右可微調)",
"lyric_menu__size_reset": "重置",
"min": "最小化",
"music_album": "專輯名",
@@ -188,7 +182,7 @@
"music_singer": "藝術家",
"music_sort__input_tip": "請輸入要調整到第幾個位置",
"music_sort__title": "將 {name} 的位置調整到:",
"music_sort__title_multiple": "將已選的 {num} 首歌曲的位置調整",
"music_sort__title_multiple": "將已選的 {num} 首歌曲的位置調整",
"music_time": "時長",
"my_list": "我的列表",
"no_item": "列表竟然是空的...",
@@ -205,37 +199,36 @@
"play_timeout_tip": "{time} 後暫停播放",
"play_timeout_unit": "分鐘",
"play_timeout_update": "更新定時",
"player__add_music_to": "新增目前歌曲到...",
"player__add_music_to": "添加當前歌曲到...",
"player__album": "專輯名:",
"player__buffering": "緩衝中...",
"player__desktop_lyric_lock": "右鎖定歌詞",
"player__desktop_lyric_lock": "右鎖定歌詞",
"player__desktop_lyric_off": "關閉桌面歌詞",
"player__desktop_lyric_on": "開啟桌面歌詞",
"player__desktop_lyric_unlock": "右解鎖歌詞",
"player__desktop_lyric_unlock": "右解鎖歌詞",
"player__end": "播放完畢",
"player__error": "音訊載入出錯5 秒後切換下一首",
"player__geting_url": "歌曲連結獲取中...",
"player__error": "音頻加載出錯5 秒後切換下一首",
"player__geting_url": "歌曲鏈接獲取中...",
"player__geting_url_delay_retry": "服務繁忙,{time}秒後重試...",
"player__hide_detail_tip": "隱藏詳情頁(面內右鍵雙擊可快速隱藏詳情頁)",
"player__hide_detail_tip": "隱藏詳情頁(面內右鍵雙擊可快速隱藏詳情頁)",
"player__loading": "音樂加載中...",
"player__music_album": "專輯名稱:",
"player__music_name": "歌曲名:",
"player__music_singer": "藝術家:",
"player__next": "下一首",
"player__pause": "暫停",
"player__pic_tip": "播放詳情頁(右鍵在「我的清單」上定位前播放的歌曲)",
"player__pic_tip": "播放詳情頁(右擊在“我的列表”定位前播放的歌曲)",
"player__play": "播放",
"player__play_toggle_mode_list": "順序播放",
"player__play_toggle_mode_list_loop": "清單循環",
"player__play_toggle_mode_off": "用",
"player__play_toggle_mode_random": "清單隨機",
"player__play_toggle_mode_list_loop": "列表循環",
"player__play_toggle_mode_off": "用",
"player__play_toggle_mode_random": "列表隨機",
"player__play_toggle_mode_single_loop": "單曲循環",
"player__playback_preserves_pitch": "音調補償",
"player__playback_rate": "目前播放速率:",
"player__playback_rate": "當前播放速率:",
"player__playback_rate_reset_btn": "重置",
"player__playing": "播放中...",
"player__prev": "上一首",
"player__refresh_url": "URL過期正在刷新URL...",
"player__sound_effect": "音效設(實驗性)",
"player__sound_effect": "音效設(實驗性)",
"player__sound_effect_biquad_filter": "均衡器",
"player__sound_effect_biquad_filter_preset_classical": "古典",
"player__sound_effect_biquad_filter_preset_dance": "舞曲",
@@ -251,61 +244,58 @@
"player__sound_effect_biquad_filter_save_input": "新預設...",
"player__sound_effect_convolution": "環境混響音效",
"player__sound_effect_convolution_file_bright_hall": "大廳",
"player__sound_effect_convolution_file_cardiod_35_10_spread": "心形擴散",
"player__sound_effect_convolution_file_cardiod_35_10_spread": "搖滾",
"player__sound_effect_convolution_file_cinema_diningroom": "電影院",
"player__sound_effect_convolution_file_dining_living_true_stereo": "餐廳",
"player__sound_effect_convolution_file_feedback_spring": "反饋彈簧",
"player__sound_effect_convolution_file_living_bedroom_leveled": "廁所",
"player__sound_effect_convolution_file_matrix_1": "矩陣混響",
"player__sound_effect_convolution_file_matrix_2": "矩陣混響2",
"player__sound_effect_convolution_file_living_bedroom_leveled": "衛生間",
"player__sound_effect_convolution_file_matrix_1": "矩陣",
"player__sound_effect_convolution_file_matrix_2": "矩陣2",
"player__sound_effect_convolution_file_s2_r4_bd": "教堂",
"player__sound_effect_convolution_file_s3_r1_bd": "立體聲",
"player__sound_effect_convolution_file_spreader25_125ms": "室內2",
"player__sound_effect_convolution_file_spreader50_65ms": "室內",
"player__sound_effect_convolution_file_telephone": "電話",
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "磁性立體聲",
"player__sound_effect_convolution_main_gain": "原始音增益",
"player__sound_effect_convolution_file_tim_omni_35_10_magnetic": "搖滾2",
"player__sound_effect_convolution_main_gain": "原始音增益",
"player__sound_effect_convolution_send_gain": "環境音效增益",
"player__sound_effect_panner": "3D立體環繞需使用耳機",
"player__sound_effect_panner_enabled": "啟用",
"player__sound_effect_panner_sound_r": "聲音距離",
"player__sound_effect_panner_sound_speed": "環繞速度",
"player__sound_effect_pitch_shifter": "音調升降調節",
"player__sound_effect_pitch_shifter_preset_semitones": "{num} 半音",
"player__sound_effect_pitch_shifter_reset_btn": "重置",
"player__sound_effect_pitch_shifter_tip": "由於升降調需要即時處理音訊數據這會導致額外的CPU佔用\n\n已知問題\n如果CPU資源不夠時將處理導致任務堆積而出現聲音異常這時需要暫停播放一段時間等堆積的任務處理完畢再播放",
"player__stop": "暫停播放",
"player__volume": "前音量:",
"player__volume": "前音量:",
"player__volume_mute_label": "靜音",
"player__volume_muted": "已靜音",
"search": "搜",
"search__hot_search": "熱門搜",
"search": "搜",
"search__hot_search": "熱門搜",
"search__type_music": "歌曲",
"search__type_songlist": "歌單",
"search__welcome": "搜我所想~~😉",
"setting": "設",
"setting": "設",
"setting__about": "關於洛雪音樂",
"setting__backup": "備份與復",
"setting__backup_all": "所有數據(列表數據與設數據)",
"setting__backup_all_export": "出",
"setting__backup": "備份與復",
"setting__backup_all": "所有數據(列表數據與設數據)",
"setting__backup_all_export": "出",
"setting__backup_all_export_desc": "選擇備份保存位置",
"setting__backup_all_import": "導入",
"setting__backup_all_import_desc": "選擇備份文件",
"setting__backup_other": "其他備份格式(目前不支援還原此類備份檔案",
"setting__backup_other_export_dir": "選擇檔案儲存位置",
"setting__backup_other_export_list_csv": "出 CSV 格式的列表",
"setting__backup_other_export_list_text": "出 TXT 格式的列表",
"setting__backup_other_export_list_text_confirm": "是否將所有清單合併為一個檔案",
"setting__backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自列表,設數據不包括快捷鍵設",
"setting__backup_other": "其他備份格式(目前不支持恢復此類備份文件",
"setting__backup_other_export_dir": "選擇文件保存位置",
"setting__backup_other_export_list_csv": "出 CSV 格式的列表",
"setting__backup_other_export_list_text": "出 TXT 格式的列表",
"setting__backup_other_export_list_text_confirm": "是否將所有列表合併為一個文件",
"setting__backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自定義列表,設數據不包括快捷鍵設",
"setting__backup_part_export_list": "導出列表",
"setting__backup_part_export_list_desc": "選擇歌單存位置",
"setting__backup_part_export_setting": "出設",
"setting__backup_part_export_setting_desc": "選擇設定儲存位置",
"setting__backup_part_export_list_desc": "選擇歌單存位置",
"setting__backup_part_export_setting": "出設",
"setting__backup_part_export_setting_desc": "選擇設置保存位置",
"setting__backup_part_import_list": "導入列表",
"setting__backup_part_import_list_confirm": "備份檔案中清單與現有清單ID相同時現有清單內歌曲將會被覆蓋,是否要繼續?",
"setting__backup_part_import_list_confirm": "備份文件中列表與現有列表ID相同時現有列表內歌曲將會被覆蓋,是否要繼續?",
"setting__backup_part_import_list_desc": "選擇列表文件",
"setting__backup_part_import_setting": "導入設",
"setting__backup_part_import_setting_desc": "選擇設定檔",
"setting__basic": "基本設",
"setting__backup_part_import_setting": "導入設",
"setting__backup_part_import_setting_desc": "選擇配置文件",
"setting__basic": "基本設",
"setting__basic_animation": "彈出層隨機動畫",
"setting__basic_control_btn_position": "控制按鈕位置",
"setting__basic_control_btn_position_left": "左邊",
@@ -313,13 +303,13 @@
"setting__basic_font": "字體",
"setting__basic_font_size": "字體大小",
"setting__basic_font_size_14px": "較小",
"setting__basic_font_size_15px": "小",
"setting__basic_font_size_15px": "小",
"setting__basic_font_size_16px": "標準",
"setting__basic_font_size_17px": "大",
"setting__basic_font_size_18px": "較大",
"setting__basic_font_size_19px": "非常大",
"setting__basic_lang": "語言",
"setting__basic_lang_title": "軟顯示的語言",
"setting__basic_lang_title": "軟顯示的語言",
"setting__basic_playbar_progress_style": "播放欄進度條樣式",
"setting__basic_playbar_progress_style_full": "全寬",
"setting__basic_playbar_progress_style_middle": "中等",
@@ -329,17 +319,17 @@
"setting__basic_source_status_failed": "初始化失敗",
"setting__basic_source_status_initing": "初始化中",
"setting__basic_source_status_success": "初始化成功",
"setting__basic_source_temp": "臨時介面(軟的某些功能不可用,建議測試介面不可用再使用本介面",
"setting__basic_source_test": "測試介面(幾乎軟的所有功能都可用)",
"setting__basic_source_user_api_btn": "自訂來源管理",
"setting__basic_source_temp": "臨時接口(軟的某些功能不可用,建議測試接口不可用再使用本接口",
"setting__basic_source_test": "測試接口(幾乎軟的所有功能都可用)",
"setting__basic_source_user_api_btn": "自定義源管理",
"setting__basic_sourcename": "音源名字",
"setting__basic_sourcename_alias": "別名",
"setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "選擇音源名字類型",
"setting__basic_start_in_fullscreen": "以全螢幕模式啟動",
"setting__basic_start_in_fullscreen": "以全模式啟動",
"setting__basic_theme": "主題顏色",
"setting__basic_theme_auto_tip": "此乃動態主題,你可以預先設一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設的相應主題。\n注意:滑鼠 此主題項即可開亮、暗色主題設定視窗。",
"setting__basic_to_tray": "關閉軟時不退出軟將其最小化到系統托盤",
"setting__basic_theme_auto_tip": "此乃動態主題,你可以預先設一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設的相應主題。\n注:鼠標 此主題項即可開亮、暗色主題設置窗口。",
"setting__basic_to_tray": "關閉軟時不退出軟將其最小化到系統托盤",
"setting__basic_window_size": "窗口尺寸",
"setting__basic_window_size_big": "大",
"setting__basic_window_size_huge": "巨大",
@@ -348,58 +338,54 @@
"setting__basic_window_size_oversized": "超大",
"setting__basic_window_size_small": "小",
"setting__basic_window_size_smaller": "較小",
"setting__basic_window_size_title": "設定軟體視窗尺寸",
"setting__click_copy": "點複製",
"setting__basic_window_size_title": "設置軟件窗口尺寸",
"setting__click_copy": "點複製",
"setting__click_open": "點擊打開",
"setting__desktop_lyric": "桌面歌詞設",
"setting__desktop_lyric": "桌面歌詞設",
"setting__desktop_lyric_align": "歌詞對齊方式",
"setting__desktop_lyric_align_center": "居中",
"setting__desktop_lyric_align_left": "居左",
"setting__desktop_lyric_align_right": "居右",
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗之上",
"setting__desktop_lyric_always_on_top_loop": "自動刷新歌詞置頂(歌詞置頂後仍被某些程遮擋時可嘗試啟用此設",
"setting__desktop_lyric_audio_visualization": "音訊視覺化(實驗性)",
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗之上",
"setting__desktop_lyric_always_on_top_loop": "自動刷新歌詞置頂(歌詞置頂後仍被某些程遮擋時可嘗試啟用此設",
"setting__desktop_lyric_audio_visualization": "音頻可視化(實驗性)",
"setting__desktop_lyric_color": "歌詞字體顏色",
"setting__desktop_lyric_color_reset": "重置顏色",
"setting__desktop_lyric_delay_scroll": "延遲歌詞滾動",
"setting__desktop_lyric_direction": "歌詞顯示方向",
"setting__desktop_lyric_direction_horizontal": "水平方向",
"setting__desktop_lyric_direction_vertical": "垂直方向",
"setting__desktop_lyric_ellipsis": "不允許歌詞換行",
"setting__desktop_lyric_ellipsis": "不允許桌面歌詞換行",
"setting__desktop_lyric_enable": "顯示歌詞",
"setting__desktop_lyric_font": "歌詞字體",
"setting__desktop_lyric_font_default": "預設",
"setting__desktop_lyric_font_default": "默認",
"setting__desktop_lyric_font_weight": "加粗字體",
"setting__desktop_lyric_fullscreen_hide": "全螢幕時自動關閉歌詞",
"setting__desktop_lyric_hover_hide": "鼠移入歌詞區域時降低歌詞透明度(此功能平台容性問題)",
"setting__desktop_lyric_fullscreen_hide": "全時自動關閉歌詞",
"setting__desktop_lyric_hover_hide": "鼠移入歌詞區域時降低歌詞透明度(此功能存在平台容性問題)",
"setting__desktop_lyric_line_gap": "歌詞間距({num}",
"setting__desktop_lyric_line_gap_add": "加大間距",
"setting__desktop_lyric_line_gap_dec": "減小間距",
"setting__desktop_lyric_lock": "鎖定歌詞",
"setting__desktop_lyric_lock_screen": "不允許歌詞窗拖出主畫面之外",
"setting__desktop_lyric_lock_screen": "不允許歌詞窗拖出主屏幕之外",
"setting__desktop_lyric_played_color": "已播放顏色",
"setting__desktop_lyric_reset": "重置",
"setting__desktop_lyric_reset_window": "重置視窗設定",
"setting__desktop_lyric_reset_window": "重置窗口設置",
"setting__desktop_lyric_scroll_align": "正在播放歌詞滾動位置",
"setting__desktop_lyric_scroll_align_center": "中心",
"setting__desktop_lyric_scroll_align_top": "頂部",
"setting__desktop_lyric_shadow_color": "陰影顏色",
"setting__desktop_lyric_show_taskbar": "在工作列顯示歌詞進程(此設用於在錄影軟體無法擷取歌詞視窗時的變通解決方法)",
"setting__desktop_lyric_show_taskbar": "在任務欄顯示歌詞進程(此設用於在錄屏軟件無法捕獲歌詞窗口時的變通解決方法)",
"setting__desktop_lyric_unplay_color": "未播放顏色",
"setting__dislike_list_input_tip": "歌曲名@歌手名\n歌曲名\n@歌手名",
"setting__dislike_list_save_btn": "儲存",
"setting__dislike_list_tips": "1. 每條一行,若歌曲或歌手名字中存在“@”符號,則需要將其替換成“",
"setting__dislike_list_title": "不喜歡的歌曲規則列表",
"setting__download": "下載設定",
"setting__download_data_embed": "是否將以下內容嵌入到音訊檔案中",
"setting__download": "下載設置",
"setting__download_data_embed": "是否將以下內容嵌入到音頻文件中",
"setting__download_embed_lyric": "歌詞嵌入",
"setting__download_embed_pic": "封面嵌入",
"setting__download_embed_rlyric": "同時嵌入羅馬音歌詞(如果有)",
"setting__download_embed_tlyric": "同時嵌入翻譯歌詞(如果有)",
"setting__download_enable": "是否啟用下載功能",
"setting__download_lyric": "歌詞下載",
"setting__download_lyric_format": "下載的歌詞檔案編碼格式",
"setting__download_lyric_format_gbk": "GBK在某些裝置上出現中文亂碼時可嘗試選擇此格式)",
"setting__download_lyric_format": "下載的歌詞文件編碼格式",
"setting__download_lyric_format_gbk": "GBK在某些設備上出現中文亂碼時可嘗試選擇此格式)",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "是否同時下載歌詞文件",
"setting__download_name": "文件命名方式",
@@ -408,172 +394,156 @@
"setting__download_name3": "歌名",
"setting__download_name_title": "下載歌曲時的命名方式",
"setting__download_path": "下載路徑",
"setting__download_path_change_btn": "更",
"setting__download_path_label": "前下載路徑:",
"setting__download_path_open_label": "點擊開啟目前路徑",
"setting__download_path_title": "下載歌曲已儲存的路徑",
"setting__download_rlyric": "同時將羅馬音歌詞寫入歌詞中(如果有)",
"setting__download_select_save_path": "選擇歌曲存路徑",
"setting__download_skip_exist_file": "下載目錄存在同名的檔案時跳過下載此任務",
"setting__download_tlyric": "同時將翻譯歌詞寫入歌詞檔案中(如果有)",
"setting__download_path_change_btn": "更",
"setting__download_path_label": "前下載路徑:",
"setting__download_path_open_label": "點擊打開當前路徑",
"setting__download_path_title": "下載歌曲存的路徑",
"setting__download_rlyric": "同時將羅馬音歌詞寫入歌詞文件中(如果有)",
"setting__download_select_save_path": "選擇歌曲存路徑",
"setting__download_skip_exist_file": "下載目錄存在同名的文件時跳過下載此任務",
"setting__download_tlyric": "同時將翻譯歌詞寫入歌詞文件中(如果有)",
"setting__download_use_other_source": "自動換源下載",
"setting__download_use_other_source_tip": "當無法從歌曲的原始源下載時,嘗試切換到其他源下載,附註此功能不100%保證換源後的歌曲版本與原版一致",
"setting__hot_key": "快鍵設",
"setting__hot_key_common_focus_search_input": "聚焦搜框",
"setting__hot_key_common_min": "最小化程",
"setting__download_use_other_source_tip": "當無法從歌曲的原始源下載時,嘗試切換到其他源下載,此功能不100%保證換源後的歌曲版本與原版一致",
"setting__hot_key": "快鍵設",
"setting__hot_key_common_focus_search_input": "聚焦搜框",
"setting__hot_key_common_min": "最小化程",
"setting__hot_key_common_toggle_close": "退出程序",
"setting__hot_key_common_toggle_hide": "顯示/隱藏程",
"setting__hot_key_common_toggle_hide": "顯示/隱藏程",
"setting__hot_key_common_toggle_min": "最小化/還原程序",
"setting__hot_key_desktop_lyric_toggle_always_top": "桌面歌詞置頂切換",
"setting__hot_key_desktop_lyric_toggle_lock": "桌面歌詞鎖定切換",
"setting__hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
"setting__hot_key_global_title": "全域快速鍵",
"setting__hot_key_local_title": "軟內快鍵",
"setting__hot_key_player_music_dislike": "不喜歡該歌曲",
"setting__hot_key_player_music_love": "收藏歌曲",
"setting__hot_key_player_music_unlove": "取消收藏",
"setting__hot_key_global_title": "全局快捷鍵",
"setting__hot_key_local_title": "軟內快鍵",
"setting__hot_key_player_next": "下一首歌曲",
"setting__hot_key_player_prev": "上一首歌曲",
"setting__hot_key_player_toggle_play": "播放/暫停控制",
"setting__hot_key_player_toggle_play": "播放/暫停控制",
"setting__hot_key_player_volume_down": "減少音量",
"setting__hot_key_player_volume_mute": "靜音切換",
"setting__hot_key_player_volume_up": "增加音量",
"setting__hot_key_tip_input": "請輸入新的按鍵",
"setting__hot_key_unset_input": "未設",
"setting__hot_key_unset_input": "未設",
"setting__is_enable": "是否啟用",
"setting__is_show": "是否顯示",
"setting__list": "清單設定",
"setting__list_action_btn": "顯示清單操作按鈕",
"setting__list_add_music_location_type": "新增歌曲到清單時的位置",
"setting__list": "列表設置",
"setting__list_action_btn": "顯示列表操作按鈕",
"setting__list_add_music_location_type": "添加歌曲到列表時的位置",
"setting__list_add_music_location_type_bottom": "底部",
"setting__list_add_music_location_type_top": "頂部",
"setting__list_click_action": "雙擊清單裡的歌曲時自動切換到目前清單播放(僅對歌單、排行榜有效)",
"setting__list_scroll": "記住播放清單滾動條位置(僅對我的音樂分類有效)",
"setting__list_source": "顯示歌曲源(僅對我的音樂分類有效)",
"setting__network": "網路設定",
"setting__list_click_action": "雙擊列表裡的歌曲時自動切換到當前列表播放(僅對歌單、排行榜有效)",
"setting__list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)",
"setting__list_source": "顯示歌曲源(僅對我的音樂分類有效)",
"setting__network": "網絡設置",
"setting__network_proxy_host": "主機",
"setting__network_proxy_password": "密碼",
"setting__network_proxy_port": "連接埠",
"setting__network_proxy_title": "HTTP代理設(亂設定軟體將無法網)",
"setting__network_proxy_username": "使用者名稱",
"setting__odc": "強迫症設",
"setting__odc_clear_search_input": "離開搜尋介面時清空搜框",
"setting__odc_clear_search_list": "離開搜尋介面時清空搜列表",
"setting__network_proxy_port": "端口",
"setting__network_proxy_title": "HTTP代理設(亂設置軟件將無法網)",
"setting__network_proxy_username": "用戶名",
"setting__odc": "強迫症設",
"setting__odc_clear_search_input": "離開搜索界面時清空搜框",
"setting__odc_clear_search_list": "離開搜索界面時清空搜列表",
"setting__other": "其他",
"setting__other_dislike_list": "不喜歡的歌曲規則",
"setting__other_dislike_list_label": "規則數量:",
"setting__other_dislike_list_show_btn": "編輯不喜歡歌曲規則",
"setting__other_listdata": "列表資料清理",
"setting__other_listdata_clear_btn": "清空我的清單數據",
"setting__other_listdata_clear_tip_confirm": "這將清理你創建的 所有清單 及收藏的 所有歌曲,是否真的要繼續?",
"setting__other_listdata": "列表數據清理",
"setting__other_listdata_clear_btn": "清空我的列表數據",
"setting__other_listdata_clear_tip_confirm": "這將清理你創建的 所有列表 及收藏的 所有歌曲,是否真的要繼續?",
"setting__other_lyric_edited_cache": "已調整過偏移時間的歌詞管理",
"setting__other_lyric_edited_clear_btn": "清理已調整過時間的歌詞",
"setting__other_lyric_edited_clear_tip_confirm": "這將清理所有你之前已調整過偏移時間的歌詞,是否確認清理? \n手抖確認🤪",
"setting__other_lyric_edited_label": "歌詞數量:",
"setting__other_lyric_raw_clear_btn": "清理歌詞快取",
"setting__other_lyric_raw_clear_btn": "清理歌詞緩存",
"setting__other_lyric_raw_label": "歌詞數量:",
"setting__other_music_url_clear_btn": "清理歌曲URL緩存",
"setting__other_music_url_label": "歌曲URL數量",
"setting__other_other_cache": "其他快取管理",
"setting__other_other_cache": "其他緩存管理",
"setting__other_other_source_clear_btn": "清理換源歌曲緩存",
"setting__other_other_source_label": "換源歌曲資訊數",
"setting__other_resource_cache": "資源快取管理",
"setting__other_resource_cache_clear_btn": "清理資源快取",
"setting__other_other_source_label": "換源歌曲信息數量",
"setting__other_resource_cache": "資源緩存管理",
"setting__other_resource_cache_clear_btn": "清理資源緩存",
"setting__other_resource_cache_confirm": "我要清掉",
"setting__other_resource_cache_label": "軟已使用快取大小:",
"setting__other_resource_cache_tip": "圖片、音等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟會根據磁空間動態管理快取大小",
"setting__other_resource_cache_tip_confirm": "涉及圖片、音等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟會根據磁空間動態管理快取大小,是否仍要清理?",
"setting__other_tray_theme": "托盤圖樣式",
"setting__other_resource_cache_label": "軟已使用緩存大小:",
"setting__other_resource_cache_tip": "圖片、音等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟會根據磁空間動態管理緩存大小",
"setting__other_resource_cache_tip_confirm": "涉及圖片、音等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟會根據磁空間動態管理緩存大小,是否仍要清理?",
"setting__other_tray_theme": "托盤圖樣式",
"setting__other_tray_theme_black": "黑色",
"setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色",
"setting__play": "播放設",
"setting__play_auto_clean_played_list": "點選與播放清單相同的清單切歌時是否已清空已播放清單(隨機模式下清單內所有歌曲會重新參與隨機)",
"setting__play": "播放設",
"setting__play_auto_skip_on_error": "播放錯誤時自動切換歌曲",
"setting__play_detail": "播放詳情頁設",
"setting__play_detail": "播放詳情頁設",
"setting__play_detail_align": "歌詞對齊方式",
"setting__play_detail_align_center": "居中",
"setting__play_detail_align_left": "居左",
"setting__play_detail_align_right": "居右",
"setting__play_detail_font_size": "歌詞字體大小(可在播放詳情頁使用鍵盤的 - 調整字體大小)",
"setting__play_detail_font_size_current": "前字體大小:{size}",
"setting__play_detail_font_size": "歌詞字體大小(可在播放詳情頁使用鍵盤的 + - 調整字體大小)",
"setting__play_detail_font_size_current": "前字體大小:{size}",
"setting__play_detail_font_size_reset": "重置",
"setting__play_detail_font_zoom": "縮放前正在播放的歌詞",
"setting__play_detail_lyric_progress": "允許過歌詞調整播放進度",
"setting__play_detail_font_zoom": "縮放前正在播放的歌詞",
"setting__play_detail_lyric_progress": "允許過歌詞調整播放進度",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放如果可用此功能比較耗性能低配置電腦不建議開啟",
"setting__play_lyric_roma": "顯示歌詞羅馬音(如果可用)",
"setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文",
"setting__play_lyric_transition": "顯示歌詞翻譯(如果可用)",
"setting__play_mediaDevice": "音輸出",
"setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出裝置被改變時暫停播放歌曲",
"setting__play_mediaDevice": "音輸出",
"setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
"setting__play_mediaDevice_title": "選擇聲音輸出的媒體設備",
"setting__play_media_device_error_tip": "此功能與進階音訊功能(音訊視覺化、音效設)衝突,你本次啟動軟時已啟用這些功能,此設暫不可用,請 關閉這些功能 並 重新啟動後,再來修改此設。",
"setting__play_media_device_tip": "此功能與音訊視覺化功能衝突,兩者無法同時啟用,是否將音訊視覺化關閉 並 應用所選音輸出設",
"setting__play_power_save_blocker": "播放歌曲時阻止電腦休眠",
"setting__play_media_device_error_tip": "此功能與高級音頻功能(音頻可視化、音效設)衝突,你本次啟動軟時已啟用這些功能,此設暫不可用,請 關閉這些功能 並 重後,再來修改此設。",
"setting__play_media_device_tip": "此功能與音頻可視化功能衝突,兩者無法同時啟用,是否將音頻可視化關閉 並 應用所選音輸出設",
"setting__play_quality": "優先播放320K品質的歌曲如果可用",
"setting__play_save_play_time": "記住播放進度",
"setting__play_startup_auto_play": "啟動軟後自動播放音樂",
"setting__play_task_bar": "在工作列上顯示前歌曲播放進度",
"setting__play_startup_auto_play": "啟動軟後自動播放音樂",
"setting__play_task_bar": "在任務欄上顯示前歌曲播放進度",
"setting__play_timeout": "定時暫停",
"setting__player_audio_visualization_tip": "自訂音訊輸出設備與音訊視覺化功能會衝突,啟用了音訊視覺化後音輸出設備將會被重設為預設的輸出設備,目前此問題暫無法解決,是否仍要開啟?",
"setting__search": "搜尋設定",
"setting__search_focus_search_box": "啟動時自動聚焦搜框",
"setting__search_history": "顯示歷史搜記錄",
"setting__search_hot": "顯示熱門搜",
"setting__player_audio_visualization_tip": "自定義音頻輸出設備與音頻可視化功能會衝突,啟用了音頻可視化後音輸出設備將會被重置為默認的輸出設備,目前此問題暫無法解決,是否仍要開啟?",
"setting__search": "搜索設置",
"setting__search_focus_search_box": "啟動時自動聚焦搜框",
"setting__search_history": "顯示歷史搜記錄",
"setting__search_hot": "顯示熱門搜",
"setting__setting__desktop_lyric_font_weight_extended": "翻譯、羅馬音歌詞",
"setting__setting__desktop_lyric_font_weight_font": "逐字歌詞",
"setting__setting__desktop_lyric_font_weight_line": "逐行歌詞",
"setting__sync": "資料同步",
"setting__sync_client_address": "前設備址:{address}",
"setting__sync": "數據同步",
"setting__sync_client_address": "前設備址:{address}",
"setting__sync_client_host": "同步服務地址",
"setting__sync_client_host_tip": "http://IP址:連接埠號",
"setting__sync_client_host_tip": "http://IP址:端口號",
"setting__sync_client_mode": "客戶端模式",
"setting__sync_client_status": "狀態:{status}",
"setting__sync_code_blocked_ip": "前設備的IP已被服務端封",
"setting__sync_code_blocked_ip": "前設備的IP已被服務端封",
"setting__sync_code_fail": "連接碼無效",
"setting__sync_enable": "啟用同步功能",
"setting__sync_mode": "同步模式",
"setting__sync_mode_client": "客戶端模式",
"setting__sync_mode_server": "服務端模式",
"setting__sync_server_address": "同步服務址:{address}",
"setting__sync_server_address": "同步服務址:{address}",
"setting__sync_server_auth_code": "連接碼:{code}",
"setting__sync_server_device": "已連接的裝置{devices}",
"setting__sync_server_device_list_btn_remove": "移除",
"setting__sync_server_device_list_noitem": "這裡啥也沒有 ┗( ▔, ▔ )┛",
"setting__sync_server_device_list_time": "最後連結時間:{time}",
"setting__sync_server_device_list_tips": "💡 裝置移除後,再連線時需重新輸入連接碼",
"setting__sync_server_device_list_title": "已認證設備",
"setting__sync_server_mode": "服務端模式(由於資料是明文傳輸,請在受信任的網路下使用)",
"setting__sync_server_port": "同步連接埠設定",
"setting__sync_server_port_tip": "請輸入同步服務連接埠號",
"setting__sync_server_device": "已連接的設備{devices}",
"setting__sync_server_mode": "服務端模式(由於數據是明文傳輸,請在受信任的網絡下使用)",
"setting__sync_server_port": "同步端口設置",
"setting__sync_server_port_tip": "請輸入同步服務端口號",
"setting__sync_server_refresh_code": "刷新連接碼",
"setting__sync_server_show_device_list": "已認證設備列表",
"setting__sync_tip": "使用方式請看常見問題「同步功能」部分",
"setting__update": "軟體更新",
"setting__sync_tip": "使用方式請看常見問題“同步功能”部分",
"setting__update": "軟件更新",
"setting__update_checking": "檢查更新中...",
"setting__update_current_label": "前版本:",
"setting__update_current_label": "前版本:",
"setting__update_downloading": "發現新版本並在努力下載中,請稍後...⏳",
"setting__update_init": "處理更新中...",
"setting__update_latest": "軟已是最新,盡情體驗吧~🥂",
"setting__update_latest": "軟已是最新,盡情體驗吧~🥂",
"setting__update_latest_label": "最新版本:",
"setting__update_new_version": "發現新版本,趕快去更新吧~🚀🚀",
"setting__update_open_version_modal_btn": "開啟更新視窗",
"setting__update_open_version_modal_btn": "打開更新窗口",
"setting__update_progress": "狀態:",
"setting__update_show_change_log": "更新版本後的首次啟動時顯示更新日誌",
"setting__update_try_auto_update": "發現新版本時嘗試自動下載更新",
"setting__update_unknown": "未知",
"setting__update_unknown_tip": "❓ 取得最新版本資訊失敗,建議去關於介面開啟專案發佈位址查看目前版本是否最新",
"setting_sync_status_enabled": "已連接",
"song_list": "歌單",
"songlist__import_input_btn_confirm": "打開",
"songlist__import_input_show_btn": "開啟歌單",
"songlist__import_input_tip": "輸入歌單連結或歌單ID",
"songlist__import_input_tip_1": "不支援跨來源開啟歌單,請確認要開啟的歌單與目前歌單來源是否對應",
"songlist__import_input_tip_2": "若遇到無法打開的歌單鏈接,歡迎回饋",
"songlist__import_input_tip_3": "酷狗來源不支援用歌單ID打開但支援酷狗碼打開",
"songlist__import_input_tip_4": "網易源的「我喜歡」歌單需要 Token 才能打開,詳情看",
"songlist__import_input_show_btn": "打開歌單",
"songlist__import_input_tip": "輸入歌單鏈接或歌單ID",
"songlist__import_input_tip_1": "不支持跨源打開歌單,請確認要打開的歌單與當前歌單源是否對應",
"songlist__import_input_tip_2": "若遇到無法打開的歌單鏈接,歡迎反饋",
"songlist__import_input_tip_3": "酷狗源不支持用歌單ID打開但支持酷狗碼打開",
"songlist__import_input_tip_4": "網易源的“我喜歡”歌單需要 Token 才能打開,詳情看",
"songlist__import_input_title": "打開分享的歌單",
"songlist__open_list": "開{name}歌單",
"songlist__open_list": "開{name}歌單",
"songlist__tag_info_hot_tag": "熱門標籤",
"source_alias_all": "聚合大會",
"source_alias_bd": "小杜音樂",
@@ -583,7 +553,7 @@
"source_alias_tx": "小秋音樂",
"source_alias_wy": "小芸音樂",
"source_alias_xm": "小霞音樂",
"source_all": "聚合搜",
"source_all": "聚合搜",
"source_bd": "百度音樂",
"source_kg": "酷狗音樂",
"source_kw": "酷我音樂",
@@ -593,47 +563,43 @@
"source_xm": "蝦米音樂",
"sync__auth_code_input_tip": "請輸入連接碼",
"sync__auth_code_title": "需要輸入連接碼",
"sync__dislike_merge_tip_desc": "合併兩邊列表內容並去重",
"sync__dislike_other_tip_desc": "「取消同步」將不使用不喜歡清單同步功能",
"sync__dislike_overwrite_tip_desc": "被覆蓋者的列表將被替換成覆蓋者的列表",
"sync__dislike_title": "選擇與 {name} 的不喜歡清單同步方式",
"sync__list_merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__list_other_tip_desc": "「取消同步」將不使用清單同步功能。",
"sync__list_overwrite_tip_desc": "被覆蓋者與覆蓋者清單ID相同的清單將被刪除後替換成覆蓋者的清單清單ID不同的清單將合併為一起若勾選完全覆蓋則被覆蓋者的所有清單將會被移除然後替換成覆蓋者的列表。",
"sync__list_title": "選擇與 {name} 的列表同步方式",
"sync__merge_btn_local_remote": "本機列表 合併 遠端列表",
"sync__merge_btn_remote_local": "遠端列表 合併 本機列表",
"sync__merge_btn_local_remote": "本機列表 合併 遠程列表",
"sync__merge_btn_remote_local": "遠程列表 合併 本機列表",
"sync__merge_label": "合併",
"sync__merge_tip": "合併:",
"sync__merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip_desc": "“取消同步”將直接斷開雙方的連接。",
"sync__overwrite": "完全覆蓋",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠列表",
"sync__overwrite_btn_none": "僅使用時同步功能",
"sync__overwrite_btn_remote_local": "遠列表 覆蓋 本機列表",
"sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠列表",
"sync__overwrite_btn_none": "僅使用時同步功能",
"sync__overwrite_btn_remote_local": "遠列表 覆蓋 本機列表",
"sync__overwrite_label": "覆蓋",
"sync__overwrite_tip": "覆蓋:",
"sync__overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表列表ID不同的列表將被合併到一起若勾選完全覆蓋則被覆蓋者的所有列表將被移除然後替換成覆蓋者的列表。",
"sync__title": "選擇與 {name} 的列表同步方式",
"sync_status_disabled": "未連接",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
"tag__lossless_24bit": "24bit",
"theme_add": "新增主題",
"theme_add": "添加主題",
"theme_auto": "道法自然",
"theme_auto_tip": "鼠 右 可開亮、暗主題設窗口",
"theme_auto_tip": "鼠開亮、暗主題設窗口",
"theme_black": "黑燈瞎火",
"theme_blue": "藍田生玉",
"theme_blue2": "清熱版藍",
"theme_blue_plus": "蛋雅深藍",
"theme_china_ink": "近墨者黑",
"theme_edit_modal__app_bg": "應用背景顏色",
"theme_edit_modal__aside_color": "側欄按鈕顏色",
"theme_edit_modal__aside_color": "側欄按鈕顏色",
"theme_edit_modal__badge": "標籤顏色",
"theme_edit_modal__badge_primary": "主顏色",
"theme_edit_modal__badge_secondary": "次要顏色",
"theme_edit_modal__badge_tertiary": "第三顏色",
"theme_edit_modal__bg_image": "背景圖片",
"theme_edit_modal__bg_image_add": "背景圖片",
"theme_edit_modal__bg_image_add": "添加背景圖片",
"theme_edit_modal__bg_image_change": "更改背景圖片",
"theme_edit_modal__bg_image_remove": "移除背景圖片",
"theme_edit_modal__close_btn": "關閉",
@@ -644,7 +610,7 @@
"theme_edit_modal__hide_btn": "隱藏播放詳情頁",
"theme_edit_modal__main_bg": "內容區域背景顏色",
"theme_edit_modal__min_btn": "最小化",
"theme_edit_modal__pick_cancel": "重置",
"theme_edit_modal__pick_cancel": "取消",
"theme_edit_modal__pick_color": "選擇顏色",
"theme_edit_modal__pick_last_color": "使用之前的顏色",
"theme_edit_modal__pick_save": "確認",
@@ -658,40 +624,39 @@
"theme_green": "綠意盎然",
"theme_grey": "灰常美麗",
"theme_happy_new_year": "新年快樂",
"theme_max_tip": "最多只能加10個主題哦一些再加吧 😜",
"theme_max_tip": "最多只能加10個主題哦一些再加吧 😜",
"theme_mid_autumn": "月裡嫦娥",
"theme_ming": "青出於黑",
"theme_naruto": "木葉之村",
"theme_orange": "橙黃橘綠",
"theme_pink": "粉裝玉琢",
"theme_pink": "粉裝玉琢",
"theme_purple": "重斤球紫",
"theme_red": "熱情火",
"theme_red": "熱情火",
"theme_selector_modal__dark_title": "暗色主題",
"theme_selector_modal__light_title": "亮色主題",
"theme_selector_modal__theme_name": "主題名稱",
"theme_selector_modal__title": "跟隨系統主題設置",
"theme_selector_modal__title_tip": ":你可以預先設一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設的相應主題。",
"toggle_source_failed": "換源失敗,請嘗試手動在其他源搜該歌曲播放",
"toggle_source_try": "嘗試切換到其他源...",
"update__downgrade_tip": "我們發現你降級了版本({ver}),若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以過文中提\n到的回饋管道給我們饋😘!\n注意從新版本降級舊版時建議先備份歌單若出現異常則可過清理資料解決,資料目錄路徑文有記錄。",
"update__error_top": "自動下載新版本失敗,你可以嘗試重新下載更新或手動去下載更新,\n新版地址在更新彈窗下有寫,下載新版直接覆蓋安裝即可,若安裝失敗則看常見問題解決,\n注意目前只有Windows安裝版可以自動更新Linux的AppImage、deb版似乎也可以測試),其他版本請手動下載更新!",
"theme_selector_modal__title_tip": ":你可以預先設一個亮色主題及暗色主題,此後將根據系統的亮、暗主題色自動切換為你預先設的相應主題。",
"toggle_source_failed": "換源失敗,請嘗試手動在其他源搜該歌曲播放",
"toggle_source_try": "嘗試切換到其他源...",
"update__downgrade_tip": "我們發現你降級了版本({ver}),若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以過文中提到的反饋渠道給我們饋😘!\n注意從新版本降級舊版時建議先備份歌單若出現異常則可過清理數據解決,數據目錄路徑文有記錄。",
"update__error_top": "自動下載新版本失敗,你可以嘗試重新下載更新或手動去下載更新,\n新版地址在更新彈窗下有寫,下載新版直接覆蓋安裝即可,若安裝失敗則看常見問題解決,\n注意目前只有Windows安裝版可以自動更新Linux的AppImage、deb版似乎也可以未測試其他版本請手動下載更新",
"update__ignore_cancel": "我就不想更新🤨",
"update__ignore_confirm": "好 去更新看看❤️",
"update__ignore_confirm_tip": "目前只有Windows安裝版可以自動更新Linux的AppImage、deb版似乎也可以測試),其他版本請手動下載更新,\n新版址在更新彈窗下有寫,下載新版直接覆蓋安裝即可,若安裝失敗看常見問題解決。",
"update__ignore_confirm_tip": "目前只有Windows安裝版可以自動更新Linux的AppImage、deb版似乎也可以未測試其他版本請手動下載更新\n新版址在更新彈窗下有寫,下載新版直接覆蓋安裝即可,若安裝失敗看常見問題解決。",
"update__ignore_confirm_tip_confirm": "OK 已了解",
"update__ignore_tip": "你現在使用的版本距離最新版本已經落後了 {num} 個版本🤪,為了更好的使用體驗,建議更新到最新版本哦~\n附註:若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以過文中提到的回饋管道給我們饋😘!",
"update__timeout_top": "下載時間過長提示\n\n你前所在網訪問GitHub較慢新版本已經下了一個鐘了還沒完成😳你仍可選擇繼續等但牆裂建議手動更新版本",
"update__ignore_tip": "你現在使用的版本距離最新版本已經落後了 {num} 個版本🤪,為了更好的使用體驗,建議更新到最新版本哦~\n:若使用新版本時遇到問題,請先嘗試閱讀常見問題解決,若你遇到的問題在常見問題中未記錄或無法解決,可以過文中提到的反饋渠道給我們饋😘!",
"update__timeout_top": "下載時間過長提示\n\n你前所在網訪問GitHub較慢新版本已經下了一個鐘了還沒完成😳你仍可選擇繼續等但牆裂建議手動更新版本",
"user_api__allow_show_update_alert": "允許顯示更新彈窗",
"user_api__btn_export": "出",
"user_api__btn_export": "出",
"user_api__btn_import": "導入",
"user_api__btn_remove": "移除",
"user_api__import_file": "選擇音樂API腳本文件",
"user_api__init_failed_alert": "自訂來源 [{name}] 初始化失敗:",
"user_api__max_tip": "最多只能同時存在20個來源哦🤪\n想要繼續導入的話請先移除一些舊的源騰出位置吧",
"user_api__max_tip": "最多只能同時存在20個源哦🤪\n想要繼續導入的話請先移除一些舊的源騰出位置吧",
"user_api__noitem": "這裡竟然是空的 😲",
"user_api__note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
"user_api__readme": "源編寫說明:",
"user_api__title": "自訂來源管理",
"user_api__update_alert": "自訂來源 [{name}] 發現新版本:",
"user_api__update_alert_open_url": "開更新地址"
"user_api__readme": "源編寫說明:",
"user_api__title": "自定義源管理",
"user_api__update_alert": "自定義源 [{name}] 發現新版本:",
"user_api__update_alert_open_url": "開更新地址"
}

View File

@@ -1,18 +0,0 @@
/* eslint-env node */
const { base, typescript } = require('../../.eslintrc.base.cjs')
module.exports = {
root: true,
...base,
overrides: [
{
...typescript,
parserOptions: {
project: './tsconfig.json',
},
},
],
ignorePatterns: [
'vendors',
],
}

View File

@@ -1,4 +1,4 @@
import path from 'node:path'
import { join, dirname } from 'path'
import { existsSync, mkdirSync, renameSync } from 'fs'
import { app, shell, screen, nativeTheme, dialog } from 'electron'
import { URL_SCHEME_RXP } from '@common/constants'
@@ -6,23 +6,26 @@ import { getTheme, initHotKey, initSetting, parseEnvParams } from './utils'
import { navigationUrlWhiteList } from '@common/config'
import defaultSetting from '@common/defaultSetting'
import { closeWindow, isExistWindow as isExistMainWindow, showWindow as showMainWindow } from './modules/winMain'
import { createAppEvent, createDislikeEvent, createListEvent } from '@main/event'
import { createAppEvent, createListEvent } from '@main/event'
import { isMac, log } from '@common/utils'
import createWorkers from './worker'
import { migrateDBData } from './utils/migrate'
import { encodePath, openDirInExplorer } from '@common/utils/electron'
import { openDirInExplorer } from '@common/utils/electron'
export const initGlobalData = () => {
global.isDev = process.env.NODE_ENV !== 'production'
const envParams = parseEnvParams()
global.envParams = {
cmdParams: envParams.cmdParams,
deeplink: envParams.deeplink,
}
global.staticPath =
process.env.NODE_ENV !== 'production'
? webpackStaticPath
: path.join(encodePath(__dirname), 'static')
if (global.isDev) {
// eslint-disable-next-line no-undef
global.staticPath = webpackStaticPath
} else {
global.staticPath = join(__dirname, '/static')
}
}
export const initSingleInstanceHandle = () => {
@@ -72,10 +75,10 @@ export const applyElectronEnvParams = () => {
export const setUserDataPath = () => {
// windows平台下如果应用目录下存在 portable 文件夹则将数据存在此文件下
if (process.platform == 'win32') {
const portablePath = path.join(path.dirname(app.getPath('exe')), '/portable')
const portablePath = join(dirname(app.getPath('exe')), '/portable')
if (existsSync(portablePath)) {
app.setPath('appData', portablePath)
const appDataPath = path.join(portablePath, '/userData')
const appDataPath = join(portablePath, '/userData')
if (!existsSync(appDataPath)) mkdirSync(appDataPath)
app.setPath('userData', appDataPath)
}
@@ -83,12 +86,12 @@ export const setUserDataPath = () => {
const userDataPath = app.getPath('userData')
global.lxOldDataPath = userDataPath
global.lxDataPath = path.join(userDataPath, 'LxDatas')
global.lxDataPath = join(userDataPath, 'LxDatas')
if (!existsSync(global.lxDataPath)) mkdirSync(global.lxDataPath)
}
export const registerDeeplink = (startApp: () => void) => {
if (process.env.NODE_ENV !== 'production' && process.platform === 'win32') {
if (global.isDev && process.platform === 'win32') {
// Set the path of electron.exe and your app.
// These two additional parameters are only available on windows.
// console.log(process.execPath, process.argv)
@@ -114,8 +117,8 @@ export const registerDeeplink = (startApp: () => void) => {
export const listenerAppEvent = (startApp: () => void) => {
app.on('web-contents-created', (event, contents) => {
contents.on('will-navigate', (event, navigationUrl) => {
if (process.env.NODE_ENV !== 'production') {
console.log('navigation to url:', navigationUrl.length > 130 ? navigationUrl.substring(0, 130) + '...' : navigationUrl)
if (global.isDev) {
console.log('navigation to url:', navigationUrl)
return
}
if (!navigationUrlWhiteList.some(url => url.test(navigationUrl))) {
@@ -172,11 +175,9 @@ export const listenerAppEvent = (startApp: () => void) => {
initScreenParams()
})
nativeTheme.addListener('updated', () => {
const shouldUseDarkColors = nativeTheme.shouldUseDarkColors
if (shouldUseDarkColors == global.lx.theme.shouldUseDarkColors) return
global.lx.theme.shouldUseDarkColors = shouldUseDarkColors
global.lx?.event_app.system_theme_change(shouldUseDarkColors)
nativeTheme.addListener('updated', (event: any) => {
const themeInfo: Electron.NativeTheme = event.sender
global.lx?.event_app.system_theme_change(themeInfo.shouldUseDarkColors)
})
}
@@ -214,7 +215,6 @@ export const initAppSetting = async() => {
// mainWindowClosed: true,
event_app: createAppEvent(),
event_list: createListEvent(),
event_dislike: createDislikeEvent(),
appSetting: defaultSetting,
worker: createWorkers(),
hotKey: {
@@ -226,7 +226,7 @@ export const initAppSetting = async() => {
state: new Map(),
},
theme: {
shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
shouldUseDarkColors: false,
theme: {
id: '',
name: '',
@@ -240,13 +240,13 @@ export const initAppSetting = async() => {
if (!isInitialized) {
let dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
if (dbFileExists === null) {
const backPath = path.join(global.lxDataPath, `lx.data.db.${Date.now()}.bak`)
const backPath = join(global.lxDataPath, `lx.data.db.${Date.now()}.bak`)
dialog.showMessageBoxSync({
type: 'warning',
message: 'Database verify failed',
detail: `数据库表结构校验失败,我们将把有问题的数据库备份到:${backPath}\n若此问题导致你的数据丢失你可以尝试从备份文件找回它们。\n\nThe database table structure verification failed, we will back up the problematic database to: ${backPath}\nIf this problem causes your data to be lost, you can try to retrieve them from the backup file.`,
})
renameSync(path.join(global.lxDataPath, 'lx.data.db'), backPath)
renameSync(join(global.lxDataPath, 'lx.data.db'), backPath)
openDirInExplorer(backPath)
dbFileExists = await global.lx.worker.dbService.init(global.lxDataPath)
}
@@ -256,7 +256,7 @@ export const initAppSetting = async() => {
}
// global.lx.theme = getTheme()
isInitialized ||= true
isInitialized = true
}
export const quitApp = () => {

View File

@@ -102,4 +102,4 @@ declare class EventType extends Event {
off<K extends keyof EventMethods>(event: K, listener: EventMethods[K]): this
}
export type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>
export declare type Type = Omit<EventType, keyof Omit<EventEmitter, 'on' | 'off' | 'once'>>

Some files were not shown because too many files have changed in this diff Show More